diff --git a/contracts/ForkingManager.sol b/contracts/ForkingManager.sol index 3b0993da..b1365ba8 100644 --- a/contracts/ForkingManager.sol +++ b/contracts/ForkingManager.sol @@ -37,40 +37,10 @@ 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; - } - - DisputeData public disputeData; - - // 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; - } + // Counter for new proposals to fork + uint256 public proposalCounter = 0; + // mapping to store the fork proposal data + mapping(uint256 => ForkProposal) public forkProposals; /// @inheritdoc IForkingManager function initialize( @@ -80,7 +50,8 @@ contract ForkingManager is IForkingManager, ForkableStructure { address _parentContract, address _globalExitRoot, uint256 _arbitrationFee, - address _chainIdManager + address _chainIdManager, + ForkProposal[] calldata proposals ) external initializer { zkEVM = _zkEVM; bridge = _bridge; @@ -90,24 +61,56 @@ contract ForkingManager is IForkingManager, ForkableStructure { arbitrationFee = _arbitrationFee; chainIdManager = _chainIdManager; ForkableStructure.initialize(address(this), _parentContract); + for (uint i = 0; i < proposals.length; i++) { + forkProposals[i] = proposals[i]; + } + proposalCounter = proposals.length; } /** - * @notice function to initiate and execute the fork - * @param _disputeData the dispute contract and call to identify the dispute + * @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 preparationTime is the duration until when the fork can be executed + * @return counter: A index of the fork proposal */ function initiateFork( - DisputeData memory _disputeData, - NewImplementations calldata newImplementations - ) external onlyBeforeForking { + DisputeData memory disputeData, + NewImplementations calldata newImplementations, + uint256 preparationTime + ) external onlyBeforeForking returns (uint256) { // Charge the forking fee IERC20(forkonomicToken).safeTransferFrom( msg.sender, address(this), arbitrationFee ); + uint256 counter = proposalCounter; + // Store the dispute contract and call to identify the dispute + forkProposals[counter] = ForkProposal({ + disputeData: disputeData, + proposedImplementations: newImplementations, + // solhint-disable-next-line not-rely-on-time + executionTime: block.timestamp + preparationTime + }); + proposalCounter = counter + 1; + return counter; + } + + /** + * @dev function that executes a fork proposal + * @param counter the counter that was given while creating the fork proposal + */ + function executeFork(uint256 counter) external onlyBeforeForking { + require( + forkProposals[counter].executionTime != 0 && + // solhint-disable-next-line not-rely-on-time + forkProposals[counter].executionTime <= block.timestamp, + "ForkingManager: fork not ready" + ); + NewImplementations memory newImplementations = forkProposals[counter] + .proposedImplementations; + // Create the children of each contract NewInstances memory newInstances; ( @@ -236,24 +239,40 @@ contract ForkingManager is IForkingManager, ForkableStructure { ); //Initialize the forking manager contracts - IForkingManager(newInstances.forkingManager.one).initialize( - newInstances.zkEVM.one, - newInstances.bridge.one, - newInstances.forkonomicToken.one, - address(this), - newInstances.globalExitRoot.one, - arbitrationFee, - chainIdManager - ); - IForkingManager(newInstances.forkingManager.two).initialize( - newInstances.zkEVM.two, - newInstances.zkEVM.two, - newInstances.forkonomicToken.two, - address(this), - newInstances.globalExitRoot.two, - arbitrationFee, - chainIdManager - ); + { + ForkProposal[] memory proposals = new ForkProposal[]( + proposalCounter - 1 + ); + { + uint256 skipAddition = 0; + for (uint i = 0; i < proposalCounter - 1; i++) { + if (i == counter) { + skipAddition = 1; + } + proposals[i] = forkProposals[i + skipAddition]; + } + } + IForkingManager(newInstances.forkingManager.one).initialize( + newInstances.zkEVM.one, + newInstances.bridge.one, + newInstances.forkonomicToken.one, + address(this), + newInstances.globalExitRoot.one, + arbitrationFee, + chainIdManager, + proposals + ); + IForkingManager(newInstances.forkingManager.two).initialize( + newInstances.zkEVM.two, + newInstances.zkEVM.two, + newInstances.forkonomicToken.two, + address(this), + newInstances.globalExitRoot.two, + arbitrationFee, + chainIdManager, + proposals + ); + } //Initialize the global exit root contracts IForkableGlobalExitRoot(newInstances.globalExitRoot.one).initialize( @@ -268,8 +287,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..6e7ec27d 100644 --- a/contracts/interfaces/IForkingManager.sol +++ b/contracts/interfaces/IForkingManager.sol @@ -4,6 +4,46 @@ 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; + } + + // Struct containing the data for the paid fork + struct ForkProposal { + DisputeData disputeData; + NewImplementations proposedImplementations; + uint256 executionTime; + } + function initialize( address _zkEVM, address _bridge, @@ -11,6 +51,7 @@ interface IForkingManager is IForkableStructure { address _parentContract, address _globalExitRoot, uint256 _arbitrationFee, - address _chainIdManager + address _chainIdManager, + ForkProposal[] memory proposals ) external; } diff --git a/test/ForkingManager.t.sol b/test/ForkingManager.t.sol index c3ade804..cab448d4 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) { @@ -157,6 +178,7 @@ contract ForkingManagerTest is Test { rollupVerifierMock, IPolygonZkEVMBridge(address(bridge)) ); + IForkingManager.ForkProposal[] memory proposals; forkmanager.initialize( address(zkevm), address(bridge), @@ -164,7 +186,8 @@ contract ForkingManagerTest is Test { address(0x0), address(globalExitRoot), arbitrationFee, - chainIdManager + chainIdManager, + proposals ); forkonomicToken.initialize( address(forkmanager), @@ -176,34 +199,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, @@ -211,7 +214,8 @@ contract ForkingManagerTest is Test { globalExitRootImplementation: newGlobalExitRootImplementation, verifier: newVerifierImplementation, forkID: newForkID - }) + }), + 0 ); // Mint and approve the arbitration fee for the test contract @@ -227,11 +231,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, @@ -239,46 +243,21 @@ contract ForkingManagerTest is Test { globalExitRootImplementation: newGlobalExitRootImplementation, verifier: newVerifierImplementation, forkID: newForkID - }) + }), + 0 ); } - 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( + uint256 id = forkmanager.initiateFork( disputeData, - ForkingManager.NewImplementations({ + IForkingManager.NewImplementations({ bridgeImplementation: newBridgeImplementation, zkEVMImplementation: newZkevmImplementation, forkonomicTokenImplementation: newForkonomicTokenImplementation, @@ -286,8 +265,10 @@ contract ForkingManagerTest is Test { globalExitRootImplementation: newGlobalExitRootImplementation, verifier: newVerifierImplementation, forkID: newForkID - }) + }), + 0 ); + forkmanager.executeFork(id); // Fetch the children from the ForkingManager (address childForkmanager1, address childForkmanager2) = forkmanager @@ -412,16 +393,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 +404,173 @@ 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); + uint256 id = forkmanager.initiateFork( + disputeData, + IForkingManager.NewImplementations({ + bridgeImplementation: newBridgeImplementation, + zkEVMImplementation: newZkevmImplementation, + forkonomicTokenImplementation: newForkonomicTokenImplementation, + forkingManagerImplementation: newForkmanagerImplementation, + globalExitRootImplementation: newGlobalExitRootImplementation, + verifier: newVerifierImplementation, + forkID: newForkID + }), + 0 + ); + forkmanager.executeFork(id); + + ( + ForkingManager.DisputeData memory receivedDisputeData, + , + uint256 receivedExecutionTime + ) = ForkingManager(forkmanager).forkProposals(0); + + // Assert the dispute contract and call stored in the ForkingManager match the ones we provided + assertEq(receivedDisputeData.disputeContract, disputeContract); + assertEq(receivedDisputeData.disputeCall, disputeCall); + assertEq(receivedExecutionTime, testTimestamp); + } + + function testExecuteForkRespectsTime() public { + // reverts on empty proposal list + vm.expectRevert("ForkingManager: fork not ready"); + forkmanager.executeFork(0); + + // 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 duration = 100; + uint256 testTimestamp = 123454234; + vm.warp(testTimestamp); + uint256 id = forkmanager.initiateFork( + disputeData, + IForkingManager.NewImplementations({ + bridgeImplementation: newBridgeImplementation, + zkEVMImplementation: newZkevmImplementation, + forkonomicTokenImplementation: newForkonomicTokenImplementation, + forkingManagerImplementation: newForkmanagerImplementation, + globalExitRootImplementation: newGlobalExitRootImplementation, + verifier: newVerifierImplementation, + forkID: newForkID + }), + duration + ); + + vm.expectRevert("ForkingManager: fork not ready"); + forkmanager.executeFork(id); + vm.warp(testTimestamp + duration); + forkmanager.executeFork(id); + } + + 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); + uint256 id = forkmanager.initiateFork( + disputeData, + IForkingManager.NewImplementations({ + bridgeImplementation: newBridgeImplementation, + zkEVMImplementation: newZkevmImplementation, + forkonomicTokenImplementation: newForkonomicTokenImplementation, + forkingManagerImplementation: newForkmanagerImplementation, + globalExitRootImplementation: newGlobalExitRootImplementation, + verifier: newVerifierImplementation, + forkID: newForkID + }), + 0 + ); + + forkmanager.executeFork(id); + vm.expectRevert("No changes after forking"); + forkmanager.executeFork(id); + } + + function testExecuteForkCopiesForkProposals() 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 + }), + 0 + ); + disputeData.disputeCall = "0x2"; + uint256 id = forkmanager.initiateFork( + disputeData, + IForkingManager.NewImplementations({ + bridgeImplementation: newBridgeImplementation, + zkEVMImplementation: newZkevmImplementation, + forkonomicTokenImplementation: newForkonomicTokenImplementation, + forkingManagerImplementation: newForkmanagerImplementation, + globalExitRootImplementation: newGlobalExitRootImplementation, + verifier: newVerifierImplementation, + forkID: newForkID + }), + 0 + ); + disputeData.disputeCall = "0x3"; + forkmanager.initiateFork( + disputeData, + IForkingManager.NewImplementations({ + bridgeImplementation: newBridgeImplementation, + zkEVMImplementation: newZkevmImplementation, + forkonomicTokenImplementation: newForkonomicTokenImplementation, + forkingManagerImplementation: newForkmanagerImplementation, + globalExitRootImplementation: newGlobalExitRootImplementation, + verifier: newVerifierImplementation, + forkID: newForkID + }), + 0 + ); + + forkmanager.executeFork(id); + (address forkManagerChild1, address forkManagerChild2) = forkmanager + .getChildren(); + (IForkingManager.DisputeData memory data1, , ) = ForkingManager( + forkManagerChild1 + ).forkProposals(0); + assertEq(data1.disputeCall, "0x1"); + (IForkingManager.DisputeData memory data2, , ) = ForkingManager( + forkManagerChild2 + ).forkProposals(0); + assertEq(data2.disputeCall, "0x1"); + (IForkingManager.DisputeData memory data3, , ) = ForkingManager( + forkManagerChild1 + ).forkProposals(1); + assertEq(data3.disputeCall, "0x3"); + (IForkingManager.DisputeData memory data4, , ) = ForkingManager( + forkManagerChild2 + ).forkProposals(1); + assertEq(data4.disputeCall, "0x3"); + } }