From 576c0fa94eaa3d72bc9994c8de36c6b2b74538aa Mon Sep 17 00:00:00 2001 From: josojo Date: Sun, 5 Nov 2023 00:02:46 +0100 Subject: [PATCH] Delayed fork proposals (#89) This PR allows to submit delayed/scheduled fork proposals. I image that each adjudication framework will enforce its own delay and hence, we don't enforce a specific number. The main concern about this implementation is that different delays might cause overlapping scheduled forks. This implementation copies the forkschedules then just to the new forking managers. This is okay, as different forks should be scheduled to remove different courts.(This could be enforced by outside non-core helper contracts at the submission time of the tx. --- contracts/ForkingManager.sol | 66 +++---- contracts/interfaces/IForkingManager.sol | 33 ++++ test/ForkingManager.t.sol | 226 ++++++++++++++++------- 3 files changed, 224 insertions(+), 101 deletions(-) diff --git a/contracts/ForkingManager.sol b/contracts/ForkingManager.sol index 3b0993da..90a2bed6 100644 --- a/contracts/ForkingManager.sol +++ b/contracts/ForkingManager.sol @@ -37,40 +37,12 @@ contract ForkingManager is IForkingManager, ForkableStructure { // Fee that needs to be paid to initiate a fork uint256 public arbitrationFee; - // Dispute contract and call to identify the dispute - // that will be used to initiate/justify the fork - struct DisputeData { - address disputeContract; - bytes disputeCall; - } - + // Following variables are defined during the fork proposal DisputeData public disputeData; + NewImplementations public proposedImplementations; + uint256 public executionTimeForProposal = 0; - // Struct containing the addresses of the new implementations - struct NewImplementations { - address bridgeImplementation; - address zkEVMImplementation; - address forkonomicTokenImplementation; - address forkingManagerImplementation; - address globalExitRootImplementation; - address verifier; - uint64 forkID; - } - - // Struct that holds an address pair used to store the new child contracts - struct AddressPair { - address one; - address two; - } - - // Struct containing the addresses of the new instances - struct NewInstances { - AddressPair forkingManager; - AddressPair bridge; - AddressPair zkEVM; - AddressPair forkonomicToken; - AddressPair globalExitRoot; - } + uint256 public immutable forkPreparationTime = 1 weeks; /// @inheritdoc IForkingManager function initialize( @@ -93,21 +65,40 @@ contract ForkingManager is IForkingManager, ForkableStructure { } /** - * @notice function to initiate and execute the fork + * @notice function to initiate and schedule the fork * @param _disputeData the dispute contract and call to identify the dispute - * @param newImplementations the addresses of the new implementations that will - * be used to create the second children of the contracts + * @param _newImplementations the addresses of the new implementations that will */ function initiateFork( DisputeData memory _disputeData, - NewImplementations calldata newImplementations + NewImplementations calldata _newImplementations ) external onlyBeforeForking { + require(executionTimeForProposal == 0, "ForkingManager: fork pending"); // Charge the forking fee IERC20(forkonomicToken).safeTransferFrom( msg.sender, address(this), arbitrationFee ); + + disputeData = _disputeData; + proposedImplementations = _newImplementations; + // solhint-disable-next-line not-rely-on-time + executionTimeForProposal = (block.timestamp + forkPreparationTime); + } + + /** + * @dev function that executes a fork proposal + */ + function executeFork() external onlyBeforeForking { + require( + executionTimeForProposal != 0 && + // solhint-disable-next-line not-rely-on-time + executionTimeForProposal <= block.timestamp, + "ForkingManager: fork not ready" + ); + NewImplementations memory newImplementations = proposedImplementations; + // Create the children of each contract NewInstances memory newInstances; ( @@ -268,8 +259,5 @@ contract ForkingManager is IForkingManager, ForkableStructure { newInstances.zkEVM.two, newInstances.bridge.two ); - - // Store the dispute contract and call to identify the dispute - disputeData = _disputeData; } } diff --git a/contracts/interfaces/IForkingManager.sol b/contracts/interfaces/IForkingManager.sol index cf945841..a85be86b 100644 --- a/contracts/interfaces/IForkingManager.sol +++ b/contracts/interfaces/IForkingManager.sol @@ -4,6 +4,39 @@ pragma solidity ^0.8.20; import {IForkableStructure} from "./IForkableStructure.sol"; interface IForkingManager is IForkableStructure { + // Dispute contract and call to identify the dispute + // that will be used to initiate/justify the fork + struct DisputeData { + address disputeContract; + bytes disputeCall; + } + + // Struct containing the addresses of the new implementations + struct NewImplementations { + address bridgeImplementation; + address zkEVMImplementation; + address forkonomicTokenImplementation; + address forkingManagerImplementation; + address globalExitRootImplementation; + address verifier; + uint64 forkID; + } + + // Struct that holds an address pair used to store the new child contracts + struct AddressPair { + address one; + address two; + } + + // Struct containing the addresses of the new instances + struct NewInstances { + AddressPair forkingManager; + AddressPair bridge; + AddressPair zkEVM; + AddressPair forkonomicToken; + AddressPair globalExitRoot; + } + function initialize( address _zkEVM, address _bridge, diff --git a/test/ForkingManager.t.sol b/test/ForkingManager.t.sol index c3ade804..3b51917a 100644 --- a/test/ForkingManager.t.sol +++ b/test/ForkingManager.t.sol @@ -7,6 +7,7 @@ import {ForkableZkEVM} from "../contracts/ForkableZkEVM.sol"; import {ForkonomicToken} from "../contracts/ForkonomicToken.sol"; import {ForkableGlobalExitRoot} from "../contracts/ForkableGlobalExitRoot.sol"; import {IBasePolygonZkEVMGlobalExitRoot} from "@RealityETH/zkevm-contracts/contracts/interfaces/IPolygonZkEVMGlobalExitRoot.sol"; +import {IForkingManager} from "../contracts/interfaces/IForkingManager.sol"; import {IVerifierRollup} from "@RealityETH/zkevm-contracts/contracts/interfaces/IVerifierRollup.sol"; import {IPolygonZkEVMBridge} from "@RealityETH/zkevm-contracts/contracts/interfaces/IPolygonZkEVMBridge.sol"; import {PolygonZkEVMBridge} from "@RealityETH/zkevm-contracts/contracts/inheritedMainContracts/PolygonZkEVMBridge.sol"; @@ -60,6 +61,26 @@ contract ForkingManagerTest is Test { uint64 public firstChainId = 1; uint64 public secondChainId = 2; + // Setup new implementations for the fork + address public newBridgeImplementation = address(new ForkableBridge()); + address public newForkmanagerImplementation = address(new ForkingManager()); + address public newZkevmImplementation = address(new ForkableZkEVM()); + address public newVerifierImplementation = + address(0x1234567890123456789012345678901234567894); + address public newGlobalExitRootImplementation = + address(new ForkableGlobalExitRoot()); + address public newForkonomicTokenImplementation = + address(new ForkonomicToken()); + address public disputeContract = + address(0x1234567890123456789012345678901234567894); + bytes public disputeCall = "0x34567890129"; + + ForkingManager.DisputeData public disputeData = + IForkingManager.DisputeData({ + disputeContract: disputeContract, + disputeCall: disputeCall + }); + event Transfer(address indexed from, address indexed to, uint256 tokenId); function bytesToAddress(bytes32 b) public pure returns (address) { @@ -176,34 +197,14 @@ contract ForkingManagerTest is Test { } function testInitiateForkChargesFees() public { - // Setup new implementations for the fork - address newBridgeImplementation = address(new ForkableBridge()); - address newForkmanagerImplementation = address(new ForkingManager()); - address newZkevmImplementation = address(new ForkableZkEVM()); - address newVerifierImplementation = address( - 0x1234567890123456789012345678901234567894 - ); - - address newGlobalExitRootImplementation = address( - new ForkableGlobalExitRoot() - ); - address newForkonomicTokenImplementation = address( - new ForkonomicToken() - ); - - address disputeContract = address( - 0x1234567890123456789012345678901234567894 - ); - bytes memory disputeCall = "0x34567890129"; - // Call the initiateFork function to create a new fork vm.expectRevert(bytes("ERC20: insufficient allowance")); forkmanager.initiateFork( - ForkingManager.DisputeData({ + IForkingManager.DisputeData({ disputeContract: disputeContract, disputeCall: disputeCall }), - ForkingManager.NewImplementations({ + IForkingManager.NewImplementations({ bridgeImplementation: newBridgeImplementation, zkEVMImplementation: newZkevmImplementation, forkonomicTokenImplementation: newForkonomicTokenImplementation, @@ -227,11 +228,11 @@ contract ForkingManagerTest is Test { ); forkmanager.initiateFork( - ForkingManager.DisputeData({ + IForkingManager.DisputeData({ disputeContract: disputeContract, disputeCall: disputeCall }), - ForkingManager.NewImplementations({ + IForkingManager.NewImplementations({ bridgeImplementation: newBridgeImplementation, zkEVMImplementation: newZkevmImplementation, forkonomicTokenImplementation: newForkonomicTokenImplementation, @@ -243,42 +244,16 @@ contract ForkingManagerTest is Test { ); } - function testInitiateForkSetsCorrectImplementations() public { - // Setup new implementations for the fork - address newBridgeImplementation = address(new ForkableBridge()); - address newForkmanagerImplementation = address(new ForkingManager()); - address newZkevmImplementation = address(new ForkableZkEVM()); - address newVerifierImplementation = address( - 0x1234567890123456789012345678901234567894 - ); - - address newGlobalExitRootImplementation = address( - new ForkableGlobalExitRoot() - ); - address newForkonomicTokenImplementation = address( - new ForkonomicToken() - ); - - address disputeContract = address( - 0x1234567890123456789012345678901234567894 - ); - bytes memory disputeCall = "0x34567890129"; - + function testInitiateForkAndExecuteSetsCorrectImplementations() public { // Mint and approve the arbitration fee for the test contract forkonomicToken.approve(address(forkmanager), arbitrationFee); vm.prank(address(this)); forkonomicToken.mint(address(this), arbitrationFee); - ForkingManager.DisputeData memory disputeData = ForkingManager - .DisputeData({ - disputeContract: disputeContract, - disputeCall: disputeCall - }); - // Call the initiateFork function to create a new fork forkmanager.initiateFork( disputeData, - ForkingManager.NewImplementations({ + IForkingManager.NewImplementations({ bridgeImplementation: newBridgeImplementation, zkEVMImplementation: newZkevmImplementation, forkonomicTokenImplementation: newForkonomicTokenImplementation, @@ -288,6 +263,8 @@ contract ForkingManagerTest is Test { forkID: newForkID }) ); + vm.warp(block.timestamp + forkmanager.forkPreparationTime() + 1); + forkmanager.executeFork(); // Fetch the children from the ForkingManager (address childForkmanager1, address childForkmanager2) = forkmanager @@ -412,16 +389,6 @@ contract ForkingManagerTest is Test { childForkmanager1 ); } - { - ( - address receivedDisputeContract, - bytes memory receivedDisputeCall - ) = ForkingManager(forkmanager).disputeData(); - - // Assert the dispute contract and call stored in the ForkingManager match the ones we provided - assertEq(receivedDisputeContract, disputeContract); - assertEq(receivedDisputeCall, disputeCall); - } { assertEq( chainIdManager, @@ -433,4 +400,139 @@ contract ForkingManagerTest is Test { ); } } + + function testInitiateForkSetsDispuateDataAndExecutionTime() public { + // Mint and approve the arbitration fee for the test contract + forkonomicToken.approve(address(forkmanager), arbitrationFee); + vm.prank(address(this)); + forkonomicToken.mint(address(this), arbitrationFee); + + // Call the initiateFork function to create a new fork + uint256 testTimestamp = 123454234; + vm.warp(testTimestamp); + forkmanager.initiateFork( + disputeData, + IForkingManager.NewImplementations({ + bridgeImplementation: newBridgeImplementation, + zkEVMImplementation: newZkevmImplementation, + forkonomicTokenImplementation: newForkonomicTokenImplementation, + forkingManagerImplementation: newForkmanagerImplementation, + globalExitRootImplementation: newGlobalExitRootImplementation, + verifier: newVerifierImplementation, + forkID: newForkID + }) + ); + skip(forkmanager.forkPreparationTime() + 1); + forkmanager.executeFork(); + + ( + address receivedDisputeContract, + bytes memory receivedDisputeCall + ) = ForkingManager(forkmanager).disputeData(); + uint256 receivedExecutionTime = ForkingManager(forkmanager) + .executionTimeForProposal(); + + // Assert the dispute contract and call stored in the ForkingManager match the ones we provided + assertEq(receivedDisputeContract, disputeContract); + assertEq(receivedDisputeCall, disputeCall); + assertEq( + receivedExecutionTime, + testTimestamp + forkmanager.forkPreparationTime() + ); + } + + function testExecuteForkRespectsTime() public { + // reverts on empty proposal list + vm.expectRevert("ForkingManager: fork not ready"); + forkmanager.executeFork(); + + // Mint and approve the arbitration fee for the test contract + forkonomicToken.approve(address(forkmanager), arbitrationFee); + vm.prank(address(this)); + forkonomicToken.mint(address(this), arbitrationFee); + + // Call the initiateFork function to create a new fork + uint256 testTimestamp = 12354234; + vm.warp(testTimestamp); + forkmanager.initiateFork( + disputeData, + IForkingManager.NewImplementations({ + bridgeImplementation: newBridgeImplementation, + zkEVMImplementation: newZkevmImplementation, + forkonomicTokenImplementation: newForkonomicTokenImplementation, + forkingManagerImplementation: newForkmanagerImplementation, + globalExitRootImplementation: newGlobalExitRootImplementation, + verifier: newVerifierImplementation, + forkID: newForkID + }) + ); + + vm.expectRevert("ForkingManager: fork not ready"); + forkmanager.executeFork(); + vm.warp(testTimestamp + forkmanager.forkPreparationTime() + 1); + forkmanager.executeFork(); + } + + function testExecuteForkCanOnlyExecutedOnce() public { + // Mint and approve the arbitration fee for the test contract + forkonomicToken.approve(address(forkmanager), arbitrationFee); + vm.prank(address(this)); + forkonomicToken.mint(address(this), arbitrationFee); + + // Call the initiateFork function to create a new fork + uint256 testTimestamp = 123454234; + vm.warp(testTimestamp); + forkmanager.initiateFork( + disputeData, + IForkingManager.NewImplementations({ + bridgeImplementation: newBridgeImplementation, + zkEVMImplementation: newZkevmImplementation, + forkonomicTokenImplementation: newForkonomicTokenImplementation, + forkingManagerImplementation: newForkmanagerImplementation, + globalExitRootImplementation: newGlobalExitRootImplementation, + verifier: newVerifierImplementation, + forkID: newForkID + }) + ); + skip(forkmanager.forkPreparationTime() + 1); + forkmanager.executeFork(); + vm.expectRevert("No changes after forking"); + forkmanager.executeFork(); + } + + function testRevertsSecondProposal() public { + // Mint and approve the arbitration fee for the test contract + forkonomicToken.approve(address(forkmanager), 3 * arbitrationFee); + vm.prank(address(this)); + forkonomicToken.mint(address(this), 3 * arbitrationFee); + + // Call the initiateFork function to create a new fork + disputeData.disputeCall = "0x1"; + forkmanager.initiateFork( + disputeData, + IForkingManager.NewImplementations({ + bridgeImplementation: newBridgeImplementation, + zkEVMImplementation: newZkevmImplementation, + forkonomicTokenImplementation: newForkonomicTokenImplementation, + forkingManagerImplementation: newForkmanagerImplementation, + globalExitRootImplementation: newGlobalExitRootImplementation, + verifier: newVerifierImplementation, + forkID: newForkID + }) + ); + disputeData.disputeCall = "0x2"; + vm.expectRevert(bytes("ForkingManager: fork pending")); + forkmanager.initiateFork( + disputeData, + IForkingManager.NewImplementations({ + bridgeImplementation: newBridgeImplementation, + zkEVMImplementation: newZkevmImplementation, + forkonomicTokenImplementation: newForkonomicTokenImplementation, + forkingManagerImplementation: newForkmanagerImplementation, + globalExitRootImplementation: newGlobalExitRootImplementation, + verifier: newVerifierImplementation, + forkID: newForkID + }) + ); + } }