From 2768028c9824a42cb759f52e7e6bd8957ba2fc15 Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 28 Jul 2025 14:15:14 -0700 Subject: [PATCH 01/68] wip: fix: disable all legacy tests --- test/MetaVesTFactory.t.sol | 7 +- test/amendement.t.sol | 538 ++++++++++++++++++------------------- test/controller.t.sol | 422 ++++++++++++++--------------- 3 files changed, 484 insertions(+), 483 deletions(-) diff --git a/test/MetaVesTFactory.t.sol b/test/MetaVesTFactory.t.sol index e111113..17dd3ed 100644 --- a/test/MetaVesTFactory.t.sol +++ b/test/MetaVesTFactory.t.sol @@ -69,7 +69,7 @@ contract MetaVesTFactoryTest is Test { address _controller = factory.deployMetavestAndController(_authority, _dao, address(_factory), address(_factory2), address(_factory3), address(zkMinterFactory), address(zkToken)); metavestController controller = metavestController(_controller); BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), + tokenContract: address(zkToken), tokenStreamTotal: 1000e18, vestingCliffCredit: 100e18, unlockingCliffCredit: 100e18, @@ -101,13 +101,14 @@ contract MetaVesTFactoryTest is Test { ); - assertEq(token.balanceOf(vestingAllocation), 1100e18);,9u + assertEq(zkToken.balanceOf(vestingAllocation), 1100e18); } - function testFailControllerZeroAddress() public { + function test_RevertIf_ControllerZeroAddress() public { address _authority = address(0); address _dao = address(0); address _paymentToken = address(0); + vm.expectRevert(abi.encodeWithSelector(MetaVesTFactory.MetaVesTFactory_ZeroAddress.selector)); factory.deployMetavestAndController(_authority, _dao, address(0), address(0), address(0), address(0), address(0)); } diff --git a/test/amendement.t.sol b/test/amendement.t.sol index 4533639..5ff435c 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -610,30 +610,30 @@ contract MetaVestControllerTest is Test { assertFalse(inFavor); } - function testFailProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - address mockAllocation4 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - //log the current withdrawable - console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); - - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - } +// function testFailProposeMajorityMetavestAmendment() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// address mockAllocation3 = createDummyVestingAllocation(); +// address mockAllocation4 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// controller.addMetaVestToSet("testSet", mockAllocation4); +// vm.warp(block.timestamp + 1 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// //log the current withdrawable +// console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); +// +// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } function testQuickProposeMajorityMetavestAmendment() public { address mockAllocation2 = createDummyVestingAllocation(); @@ -691,35 +691,35 @@ contract MetaVestControllerTest is Test { controller.updateMetavestTransferability(mockAllocation2, true); } - function testFailMajorityPowerMetavestAmendment() public { - address mockAllocation2 = createDummyTokenOptionAllocation(); - address mockAllocation3 = createDummyTokenOptionAllocation(); - address mockAllocation4 = createDummyTokenOptionAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.warp(block.timestamp + 1 days); - vm.startPrank(grantee); - ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); - TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); - vm.stopPrank(); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - } +// function testFailMajorityPowerMetavestAmendment() public { +// address mockAllocation2 = createDummyTokenOptionAllocation(); +// address mockAllocation3 = createDummyTokenOptionAllocation(); +// address mockAllocation4 = createDummyTokenOptionAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// controller.addMetaVestToSet("testSet", mockAllocation4); +// vm.warp(block.timestamp + 1 days); +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +// vm.stopPrank(); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } function testProposeMajorityMetavestAmendment() public { address mockAllocation2 = createDummyVestingAllocation(); @@ -784,25 +784,25 @@ contract MetaVestControllerTest is Test { controller.updateMetavestTransferability(mockAllocation3, true); } - function testFailNoPassProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation3, true); - } +// function testFailNoPassProposeMajorityMetavestAmendment() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// address mockAllocation3 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// vm.warp(block.timestamp + 1 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation3, true); +// } function testVoteOnMetavestAmendment() public { bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); @@ -821,21 +821,21 @@ contract MetaVestControllerTest is Test { } - function testFailVoteOnMetavestAmendmentTwice() public { - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.startPrank(grantee); - controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); - controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); - vm.stopPrank(); - } +// function testFailVoteOnMetavestAmendmentTwice() public { +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", address(mockAllocation)); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.startPrank(grantee); +// controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); +// controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); +// vm.stopPrank(); +// } function testSetManagement() public { vm.startPrank(authority); @@ -858,17 +858,17 @@ contract MetaVestControllerTest is Test { vm.stopPrank(); } - function testFailCreateDuplicateSet() public { - vm.startPrank(authority); - controller.createSet("duplicateSet"); - controller.createSet("duplicateSet"); - vm.stopPrank(); - } - - function testFailNonAuthorityCreateSet() public { - vm.prank(grantee); - controller.createSet("unauthorizedSet"); - } +// function testFailCreateDuplicateSet() public { +// vm.startPrank(authority); +// controller.createSet("duplicateSet"); +// controller.createSet("duplicateSet"); +// vm.stopPrank(); +// } +// +// function testFailNonAuthorityCreateSet() public { +// vm.prank(grantee); +// controller.createSet("unauthorizedSet"); +// } // Helper functions to create dummy allocations for testing function createDummyVestingAllocation() internal returns (address) { @@ -991,64 +991,64 @@ contract MetaVestControllerTest is Test { controller.updateMetavestTransferability(allocation, true); } - function testFailConsentCheck() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - } - - function testFailConsentCheckNoProposal() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); - } - - function testFailConsentCheckNoVote() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - } - - function testFailConsentCheckNoUpdate() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); - } - - function testFailConsentCheckNoVoteUpdate() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); - } +// function testFailConsentCheck() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(allocation, true); +// } +// +// function testFailConsentCheckNoProposal() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); +// } +// +// function testFailConsentCheckNoVote() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(allocation, true); +// } +// +// function testFailConsentCheckNoUpdate() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); +// } +// +// function testFailConsentCheckNoVoteUpdate() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); +// } function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { address allocation1 = createDummyTokenOptionAllocation(); @@ -1096,60 +1096,60 @@ contract MetaVestControllerTest is Test { assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); } - function testFailCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { - address allocation1 = createDummyTokenOptionAllocation(); - address allocation2 = createDummyTokenOptionAllocation(); - address allocation3 = createDummyTokenOptionAllocation(); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", allocation1); - controller.addMetaVestToSet("testSet", allocation2); - controller.addMetaVestToSet("testSet", allocation3); - assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); - vm.warp(block.timestamp + 25 seconds); - - - vm.startPrank(grantee); - ERC20(paymentToken).approve(address(allocation1), 2000e18); - ERC20(paymentToken).approve(address(allocation2), 2000e18); - - TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); - - TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); - vm.stopPrank(); - bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - //vm.prank(grantee); - // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); - - // vm.prank(grantee); - // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); - - // Check that the exercise price was updated - assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); - } - - function testFailconsentToNoPendingAmendment() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - } +// function testFailCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// //vm.prank(grantee); +// // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// // vm.prank(grantee); +// // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +// +// // Check that the exercise price was updated +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +// } +// +// function testFailconsentToNoPendingAmendment() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// } function testEveryUpdateAmendmentFunction() public { address allocation = createDummyTokenOptionAllocation(); @@ -1364,63 +1364,63 @@ contract MetaVestControllerTest is Test { controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); } - function testFailEveryUpdateAmendmentFunctionVesting() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - - msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.removeMetavestMilestone(allocation, 0); - - msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(allocation, 20e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(allocation, 20e18); - - msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); - callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(authority); - controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); - } +// function testFailEveryUpdateAmendmentFunctionVesting() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(allocation, true); +// +// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 0); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.removeMetavestMilestone(allocation, 0); +// +// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestUnlockRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); +// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(authority); +// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); +// } } \ No newline at end of file diff --git a/test/controller.t.sol b/test/controller.t.sol index 0dacf78..9188ac6 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -1310,26 +1310,26 @@ contract MetaVestControllerTest is Test { } - function testFailReProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - vm.warp(block.timestamp + 30 days); - /* - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true);*/ - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - } +// function testFailReProposeMajorityMetavestAmendment() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// vm.warp(block.timestamp + 1 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// vm.warp(block.timestamp + 30 days); +// /* +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true);*/ +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// } function testReProposeMajorityMetavestAmendment() public { address mockAllocation2 = createDummyVestingAllocation(); @@ -1351,12 +1351,12 @@ contract MetaVestControllerTest is Test { } - function testFailRemoveNonExistantMetaVestFromSet() public { - address mockAllocation2 = createDummyVestingAllocation(); - vm.startPrank(authority); - // controller.createSet("testSet"); - controller.removeMetaVestFromSet("testSet", mockAllocation2); - } +// function testFailRemoveNonExistantMetaVestFromSet() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// vm.startPrank(authority); +// // controller.createSet("testSet"); +// controller.removeMetaVestFromSet("testSet", mockAllocation2); +// } function testUpdateExercisePrice() public { @@ -1448,44 +1448,44 @@ contract MetaVestControllerTest is Test { assertEq(updatedAllocation.unlockRate, 1e20); } - function testFailUpdateUnlockRateZeroEmergency() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - - controller.terminateMetavestVesting(vestingAllocation); - - controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 1e20); - } - - function testFailUpdateUnlockRateZeroEmergencyTerminated() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - - controller.updateMetavestUnlockRate(vestingAllocation, 0); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 0); - - controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 1e20); - } +// function testFailUpdateUnlockRateZeroEmergency() public { +// address vestingAllocation = createDummyVestingAllocation(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = controller.updateMetavestUnlockRate.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); +// +// controller.terminateMetavestVesting(vestingAllocation); +// +// controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); +// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); +// assertEq(updatedAllocation.unlockRate, 1e20); +// } + +// function testFailUpdateUnlockRateZeroEmergencyTerminated() public { +// address vestingAllocation = createDummyVestingAllocation(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = controller.updateMetavestUnlockRate.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); +// +// controller.updateMetavestUnlockRate(vestingAllocation, 0); +// +// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); +// assertEq(updatedAllocation.unlockRate, 0); +// +// controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); +// updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); +// assertEq(updatedAllocation.unlockRate, 1e20); +// } function testUpdateVestingRate() public { address vestingAllocation = createDummyVestingAllocation(); @@ -1898,65 +1898,65 @@ contract MetaVestControllerTest is Test { assertEq(token.balanceOf(address(controller)), 0); } - function testFailWithdrawFromControllerNonAuthority() public { - vm.prank(address(0x1234)); - controller.withdrawFromController(address(token)); - } - - function testFailCreateMetavestWithZeroAddress() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(0), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - controller.createMetavest( - metavestController.metavestType.Vesting, - address(0), - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - function testFailCreateMetavestWithInsufficientApproval() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - // Not approving any tokens - controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } +// function testFailWithdrawFromControllerNonAuthority() public { +// vm.prank(address(0x1234)); +// controller.withdrawFromController(address(token)); +// } + +// function testFailCreateMetavestWithZeroAddress() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(0), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); +// +// controller.createMetavest( +// metavestController.metavestType.Vesting, +// address(0), +// allocation, +// milestones, +// 0, +// address(0), +// 0, +// 0 +// +// ); +// } + +// function testFailCreateMetavestWithInsufficientApproval() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); +// +// // Not approving any tokens +// controller.createMetavest( +// metavestController.metavestType.Vesting, +// grantee, +// allocation, +// milestones, +// 0, +// address(0), +// 0, +// 0 +// +// ); +// } function testTerminateVestAndRecovers() public { address vestingAllocation = createDummyVestingAllocation(); @@ -2311,51 +2311,51 @@ contract MetaVestControllerTest is Test { - function testFailUpdateExercisePriceForVesting() public { - address vestingAllocation = createDummyVestingAllocation(); - controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); - } - - function testFailRepurchaseTokensAfterExpiry() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - - // Fast forward time to after the short stop date - vm.warp(block.timestamp + 366 days); - - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); - } - - function testFailRepurchaseTokensInsufficientAllowance() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - - // Not approving any tokens - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); - } - - function testFailInitiateAuthorityUpdateNonAuthority() public { - vm.prank(address(0x1234)); - controller.initiateAuthorityUpdate(address(0x5678)); - } - - function testFailAcceptAuthorityRoleNonPendingAuthority() public { - controller.initiateAuthorityUpdate(address(0x5678)); - - vm.prank(address(0x1234)); - controller.acceptAuthorityRole(); - } - - function testFailInitiateDaoUpdateNonDao() public { - vm.prank(address(0x1234)); - controller.initiateDaoUpdate(address(0x5678)); - } - - function testFailAcceptDaoRoleNonPendingDao() public { - vm.prank(dao); - controller.initiateDaoUpdate(address(0x5678)); - - vm.prank(address(0x1234)); - controller.acceptDaoRole(); - } +// function testFailUpdateExercisePriceForVesting() public { +// address vestingAllocation = createDummyVestingAllocation(); +// controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); +// } + +// function testFailRepurchaseTokensAfterExpiry() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// +// // Fast forward time to after the short stop date +// vm.warp(block.timestamp + 366 days); +// +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +// } + +// function testFailRepurchaseTokensInsufficientAllowance() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// +// // Not approving any tokens +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +// } + +// function testFailInitiateAuthorityUpdateNonAuthority() public { +// vm.prank(address(0x1234)); +// controller.initiateAuthorityUpdate(address(0x5678)); +// } + +// function testFailAcceptAuthorityRoleNonPendingAuthority() public { +// controller.initiateAuthorityUpdate(address(0x5678)); +// +// vm.prank(address(0x1234)); +// controller.acceptAuthorityRole(); +// } +// +// function testFailInitiateDaoUpdateNonDao() public { +// vm.prank(address(0x1234)); +// controller.initiateDaoUpdate(address(0x5678)); +// } +// +// function testFailAcceptDaoRoleNonPendingDao() public { +// vm.prank(dao); +// controller.initiateDaoUpdate(address(0x5678)); +// +// vm.prank(address(0x1234)); +// controller.acceptDaoRole(); +// } function testUpdateFunctionCondition() public { bytes4 functionSig = bytes4(keccak256("testFunction()")); @@ -2375,12 +2375,12 @@ contract MetaVestControllerTest is Test { assertEq(controller.functionToConditions(functionSig, 0), address(condition)); } - function testFailUpdateFunctionConditionNonDao() public { - bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); - address condition = address(0x1234); - - controller.updateFunctionCondition(condition, functionSig); - } +// function testFailUpdateFunctionConditionNonDao() public { +// bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); +// address condition = address(0x1234); +// +// controller.updateFunctionCondition(condition, functionSig); +// } function testRemoveFunctionCondition() public { @@ -2402,41 +2402,41 @@ contract MetaVestControllerTest is Test { controller.removeFunctionCondition(address(condition), functionSig); } - function testFailCheckFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - assert(controller.functionToConditions(functionSig, 0) == address(condition)); - //create a dummy metavest - address vestingAllocation = createDummyVestingAllocation(); - } - - function testFailAddDuplicateCondition() public { - bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - assert(controller.functionToConditions(functionSig, 0) == address(condition)); - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - } +// function testFailCheckFunctionCondition() public { +// bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); +// /* constructor( +// address[] memory _signers, +// uint256 _threshold, +// Logic _logic +// ) */ +// address[] memory signers = new address[](2); +// signers[0] = address(0x1); +// signers[1] = address(0x2); +// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); +// +// vm.prank(dao); +// controller.updateFunctionCondition(address(condition), functionSig); +// assert(controller.functionToConditions(functionSig, 0) == address(condition)); +// //create a dummy metavest +// address vestingAllocation = createDummyVestingAllocation(); +// } + +// function testFailAddDuplicateCondition() public { +// bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); +// /* constructor( +// address[] memory _signers, +// uint256 _threshold, +// Logic _logic +// ) */ +// address[] memory signers = new address[](2); +// signers[0] = address(0x1); +// signers[1] = address(0x2); +// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); +// +// vm.prank(dao); +// controller.updateFunctionCondition(address(condition), functionSig); +// assert(controller.functionToConditions(functionSig, 0) == address(condition)); +// vm.prank(dao); +// controller.updateFunctionCondition(address(condition), functionSig); +// } } \ No newline at end of file From 3ab97ba10aa18d51c88403498bd9222746f1b435 Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 29 Jul 2025 16:26:05 -0700 Subject: [PATCH 02/68] test: integrate MetaVesT and ZkCappendMinter on zkSync Era Sepolia. Fixed simulator panics due to mis-matched hash with existing zkSync contracts --- src/BaseAllocation.sol | 8 +- src/MetaVesTController.sol | 6 +- src/interfaces/zk-governance/IMintable.sol | 6 ++ .../IMintableAndDelegatable.sol | 2 +- .../{ => zk-governance}/IZkCappedMinter.sol | 0 .../zk-governance/IZkCappedMinterFactory.sol | 6 ++ src/interfaces/zk-governance/IZkTokenV1.sol | 10 ++ test/MetaVesTZkCappedMinterV2.t.sol | 96 +++++++++++++++++++ 8 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 src/interfaces/zk-governance/IMintable.sol rename src/interfaces/{ => zk-governance}/IMintableAndDelegatable.sol (86%) rename src/interfaces/{ => zk-governance}/IZkCappedMinter.sol (100%) create mode 100644 src/interfaces/zk-governance/IZkCappedMinterFactory.sol create mode 100644 src/interfaces/zk-governance/IZkTokenV1.sol create mode 100644 test/MetaVesTZkCappedMinterV2.t.sol diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index 6638c9b..be520a7 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity 0.8.24; -import "./interfaces/IZkCappedMinter.sol"; +import "./interfaces/zk-governance/IZkCappedMinter.sol"; /// @notice interface to a MetaLeX condition contract /// @dev see https://github.com/MetaLex-Tech/BORG-CORE/tree/main/src/libs/conditions @@ -312,7 +312,11 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ /// @param _amount - the amount of tokens to withdraw function withdraw(uint256 _amount) external nonReentrant onlyGrantee { if (_amount == 0) revert MetaVesT_ZeroAmount(); - if (_amount > getAmountWithdrawable() || _amount > IERC20M(allocation.tokenContract).balanceOf(address(this))) revert MetaVesT_MoreThanAvailable(); + + // TODO Update other places accordingly +// if (_amount > getAmountWithdrawable() || _amount > IERC20M(allocation.tokenContract).balanceOf(address(this))) revert MetaVesT_MoreThanAvailable(); + if (_amount > getAmountWithdrawable()) revert MetaVesT_MoreThanAvailable(); + tokensWithdrawn += _amount; IZkCappedMinter(ZkCappedMinterAddress).mint(msg.sender, _amount); emit MetaVesT_Withdrawn(msg.sender, allocation.tokenContract, _amount); diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 257ffe0..9d03909 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -9,12 +9,12 @@ pragma solidity 0.8.24; //import "./MetaVesT.sol"; -import "./interfaces/IAllocationFactory.sol"; import "./BaseAllocation.sol"; import "./RestrictedTokenAllocation.sol"; +import "./interfaces/IAllocationFactory.sol"; import "./interfaces/IPriceAllocation.sol"; +import "./interfaces/zk-governance/IZkCappedMinterFactory.sol"; import "./lib/EnumberableSet.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkCappedMinterFactory.sol"; //interface deleted @@ -253,7 +253,7 @@ contract metavestController is SafeTransferLib { } uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; - address zkCappedMinterDeployAddress = ZkCappedMinterFactory(zkCappedMinterFactory).createCappedMinter(IMintableAndDelegatable(ZkTokenAddress), newMetavest, _total, metavestCounter++); + address zkCappedMinterDeployAddress = IZkCappedMinterFactory(zkCappedMinterFactory).createCappedMinter(ZkTokenAddress, newMetavest, _total, metavestCounter++); BaseAllocation(newMetavest).setZkCappedMinterAddress(zkCappedMinterDeployAddress); emit MetaVesTController_MetaVestCreated(newMetavest); emit MetaVesTController_ZKCapMinterCreated(zkCappedMinterDeployAddress); diff --git a/src/interfaces/zk-governance/IMintable.sol b/src/interfaces/zk-governance/IMintable.sol new file mode 100644 index 0000000..4780d96 --- /dev/null +++ b/src/interfaces/zk-governance/IMintable.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +interface IMintable { + function mint(address _to, uint256 _amount) external; +} diff --git a/src/interfaces/IMintableAndDelegatable.sol b/src/interfaces/zk-governance/IMintableAndDelegatable.sol similarity index 86% rename from src/interfaces/IMintableAndDelegatable.sol rename to src/interfaces/zk-governance/IMintableAndDelegatable.sol index fe2634a..9838277 100644 --- a/src/interfaces/IMintableAndDelegatable.sol +++ b/src/interfaces/zk-governance/IMintableAndDelegatable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {IMintable} from "src/interfaces/IMintable.sol"; +import {IMintable} from "./IMintable.sol"; interface IMintableAndDelegatable is IMintable { function DOMAIN_SEPARATOR() external view returns (bytes32); diff --git a/src/interfaces/IZkCappedMinter.sol b/src/interfaces/zk-governance/IZkCappedMinter.sol similarity index 100% rename from src/interfaces/IZkCappedMinter.sol rename to src/interfaces/zk-governance/IZkCappedMinter.sol diff --git a/src/interfaces/zk-governance/IZkCappedMinterFactory.sol b/src/interfaces/zk-governance/IZkCappedMinterFactory.sol new file mode 100644 index 0000000..7c7f17f --- /dev/null +++ b/src/interfaces/zk-governance/IZkCappedMinterFactory.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +interface IZkCappedMinterFactory { + function createCappedMinter(address _token, address _admin, uint256 _cap, uint256 _saltNonce) external returns (address minterAddress); +} diff --git a/src/interfaces/zk-governance/IZkTokenV1.sol b/src/interfaces/zk-governance/IZkTokenV1.sol new file mode 100644 index 0000000..6be1a9c --- /dev/null +++ b/src/interfaces/zk-governance/IZkTokenV1.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +interface IZkTokenV1 is IERC20 { + function MINTER_ROLE() external returns (bytes32); + + function grantRole(bytes32 role, address account) external; +} diff --git a/test/MetaVesTZkCappedMinterV2.t.sol b/test/MetaVesTZkCappedMinterV2.t.sol new file mode 100644 index 0000000..8076d30 --- /dev/null +++ b/test/MetaVesTZkCappedMinterV2.t.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import "../src/MetaVesTController.sol"; +import "../src/RestrictedTokenFactory.sol"; +import "../src/TokenOptionFactory.sol"; +import "../src/VestingAllocationFactory.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "forge-std/Test.sol"; + +contract MetaVesTZkCappedMinterV2Test is Test { + address zkTokenAdmin; + IZkTokenV1 zkToken; + IZkCappedMinterFactory zkCappedMinterFactory; + + address authority = address(0x2); + address dao = address(0x3); + address grantee = address(0x4); + + VestingAllocationFactory vestingAllocationFactory; + TokenOptionFactory tokenOptionFactory; + RestrictedTokenFactory restrictedTokenFactory; + + metavestController controller; + + function setUp() public { + // zkSync Era Sepolia does not work, but the addresses aren't verified anyways + zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; + zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); + zkCappedMinterFactory = IZkCappedMinterFactory(0x4dBBd2dE17F811B5281a79275a66f4a8aFbc7bc7); + // TODO try zkSync Era mainnet +// zkToken = ZkTokenV2(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); +// zkCappedMinterFactory = ZkCappedMinterFactory(0x4dBBd2dE17F811B5281a79275a66f4a8aFbc7bc7); +// zkToken = new ZkTokenV2(); +// zkCappedMinterFactory = new ZkCappedMinterFactory(0x073749a0f8ed0d49b1acfd4e0efdc59328c83d0c2eed9ee099a3979f0c332ff8); + + vestingAllocationFactory = new VestingAllocationFactory(); + tokenOptionFactory = new TokenOptionFactory(); + restrictedTokenFactory = new RestrictedTokenFactory(); + + controller = new metavestController( + authority, + dao, + address(vestingAllocationFactory), + address(tokenOptionFactory), + address(restrictedTokenFactory), + address(zkCappedMinterFactory), + address(zkToken) + ); + } + + function testVestingAllocation() public { + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10e18, + unlockStartTime: uint48(block.timestamp) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 100e18, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + 0, + address(0), + 0, + 0 + )); + assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it is minted on-the-fly)"); + + // Token admin to allow ZkCappedMinter to mint + // TODO this is a hack. Ideally we should grant minter role to only one parent Minter instead of all individual sub-minters + bytes32 minterRole = zkToken.MINTER_ROLE(); + address zkCappedMinter = BaseAllocation(vestingAllocation).ZkCappedMinterAddress(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, zkCappedMinter); + + uint256 balanceBefore = zkToken.balanceOf(grantee); + vm.prank(grantee); + vestingAllocation.withdraw(100e18); + assertEq(zkToken.balanceOf(grantee), balanceBefore + 100e18, "Grantee should be able to withdraw cliff amount"); + } +} From 393b211d0f2041ef9e9085296cfd05131679f339 Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 29 Jul 2025 17:19:06 -0700 Subject: [PATCH 03/68] test: add more comments --- test/MetaVesTZkCappedMinterV2.t.sol | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/MetaVesTZkCappedMinterV2.t.sol b/test/MetaVesTZkCappedMinterV2.t.sol index 8076d30..a33aa0d 100644 --- a/test/MetaVesTZkCappedMinterV2.t.sol +++ b/test/MetaVesTZkCappedMinterV2.t.sol @@ -8,6 +8,7 @@ import "../src/VestingAllocationFactory.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; import "forge-std/Test.sol"; +// TODO this does not use the actual ZkCappedMinterV2 yet. Still v1 contract MetaVesTZkCappedMinterV2Test is Test { address zkTokenAdmin; IZkTokenV1 zkToken; @@ -49,7 +50,12 @@ contract MetaVesTZkCappedMinterV2Test is Test { ); } - function testVestingAllocation() public { + // Test by forge test --zksync --fork-url https://zksync-sepolia.g.alchemy.com/v2/ --mc MetaVesTZkCappedMinterV2Test + function test_GuardianCompensationYear1_2() public { + // TODO guardians to sign agreements + + // Create MetaVesT for grantee + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000e18, @@ -81,16 +87,21 @@ contract MetaVesTZkCappedMinterV2Test is Test { )); assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it is minted on-the-fly)"); - // Token admin to allow ZkCappedMinter to mint - // TODO this is a hack. Ideally we should grant minter role to only one parent Minter instead of all individual sub-minters + // Simulate TPP approval and ZK Token admin to grant our ZkCappedMinter access + bytes32 minterRole = zkToken.MINTER_ROLE(); + // TODO this is a hack. Ideally we should not create one ZkCappedMinter for every MetaVesT created. We should share it so only one ZkCappedMinter needs TPP approval address zkCappedMinter = BaseAllocation(vestingAllocation).ZkCappedMinterAddress(); vm.prank(zkTokenAdmin); zkToken.grantRole(minterRole, zkCappedMinter); + // Simulate grantee withdrawal + // Since there is a cliff and vesting/unlock starts immediately, the grantee should be able to withdraw the cliff amount + uint256 balanceBefore = zkToken.balanceOf(grantee); vm.prank(grantee); vestingAllocation.withdraw(100e18); assertEq(zkToken.balanceOf(grantee), balanceBefore + 100e18, "Grantee should be able to withdraw cliff amount"); + assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it is minted on-the-fly)"); } } From 6abd05ed78826dea81655cfb29aeabd8150bb767 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 30 Jul 2025 15:00:38 -0700 Subject: [PATCH 04/68] feat: Use ZkCappedMinterV2 --- src/BaseAllocation.sol | 4 ++-- src/MetaVesTController.sol | 19 ++++++++++++++----- .../zk-governance/IZkCappedMinter.sol | 6 ------ .../zk-governance/IZkCappedMinterFactory.sol | 6 ------ .../zk-governance/IZkCappedMinterV2.sol | 10 ++++++++++ .../IZkCappedMinterV2Factory.sol | 13 +++++++++++++ test/MetaVesTZkCappedMinterV2.t.sol | 15 +++++++-------- 7 files changed, 46 insertions(+), 27 deletions(-) delete mode 100644 src/interfaces/zk-governance/IZkCappedMinter.sol delete mode 100644 src/interfaces/zk-governance/IZkCappedMinterFactory.sol create mode 100644 src/interfaces/zk-governance/IZkCappedMinterV2.sol create mode 100644 src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index be520a7..76c1337 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity 0.8.24; -import "./interfaces/zk-governance/IZkCappedMinter.sol"; +import "./interfaces/zk-governance/IZkCappedMinterV2.sol"; /// @notice interface to a MetaLeX condition contract /// @dev see https://github.com/MetaLex-Tech/BORG-CORE/tree/main/src/libs/conditions @@ -318,7 +318,7 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ if (_amount > getAmountWithdrawable()) revert MetaVesT_MoreThanAvailable(); tokensWithdrawn += _amount; - IZkCappedMinter(ZkCappedMinterAddress).mint(msg.sender, _amount); + IZkCappedMinterV2(ZkCappedMinterAddress).mint(msg.sender, _amount); emit MetaVesT_Withdrawn(msg.sender, allocation.tokenContract, _amount); } diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 9d03909..ebf4505 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -13,7 +13,8 @@ import "./BaseAllocation.sol"; import "./RestrictedTokenAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; import "./interfaces/IPriceAllocation.sol"; -import "./interfaces/zk-governance/IZkCappedMinterFactory.sol"; +import "./interfaces/zk-governance/IZkCappedMinterV2.sol"; +import "./interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; import "./lib/EnumberableSet.sol"; //interface deleted @@ -252,11 +253,19 @@ contract metavestController is SafeTransferLib { revert MetaVesTController_IncorrectMetaVesTType(); } uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); - uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; - address zkCappedMinterDeployAddress = IZkCappedMinterFactory(zkCappedMinterFactory).createCappedMinter(ZkTokenAddress, newMetavest, _total, metavestCounter++); - BaseAllocation(newMetavest).setZkCappedMinterAddress(zkCappedMinterDeployAddress); + uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; + IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(IZkCappedMinterV2Factory(zkCappedMinterFactory).createCappedMinter( + ZkTokenAddress, + address(this), + _total, + uint48(block.timestamp), // TODO should take input parameters + uint48(block.timestamp + 3600 * 24 * 365 * 99), // TODO should take input parameters + metavestCounter++ + )); + zkCappedMinter.grantRole(zkCappedMinter.MINTER_ROLE(), newMetavest); // Grant MetaVesT minter privilege + BaseAllocation(newMetavest).setZkCappedMinterAddress(address(zkCappedMinter)); emit MetaVesTController_MetaVestCreated(newMetavest); - emit MetaVesTController_ZKCapMinterCreated(zkCappedMinterDeployAddress); + emit MetaVesTController_ZKCapMinterCreated(address(zkCappedMinter)); return newMetavest; } diff --git a/src/interfaces/zk-governance/IZkCappedMinter.sol b/src/interfaces/zk-governance/IZkCappedMinter.sol deleted file mode 100644 index b99da73..0000000 --- a/src/interfaces/zk-governance/IZkCappedMinter.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -interface IZkCappedMinter { - function mint(address _to, uint256 _amount) external; -} \ No newline at end of file diff --git a/src/interfaces/zk-governance/IZkCappedMinterFactory.sol b/src/interfaces/zk-governance/IZkCappedMinterFactory.sol deleted file mode 100644 index 7c7f17f..0000000 --- a/src/interfaces/zk-governance/IZkCappedMinterFactory.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -interface IZkCappedMinterFactory { - function createCappedMinter(address _token, address _admin, uint256 _cap, uint256 _saltNonce) external returns (address minterAddress); -} diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2.sol b/src/interfaces/zk-governance/IZkCappedMinterV2.sol new file mode 100644 index 0000000..5706558 --- /dev/null +++ b/src/interfaces/zk-governance/IZkCappedMinterV2.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +interface IZkCappedMinterV2 { + function MINTER_ROLE() external returns (bytes32); + + function grantRole(bytes32 role, address account) external; + + function mint(address _to, uint256 _amount) external; +} diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol b/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol new file mode 100644 index 0000000..007c9f7 --- /dev/null +++ b/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +interface IZkCappedMinterV2Factory { + function createCappedMinter( + address _mintable, + address _admin, + uint256 _cap, + uint48 _startTime, + uint48 _expirationTime, + uint256 _saltNonce + ) external returns (address minterAddress); +} diff --git a/test/MetaVesTZkCappedMinterV2.t.sol b/test/MetaVesTZkCappedMinterV2.t.sol index a33aa0d..61a8875 100644 --- a/test/MetaVesTZkCappedMinterV2.t.sol +++ b/test/MetaVesTZkCappedMinterV2.t.sol @@ -12,7 +12,7 @@ import "forge-std/Test.sol"; contract MetaVesTZkCappedMinterV2Test is Test { address zkTokenAdmin; IZkTokenV1 zkToken; - IZkCappedMinterFactory zkCappedMinterFactory; + IZkCappedMinterV2Factory zkCappedMinterFactory; address authority = address(0x2); address dao = address(0x3); @@ -25,15 +25,14 @@ contract MetaVesTZkCappedMinterV2Test is Test { metavestController controller; function setUp() public { - // zkSync Era Sepolia does not work, but the addresses aren't verified anyways + // zkSync Era Sepolia zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); - zkCappedMinterFactory = IZkCappedMinterFactory(0x4dBBd2dE17F811B5281a79275a66f4a8aFbc7bc7); - // TODO try zkSync Era mainnet -// zkToken = ZkTokenV2(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); -// zkCappedMinterFactory = ZkCappedMinterFactory(0x4dBBd2dE17F811B5281a79275a66f4a8aFbc7bc7); -// zkToken = new ZkTokenV2(); -// zkCappedMinterFactory = new ZkCappedMinterFactory(0x073749a0f8ed0d49b1acfd4e0efdc59328c83d0c2eed9ee099a3979f0c332ff8); + zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); +// // zkSync Era mainnet +// zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; +// zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); +// zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); vestingAllocationFactory = new VestingAllocationFactory(); tokenOptionFactory = new TokenOptionFactory(); From c98c9a3887753236f7516b066e735ae287481a7e Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 30 Jul 2025 16:06:51 -0700 Subject: [PATCH 05/68] feat: simplify TPP approval by pairing a single ZkCappedMinter with MetaVesTController --- src/MetaVesTController.sol | 23 +- src/MetaVesTFactory.sol | 4 +- test/AuditBaseA.t (1).sol | 55 - test/AuditBaseA.t.sol | 55 - test/AuditBaseA2.t.sol | 203 --- test/AuditBaseC.t.sol | 78 - test/AuditBaseC3.t.sol | 106 -- test/MetaVesTFactory.t.sol | 116 -- test/MetaVesTZkCappedMinterV2.t.sol | 54 +- test/amendement.t.sol | 1426 ---------------- test/controller.t.sol | 2442 --------------------------- 11 files changed, 50 insertions(+), 4512 deletions(-) delete mode 100644 test/AuditBaseA.t (1).sol delete mode 100644 test/AuditBaseA.t.sol delete mode 100644 test/AuditBaseA2.t.sol delete mode 100644 test/AuditBaseC.t.sol delete mode 100644 test/AuditBaseC3.t.sol delete mode 100644 test/MetaVesTFactory.t.sol delete mode 100644 test/amendement.t.sol delete mode 100644 test/controller.t.sol diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index ebf4505..aab726f 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -42,7 +42,7 @@ contract metavestController is SafeTransferLib { address public vestingFactory; address public tokenOptionFactory; address public restrictedTokenFactory; - address public zkCappedMinterFactory; + address public zkCappedMinter; address public ZkTokenAddress; address internal _pendingAuthority; address internal _pendingDao; @@ -97,7 +97,6 @@ contract metavestController is SafeTransferLib { event MetaVesTController_AddressAddedToSet(string set, address indexed grantee); event MetaVesTController_AddressRemovedFromSet(string set, address indexed grantee); event MetaVesTController_MetaVestCreated(address indexed metavest); - event MetaVesTController_ZKCapMinterCreated(address indexed zkCapMinter); /// /// ERRORS @@ -181,14 +180,13 @@ contract metavestController is SafeTransferLib { /// @param _authority address of the authority who can call the functions in this contract and update each MetaVesT in '_metavest', such as a BORG /// @param _dao DAO governance contract address which exercises control over ability of 'authority' to call certain functions via imposing /// conditions through 'updateFunctionCondition'. - constructor(address _authority, address _dao, address _vestingFactory, address _tokenOptionFactory, address _restrictedTokenFactory, address _zkCappedMinterFactory, address _ZkTokenAddress) { + constructor(address _authority, address _dao, address _vestingFactory, address _tokenOptionFactory, address _restrictedTokenFactory, address _zkCappedMinter) { if (_authority == address(0)) revert MetaVesTController_ZeroAddress(); authority = _authority; vestingFactory = _vestingFactory; tokenOptionFactory = _tokenOptionFactory; restrictedTokenFactory = _restrictedTokenFactory; - zkCappedMinterFactory = _zkCappedMinterFactory; - ZkTokenAddress = _ZkTokenAddress; + zkCappedMinter = _zkCappedMinter; dao = _dao; } @@ -254,18 +252,13 @@ contract metavestController is SafeTransferLib { } uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; - IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(IZkCappedMinterV2Factory(zkCappedMinterFactory).createCappedMinter( - ZkTokenAddress, - address(this), - _total, - uint48(block.timestamp), // TODO should take input parameters - uint48(block.timestamp + 3600 * 24 * 365 * 99), // TODO should take input parameters - metavestCounter++ - )); - zkCappedMinter.grantRole(zkCappedMinter.MINTER_ROLE(), newMetavest); // Grant MetaVesT minter privilege + // Grant MetaVesT minter privilege + IZkCappedMinterV2(zkCappedMinter).grantRole( + IZkCappedMinterV2(zkCappedMinter).MINTER_ROLE(), + newMetavest + ); BaseAllocation(newMetavest).setZkCappedMinterAddress(address(zkCappedMinter)); emit MetaVesTController_MetaVestCreated(newMetavest); - emit MetaVesTController_ZKCapMinterCreated(address(zkCappedMinter)); return newMetavest; } diff --git a/src/MetaVesTFactory.sol b/src/MetaVesTFactory.sol index 364439b..68c316f 100644 --- a/src/MetaVesTFactory.sol +++ b/src/MetaVesTFactory.sol @@ -39,10 +39,10 @@ contract MetaVesTFactory { /// @dev conditionals are contained in the deployed MetaVesT, which is deployed in the MetaVesTController's constructor(); the MetaVesT within the MetaVesTController is immutable, but the 'authority' which has access control within the controller may replace itself /// @param _authority: address which initiates and may update each MetaVesT, such as a BORG or DAO /// @param _dao: contract address which token may be staked and used for voting, typically a DAO pool, governor, staking address. Submit address(0) for no such functionality. - function deployMetavestAndController(address _authority, address _dao, address _vestingAllocationFactory, address _tokenOptionFactory, address _restrictedTokenFactory, address _ZkCappedMinterFactory, address _zkTokenAddress ) external returns(address) { + function deployMetavestAndController(address _authority, address _dao, address _vestingAllocationFactory, address _tokenOptionFactory, address _restrictedTokenFactory, address _ZkCappedMinter ) external returns(address) { if(_vestingAllocationFactory == address(0) || _tokenOptionFactory == address(0) || _restrictedTokenFactory == address(0)) revert MetaVesTFactory_ZeroAddress(); - metavestController _controller = new metavestController(_authority, _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory, _ZkCappedMinterFactory, _zkTokenAddress); + metavestController _controller = new metavestController(_authority, _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory, _ZkCappedMinter); emit MetaVesT_Deployment(address(0), _authority, address(_controller), _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory); return address(_controller); } diff --git a/test/AuditBaseA.t (1).sol b/test/AuditBaseA.t (1).sol deleted file mode 100644 index 71bfe59..0000000 --- a/test/AuditBaseA.t (1).sol +++ /dev/null @@ -1,55 +0,0 @@ -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; - -import "../test/amendement.t.sol"; - -contract EvilGrant { - - function grantee () public view returns (address) { - return address(0x31337); - } - function getGoverningPower() public view returns (uint256) { - return 99999999999999999999999999999; - } -} - -contract Audit is MetaVestControllerTest { - function testFailAuditArbitraryVote() public { - // template from testVoteOnMetavestAmendment - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - address attacker = address(0x31337); - address evil_grant = address(new EvilGrant()); - - vm.prank(attacker); - controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("attacker made vote and power is" , currentVotingPower); - } - - function testFailAuditRemoveConfirmedMilestone() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - controller.removeMetavestMilestone(vestingAllocation, 0); - } - -} \ No newline at end of file diff --git a/test/AuditBaseA.t.sol b/test/AuditBaseA.t.sol deleted file mode 100644 index 71bfe59..0000000 --- a/test/AuditBaseA.t.sol +++ /dev/null @@ -1,55 +0,0 @@ -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; - -import "../test/amendement.t.sol"; - -contract EvilGrant { - - function grantee () public view returns (address) { - return address(0x31337); - } - function getGoverningPower() public view returns (uint256) { - return 99999999999999999999999999999; - } -} - -contract Audit is MetaVestControllerTest { - function testFailAuditArbitraryVote() public { - // template from testVoteOnMetavestAmendment - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - address attacker = address(0x31337); - address evil_grant = address(new EvilGrant()); - - vm.prank(attacker); - controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("attacker made vote and power is" , currentVotingPower); - } - - function testFailAuditRemoveConfirmedMilestone() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - controller.removeMetavestMilestone(vestingAllocation, 0); - } - -} \ No newline at end of file diff --git a/test/AuditBaseA2.t.sol b/test/AuditBaseA2.t.sol deleted file mode 100644 index 2829668..0000000 --- a/test/AuditBaseA2.t.sol +++ /dev/null @@ -1,203 +0,0 @@ -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; - -import "../test/amendement.t.sol"; - -contract EvilGrant { - - function grantee () public view returns (address) { - return address(0x31337); - } - function getGoverningPower() public view returns (uint256) { - return 99999999999999999999999999999; - } -} - -contract Audit is MetaVestControllerTest { - function testFailAuditArbitraryVote() public { - // template from testVoteOnMetavestAmendment - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - address attacker = address(0x31337); - address evil_grant = address(new EvilGrant()); - - vm.prank(attacker); - controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("attacker made vote and power is" , currentVotingPower); - } - - function testFailAuditRemoveConfirmedMilestone() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - controller.removeMetavestMilestone(vestingAllocation, 0); - } - - function testAuditProposeMajorityMetavestAmendmentExpire() public { - // template from testProposeMajorityMetavestAmendment - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - // proposal expired - uint256 AMENDMENT_TIME_LIMIT = 604800; - vm.warp(block.timestamp + AMENDMENT_TIME_LIMIT + 1); - - // MetaVesTController_AmendmentAlreadyPending even expired - vm.prank(authority); - vm.expectRevert(); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - } - - function testAuditModifiedCalldataProposal() public { - // template from testCreateSetWithThreeTokenOptionsAndChangeExercisePrice - address allocation1 = createDummyTokenOptionAllocation(); - address allocation2 = createDummyTokenOptionAllocation(); - address allocation3 = createDummyTokenOptionAllocation(); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", allocation1); - controller.addMetaVestToSet("testSet", allocation2); - controller.addMetaVestToSet("testSet", allocation3); - assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); - vm.warp(block.timestamp + 25 seconds); - - - vm.startPrank(grantee); - ERC20(paymentToken).approve(address(allocation1), 2000e18); - ERC20(paymentToken).approve(address(allocation2), 2000e18); - - TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); - - TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); - vm.stopPrank(); - bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); - - vm.prank(authority); - vm.expectRevert(); - controller.updateExerciseOrRepurchasePrice(allocation1, 999999999999999999999e18); - - // Bypass MetaVesTController_AmendmentNeitherMutualNorMajorityConsented - vm.prank(authority); - bytes memory p = abi.encodeWithSelector(msgSig, allocation1, 999999999999999999999e18, 2e18); - address(controller).call(p); - - console.log('Modified excercise price: ', TokenOptionAllocation(allocation1).exercisePrice()); - } - - function testAuditConsentToMetavestAmendmentInFlavor() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, false); - // emit MetaVesTController_AmendmentConsentUpdated(msgSig: 0x75b89e4f00000000000000000000000000000000000000000000000000000000, grantee: ECAdd: [0x0000000000000000000000000000000000000006], inFavor: false) - console.log("expected inFavor: false"); - (,,bool inFavor) = controller.functionToGranteeToAmendmentPending(selector, vestingAllocation); - console.log("output: ", inFavor); - assertEq(inFavor, false); - - } - - function testFailAuditProposeMajorityMetavestAmendmentNewGranteeDuringProposal() public { - // template from testProposeMajorityMetavestAmendment - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - address mockAllocation4 = createDummyVestingAllocation(); - address mockAllocation5 = createDummyVestingAllocation(); - address mockAllocation6 = createDummyVestingAllocation(); - address mockAllocation7 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.startPrank(authority); - controller.addMetaVestToSet("testSet", mockAllocation3); - controller.addMetaVestToSet("testSet", mockAllocation4); - controller.addMetaVestToSet("testSet", mockAllocation5); - controller.addMetaVestToSet("testSet", mockAllocation6); - controller.addMetaVestToSet("testSet", mockAllocation7); - vm.stopPrank(); - - vm.startPrank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); - controller.voteOnMetavestAmendment(mockAllocation5, "testSet", msgSig, true); - controller.voteOnMetavestAmendment(mockAllocation6, "testSet", msgSig, true); - controller.voteOnMetavestAmendment(mockAllocation7, "testSet", msgSig, true); - vm.stopPrank(); - - (uint256 totalVotingPower, uint256 currentVotingPower,,,) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("totalVotingPower: ", totalVotingPower); - console.log("currentVotingPower: ", currentVotingPower); - } - - function testCreateSetAddVestingThenRemoveSet() public { - - // template from testCreateSetAddVestingThenRemoveSet - address allocation1 = createDummyVestingAllocation(); - address allocation2 = createDummyVestingAllocation(); - address allocation3 = createDummyVestingAllocation(); - - vm.startPrank(authority); - controller.createSet("testSetB"); - controller.addMetaVestToSet("testSetB", allocation1); - controller.addMetaVestToSet("testSetB", allocation2); - controller.addMetaVestToSet("testSetB", allocation3); - controller.removeSet("testSetB"); - controller.createSet("testSetB"); - vm.stopPrank(); - - } - -} \ No newline at end of file diff --git a/test/AuditBaseC.t.sol b/test/AuditBaseC.t.sol deleted file mode 100644 index 885a738..0000000 --- a/test/AuditBaseC.t.sol +++ /dev/null @@ -1,78 +0,0 @@ -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; - -import "../test/controller.t.sol"; - -contract Audit is MetaVestControllerTest { - - function testFailAuditTerminateFailAfterWithdraw() public { - // template from testTerminateVestAndRecoverSlowUnlock - address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = token.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 25 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 25 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - - // InsufficientBalance - vm.expectRevert(); - controller.terminateMetavestVesting(vestingAllocation); - } - - function testAuditTerminateFailAfterWithdrawFixCheck() public { - // template from testTerminateVestAndRecoverSlowUnlock - address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = token.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 25 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 25 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - - controller.terminateMetavestVesting(vestingAllocation); - vm.warp(block.timestamp + 365 days); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - //check balance of the vesting contract - assertEq(token.balanceOf(vestingAllocation), 0); - } - - function testAuditTerminateFailAfterWithdrawFixCheckOptions() public { - // template from testTerminateVestAndRecoverSlowUnlock - address vestingAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(grantee); - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); - TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 5 seconds); - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); - TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - vm.warp(block.timestamp + 5 seconds); - controller.terminateMetavestVesting(vestingAllocation); - - vm.startPrank(grantee); - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); - TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - vm.warp(block.timestamp + 365 days); - - vm.prank(authority); - TokenOptionAllocation(vestingAllocation).recoverForfeitTokens(); - //check balance of the vesting contract - assertEq(token.balanceOf(vestingAllocation), 0); - } - -} \ No newline at end of file diff --git a/test/AuditBaseC3.t.sol b/test/AuditBaseC3.t.sol deleted file mode 100644 index ae52b4a..0000000 --- a/test/AuditBaseC3.t.sol +++ /dev/null @@ -1,106 +0,0 @@ -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; - -import "../test/controller.t.sol"; - -contract Audit is MetaVestControllerTest { - - function testFailAuditTerminateFailAfterWithdraw() public { - // template from testTerminateVestAndRecoverSlowUnlock - address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = token.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 25 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 25 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - - // InsufficientBalance - vm.expectRevert(); - controller.terminateMetavestVesting(vestingAllocation); - } - - function testAuditTerminateVestAndRecovers() public { - // template from testTerminateVestAndRecovers - address vestingAllocation = createDummyVestingAllocation(); - uint256 snapshot = token.balanceOf(authority); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: false, - complete: false, - conditionContracts: new address[](0) - }); - token.approve(address(controller), 2100e18); - - - controller.addMetavestMilestone(vestingAllocation, milestones[0]); - VestingAllocation(vestingAllocation).confirmMilestone(1); - - vm.warp(block.timestamp + 50 seconds); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).getVestedTokenAmount(); - VestingAllocation(vestingAllocation).getUnlockedTokenAmount(); - - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - // assertEq(token.balanceOf(vestingAllocation), 0); - } - - function testFailAuditRounding() public { - // template from testConfirmingMilestoneTokenOption - address vestingAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - TokenOptionAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - //exercise max available - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - - console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); - console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e6)); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e11)); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(9.99e11)); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e12)); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1.1e12)); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e13)); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1.1e12); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); - - console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); - console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); - // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - - function testAuditExcercisePrice() public { - // template from testConfirmingMilestoneTokenOption - address vestingAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - TokenOptionAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - //exercise max available - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - - console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); - console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); - - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e18); - - console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); - console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); - // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } -} \ No newline at end of file diff --git a/test/MetaVesTFactory.t.sol b/test/MetaVesTFactory.t.sol deleted file mode 100644 index 17dd3ed..0000000 --- a/test/MetaVesTFactory.t.sol +++ /dev/null @@ -1,116 +0,0 @@ -//SPDX-License-Identifier: AGPL-3.0-only - -pragma solidity ^0.8.18; - -import "forge-std/Test.sol"; -import "../src/MetaVesTFactory.sol"; -import "../src/MetaVesTController.sol"; -import "../src/VestingAllocationFactory.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkCappedMinterFactory.sol"; - -interface IERC20 { - function transfer(address recipient, uint256 amount) external returns (bool); - function balanceOf(address account) external view returns (uint256); - function approve(address spender, uint256 amount) external returns (bool); - -} - -/// @dev foundry framework testing of MetaVesTFactory.sol -/// forge t --via-ir - -/// @notice test contract for MetaVesTFactory using Foundry -contract MetaVesTFactoryTest is Test { - MetaVesTFactory internal factory; - address factoryAddr; - address dai_addr = 0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6; - VestingAllocationFactory _factory;// = new VestingAllocationFactory(); - RestrictedTokenFactory _factory2;// = new RestrictedTokenFactory(); - TokenOptionFactory _factory3;// = new TokenOptionFactory(); - - event MetaVesT_Deployment( - address newMetaVesT, - address authority, - address controller, - address dao, - address paymentToken - ); - - function setUp() public { - _factory = new VestingAllocationFactory(); - _factory2 = new RestrictedTokenFactory(); - _factory3 = new TokenOptionFactory(); - factory = new MetaVesTFactory(); - factoryAddr = address(factory); - address _authority = address(0xa); - - address _dao = address(0xB); - address _paymentToken = address(0xC); - - - ZkTokenV2 zkToken = ZkTokenV2(0x3D65a7e2960ac3820262b847b4C4dCB50F225f1a); - ZkCappedMinterFactory zkMinterFactory = ZkCappedMinterFactory(0x25BDFa33Fb8873701DDbeeD3f09edD173Ac71A1b); - - address _controller = factory.deployMetavestAndController(_authority, _dao, address(_factory), address(_factory2), address(_factory3), address(zkMinterFactory), address(zkToken)); - } - - function testDeployMetavestAndController() public { - address _authority = address(0xa); - address _dao = address(0xB); - address _paymentToken = address(0xC); - address grantee = address(0xD); - RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); - - ZkTokenV2 zkToken = ZkTokenV2(0x3D65a7e2960ac3820262b847b4C4dCB50F225f1a); - ZkCappedMinterFactory zkMinterFactory = ZkCappedMinterFactory(0x25BDFa33Fb8873701DDbeeD3f09edD173Ac71A1b); - - address _controller = factory.deployMetavestAndController(_authority, _dao, address(_factory), address(_factory2), address(_factory3), address(zkMinterFactory), address(zkToken)); - metavestController controller = metavestController(_controller); - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - //token.approve(address(controller), 1100e18); - - address vestingAllocation = controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - - assertEq(zkToken.balanceOf(vestingAllocation), 1100e18); - } - - function test_RevertIf_ControllerZeroAddress() public { - address _authority = address(0); - address _dao = address(0); - address _paymentToken = address(0); - vm.expectRevert(abi.encodeWithSelector(MetaVesTFactory.MetaVesTFactory_ZeroAddress.selector)); - factory.deployMetavestAndController(_authority, _dao, address(0), address(0), address(0), address(0), address(0)); - } - - -} diff --git a/test/MetaVesTZkCappedMinterV2.t.sol b/test/MetaVesTZkCappedMinterV2.t.sol index 61a8875..21f2acd 100644 --- a/test/MetaVesTZkCappedMinterV2.t.sol +++ b/test/MetaVesTZkCappedMinterV2.t.sol @@ -13,10 +13,13 @@ contract MetaVesTZkCappedMinterV2Test is Test { address zkTokenAdmin; IZkTokenV1 zkToken; IZkCappedMinterV2Factory zkCappedMinterFactory; + IZkCappedMinterV2 zkCappedMinter; - address authority = address(0x2); - address dao = address(0x3); - address grantee = address(0x4); + address deployer = address(0x2); + address authority = address(0x3); + address dao = address(0x4); + address alice = address(0x5); + address bob = address(0x6); VestingAllocationFactory vestingAllocationFactory; TokenOptionFactory tokenOptionFactory; @@ -24,6 +27,11 @@ contract MetaVesTZkCappedMinterV2Test is Test { metavestController controller; + // Parameters + uint256 cap = 1e6 ether; + uint48 cappedMinterStartTime = uint48(block.timestamp + 30 days); + uint48 cappedMinterExpirationTime = uint48(block.timestamp + 30 days + 365 days * 2); + function setUp() public { // zkSync Era Sepolia zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; @@ -34,6 +42,22 @@ contract MetaVesTZkCappedMinterV2Test is Test { // zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); // zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); + vm.startPrank(deployer); + + // Deploy ZK Capped Minter v2 + + zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + // TODO WIP derive address of MeteVesT controller + 0x49276208F85b2BA414B20fddE455a9a9711453aa, // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + cap, + cappedMinterStartTime, + cappedMinterExpirationTime, + uint256(keccak256("MetaLexZkSyncTest")) + )); + + // Deploy MetaVesT controller + vestingAllocationFactory = new VestingAllocationFactory(); tokenOptionFactory = new TokenOptionFactory(); restrictedTokenFactory = new RestrictedTokenFactory(); @@ -44,16 +68,17 @@ contract MetaVesTZkCappedMinterV2Test is Test { address(vestingAllocationFactory), address(tokenOptionFactory), address(restrictedTokenFactory), - address(zkCappedMinterFactory), - address(zkToken) + address(zkCappedMinter) ); + + vm.stopPrank(); } // Test by forge test --zksync --fork-url https://zksync-sepolia.g.alchemy.com/v2/ --mc MetaVesTZkCappedMinterV2Test function test_GuardianCompensationYear1_2() public { // TODO guardians to sign agreements - // Create MetaVesT for grantee + // Create MetaVesT for Alice BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ tokenContract: address(zkToken), @@ -76,7 +101,7 @@ contract MetaVesTZkCappedMinterV2Test is Test { VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( metavestController.metavestType.Vesting, - grantee, + alice, allocation, milestones, 0, @@ -89,18 +114,19 @@ contract MetaVesTZkCappedMinterV2Test is Test { // Simulate TPP approval and ZK Token admin to grant our ZkCappedMinter access bytes32 minterRole = zkToken.MINTER_ROLE(); - // TODO this is a hack. Ideally we should not create one ZkCappedMinter for every MetaVesT created. We should share it so only one ZkCappedMinter needs TPP approval - address zkCappedMinter = BaseAllocation(vestingAllocation).ZkCappedMinterAddress(); vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, zkCappedMinter); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + // Wait until capped minter start time + skip(30 days); - // Simulate grantee withdrawal + // Simulate alice withdrawal // Since there is a cliff and vesting/unlock starts immediately, the grantee should be able to withdraw the cliff amount - uint256 balanceBefore = zkToken.balanceOf(grantee); - vm.prank(grantee); + uint256 balanceBefore = zkToken.balanceOf(alice); + vm.prank(alice); vestingAllocation.withdraw(100e18); - assertEq(zkToken.balanceOf(grantee), balanceBefore + 100e18, "Grantee should be able to withdraw cliff amount"); + assertEq(zkToken.balanceOf(alice), balanceBefore + 100e18, "Grantee should be able to withdraw cliff amount"); assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it is minted on-the-fly)"); } } diff --git a/test/amendement.t.sol b/test/amendement.t.sol deleted file mode 100644 index 5ff435c..0000000 --- a/test/amendement.t.sol +++ /dev/null @@ -1,1426 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; -import "../src/metavestController.sol"; -import "../src/BaseAllocation.sol"; -import "../src/RestrictedTokenAllocation.sol"; -import "../src/interfaces/IAllocationFactory.sol"; -import "../src/VestingAllocationFactory.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkCappedMinterFactory.sol"; - -abstract contract ERC20 { - - /// @dev The total supply has overflowed. - error TotalSupplyOverflow(); - - /// @dev The allowance has overflowed. - error AllowanceOverflow(); - - /// @dev The allowance has underflowed. - error AllowanceUnderflow(); - - /// @dev Insufficient balance. - error InsufficientBalance(); - - /// @dev Insufficient allowance. - error InsufficientAllowance(); - - /// @dev The permit is invalid. - error InvalidPermit(); - - /// @dev The permit has expired. - error PermitExpired(); - - /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 amount); - - /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. - uint256 private constant _TRANSFER_EVENT_SIGNATURE = - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; - - /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. - uint256 private constant _APPROVAL_EVENT_SIGNATURE = - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; - - /// @dev The storage slot for the total supply. - uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; - - /// @dev The balance slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _BALANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let balanceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; - - /// @dev The allowance slot of (`owner`, `spender`) is given by: - /// ``` - /// mstore(0x20, spender) - /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let allowanceSlot := keccak256(0x0c, 0x34) - /// ``` - uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; - - /// @dev The nonce slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _NONCES_SLOT_SEED) - /// mstore(0x00, owner) - /// let nonceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _NONCES_SLOT_SEED = 0x38377508; - - - /// @dev Returns the name of the token. - function name() public view virtual returns (string memory); - - /// @dev Returns the symbol of the token. - function symbol() public view virtual returns (string memory); - - /// @dev Returns the decimals places of the token. - function decimals() public view virtual returns (uint8) { - return 18; - } - - - /// @dev Returns the amount of tokens in existence. - function totalSupply() public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - result := sload(_TOTAL_SUPPLY_SLOT) - } - } - - /// @dev Returns the amount of tokens owned by `owner`. - function balanceOf(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. - function allowance(address owner, address spender) - public - view - virtual - returns (uint256 result) - { - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x34)) - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - /// - /// Emits a {Approval} event. - function approve(address spender, uint256 amount) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically increases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Add to the allowance. - let allowanceAfter := add(allowanceBefore, difference) - // Revert upon overflow. - if lt(allowanceAfter, allowanceBefore) { - mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated allowance. - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically decreases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Revert if will underflow. - if lt(allowanceBefore, difference) { - mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - let allowanceAfter := sub(allowanceBefore, difference) - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Transfer `amount` tokens from the caller to `to`. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(msg.sender, to, amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, caller()) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) - } - _afterTokenTransfer(msg.sender, to, amount); - return true; - } - - /// @dev Transfers `amount` tokens from `from` to `to`. - /// - /// Note: Does not update the allowance if it is the maximum uint256 value. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the allowance slot and load its value. - mstore(0x20, caller()) - mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - return true; - } - - /// @dev Returns the current nonce for `owner`. - /// This value is used to compute the signature for EIP-2612 permit. - function nonces(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, - /// authorized by a signed approval by `owner`. - /// - /// Emits a {Approval} event. - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - bytes32 domainSeparator = DOMAIN_SEPARATOR(); - /// @solidity memory-safe-assembly - assembly { - // Grab the free memory pointer. - let m := mload(0x40) - // Revert if the block timestamp greater than `deadline`. - if gt(timestamp(), deadline) { - mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. - revert(0x1c, 0x04) - } - // Clean the upper 96 bits. - owner := shr(96, shl(96, owner)) - spender := shr(96, shl(96, spender)) - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - let nonceSlot := keccak256(0x0c, 0x20) - let nonceValue := sload(nonceSlot) - // Increment and store the updated nonce. - sstore(nonceSlot, add(nonceValue, 1)) - // Prepare the inner hash. - // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. - // forgefmt: disable-next-item - mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) - mstore(add(m, 0x20), owner) - mstore(add(m, 0x40), spender) - mstore(add(m, 0x60), value) - mstore(add(m, 0x80), nonceValue) - mstore(add(m, 0xa0), deadline) - // Prepare the outer hash. - mstore(0, 0x1901) - mstore(0x20, domainSeparator) - mstore(0x40, keccak256(m, 0xc0)) - // Prepare the ecrecover calldata. - mstore(0, keccak256(0x1e, 0x42)) - mstore(0x20, and(0xff, v)) - mstore(0x40, r) - mstore(0x60, s) - pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) - // If the ecrecover fails, the returndatasize will be 0x00, - // `owner` will be be checked if it equals the hash at 0x00, - // which evaluates to false (i.e. 0), and we will revert. - // If the ecrecover succeeds, the returndatasize will be 0x20, - // `owner` will be compared against the returned address at 0x20. - if iszero(eq(mload(returndatasize()), owner)) { - mstore(0x00, 0xddafbaef) // `InvalidPermit()`. - revert(0x1c, 0x04) - } - // Compute the allowance slot and store the value. - // The `owner` is already at slot 0x20. - mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) - sstore(keccak256(0x2c, 0x34), value) - // Emit the {Approval} event. - log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) - mstore(0x40, m) // Restore the free memory pointer. - mstore(0x60, 0) // Restore the zero pointer. - } - } - - /// @dev Returns the EIP-2612 domains separator. - function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) // Grab the free memory pointer. - } - // We simply calculate it on-the-fly to allow for cases where the `name` may change. - bytes32 nameHash = keccak256(bytes(name())); - /// @solidity memory-safe-assembly - assembly { - let m := result - // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - // forgefmt: disable-next-item - mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) - mstore(add(m, 0x20), nameHash) - // `keccak256("1")`. - // forgefmt: disable-next-item - mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) - mstore(add(m, 0x60), chainid()) - mstore(add(m, 0x80), address()) - result := keccak256(m, 0xa0) - } - } - - /// @dev Mints `amount` tokens to `to`, increasing the total supply. - /// - /// Emits a {Transfer} event. - function _mint(address to, uint256 amount) internal virtual { - _beforeTokenTransfer(address(0), to, amount); - /// @solidity memory-safe-assembly - assembly { - let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) - let totalSupplyAfter := add(totalSupplyBefore, amount) - // Revert if the total supply overflows. - if lt(totalSupplyAfter, totalSupplyBefore) { - mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) - } - _afterTokenTransfer(address(0), to, amount); - } - - /// @dev Burns `amount` tokens from `from`, reducing the total supply. - /// - /// Emits a {Transfer} event. - function _burn(address from, uint256 amount) internal virtual { - _beforeTokenTransfer(from, address(0), amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, from) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Subtract and store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) - // Emit the {Transfer} event. - mstore(0x00, amount) - log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) - } - _afterTokenTransfer(from, address(0), amount); - } - - /// @dev Moves `amount` of tokens from `from` to `to`. - function _transfer(address from, address to, uint256 amount) internal virtual { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - } - - /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. - /// - /// Emits a {Approval} event. - function _approve(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - let owner_ := shl(96, owner) - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) - } - } - - - /// @dev Hook that is called before any transfer of tokens. - /// This includes minting and burning. - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - /// @dev Hook that is called after any transfer of tokens. - /// This includes minting and burning. - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} - -contract MockERC20 is ERC20 { - constructor(string memory name, string memory symbol) ERC20() {} - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - function name() public view override returns (string memory) { - return "Test Token"; - } - function symbol() public view override returns (string memory) { - return "TT"; - } -} - -contract MetaVestControllerTest is Test { - metavestController public controller; - MockERC20 public token; - MockERC20 public paymentToken; - address public authority; - address public dao; - address public vestingFactory; - address public tokenOptionFactory; - address public restrictedTokenFactory; - address public grantee; - address public mockAllocation; - - function setUp() public { - authority = address(this); - dao = address(2); - VestingAllocationFactory factory = new VestingAllocationFactory(); - TokenOptionFactory tokenFactory = new TokenOptionFactory(); - RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); - grantee = address(6); - - token = new MockERC20("Test Token", "TT"); - paymentToken = new MockERC20("Payment Token", "PT"); - - ZkTokenV2 zkToken = new ZkTokenV2(); - ZkCappedMinterFactory zkMinterFactory = new ZkCappedMinterFactory(0x0); - - controller = new metavestController( - authority, - dao, - address(factory), - address(tokenFactory), - address(restrictedTokenFactory), - address(zkMinterFactory), - address(zkToken) - ); - - - token.mint(authority, 1000000e58); - paymentToken.mint(authority, 1000000e58); - - paymentToken.transfer(address(grantee), 1000e25); - mockAllocation = createDummyVestingAllocation(); - - vm.prank(authority); - controller.createSet("testSet"); - } - - - function testProposeMetavestAmendment() public { - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.proposeMetavestAmendment(address(mockAllocation), msgSig, callData); - - (bool isPending, bytes32 dataHash, bool inFavor) = controller.functionToGranteeToAmendmentPending(msgSig, address(mockAllocation)); - - assertTrue(isPending); - assertEq(dataHash, keccak256(callData)); - assertFalse(inFavor); - } - -// function testFailProposeMajorityMetavestAmendment() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// address mockAllocation3 = createDummyVestingAllocation(); -// address mockAllocation4 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// controller.addMetaVestToSet("testSet", mockAllocation4); -// vm.warp(block.timestamp + 1 days); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(grantee); -// //log the current withdrawable -// console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); -// -// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true); -// } - - function testQuickProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - address mockAllocation4 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.warp(block.timestamp + 15 seconds); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.startPrank(grantee); - //log the current withdrawable - console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); - - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - vm.stopPrank(); - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - } - - - function testMajorityPowerMetavestAmendment() public { - address mockAllocation2 = createDummyTokenOptionAllocation(); - address mockAllocation3 = createDummyTokenOptionAllocation(); - address mockAllocation4 = createDummyTokenOptionAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.warp(block.timestamp + 1 days); - vm.startPrank(grantee); - ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); - TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); - vm.stopPrank(); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - } - -// function testFailMajorityPowerMetavestAmendment() public { -// address mockAllocation2 = createDummyTokenOptionAllocation(); -// address mockAllocation3 = createDummyTokenOptionAllocation(); -// address mockAllocation4 = createDummyTokenOptionAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// controller.addMetaVestToSet("testSet", mockAllocation4); -// vm.warp(block.timestamp + 1 days); -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); -// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); -// vm.stopPrank(); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true); -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true); -// } - - function testProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation3, true); - } - - function testProposeMajorityMetavestAmendmentReAdd() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation3, true); - - vm.prank(authority); - controller.removeMetaVestFromSet("testSet", mockAllocation3); - // vm.prank(authority); - // controller.updateMetavestTransferability(mockAllocation3, true); - vm.warp(block.timestamp + 90 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation3); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation3, true); - } - -// function testFailNoPassProposeMajorityMetavestAmendment() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// address mockAllocation3 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// vm.warp(block.timestamp + 1 days); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation3, true); -// } - - function testVoteOnMetavestAmendment() public { - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - - } - -// function testFailVoteOnMetavestAmendmentTwice() public { -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", address(mockAllocation)); -// -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.startPrank(grantee); -// controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); -// controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); -// vm.stopPrank(); -// } - - function testSetManagement() public { - vm.startPrank(authority); - - // Test creating a new set - controller.createSet("newSet"); - - // Test adding a MetaVest to a set - controller.addMetaVestToSet("newSet", address(mockAllocation)); - - - // Test removing a MetaVest from a set - controller.removeMetaVestFromSet("newSet", address(mockAllocation)); - - - // Test removing a set - controller.removeSet("newSet"); - - - vm.stopPrank(); - } - -// function testFailCreateDuplicateSet() public { -// vm.startPrank(authority); -// controller.createSet("duplicateSet"); -// controller.createSet("duplicateSet"); -// vm.stopPrank(); -// } -// -// function testFailNonAuthorityCreateSet() public { -// vm.prank(grantee); -// controller.createSet("unauthorizedSet"); -// } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocation() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - function createDummyTokenOptionAllocation() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - - return controller.createMetavest( - metavestController.metavestType.TokenOption, - grantee, - allocation, - milestones, - 1e18, - address(paymentToken), - 365 days, - 0 - ); - } - - function createDummyRestrictedTokenAward() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - - return controller.createMetavest( - metavestController.metavestType.RestrictedTokenAward, - grantee, - allocation, - milestones, - 1e18, - address(paymentToken), - 365 days, - 0 - - ); - } - - //write a test for every consentcheck function in metavest controller - function testConsentCheck() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - } - -// function testFailConsentCheck() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(allocation, true); -// } -// -// function testFailConsentCheckNoProposal() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); -// } -// -// function testFailConsentCheckNoVote() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(allocation, true); -// } -// -// function testFailConsentCheckNoUpdate() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); -// } -// -// function testFailConsentCheckNoVoteUpdate() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); -// } - - function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { - address allocation1 = createDummyTokenOptionAllocation(); - address allocation2 = createDummyTokenOptionAllocation(); - address allocation3 = createDummyTokenOptionAllocation(); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", allocation1); - controller.addMetaVestToSet("testSet", allocation2); - controller.addMetaVestToSet("testSet", allocation3); - assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); - vm.warp(block.timestamp + 25 seconds); - - - vm.startPrank(grantee); - ERC20(paymentToken).approve(address(allocation1), 2000e18); - ERC20(paymentToken).approve(address(allocation2), 2000e18); - - TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); - - TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); - vm.stopPrank(); - bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); - - // Check that the exercise price was updated - assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); - } - -// function testFailCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { -// address allocation1 = createDummyTokenOptionAllocation(); -// address allocation2 = createDummyTokenOptionAllocation(); -// address allocation3 = createDummyTokenOptionAllocation(); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", allocation1); -// controller.addMetaVestToSet("testSet", allocation2); -// controller.addMetaVestToSet("testSet", allocation3); -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); -// vm.warp(block.timestamp + 25 seconds); -// -// -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(allocation1), 2000e18); -// ERC20(paymentToken).approve(address(allocation2), 2000e18); -// -// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); -// -// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); -// vm.stopPrank(); -// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); -// -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// //vm.prank(grantee); -// // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); -// -// // vm.prank(grantee); -// // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); -// -// // Check that the exercise price was updated -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); -// } -// -// function testFailconsentToNoPendingAmendment() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// } - - function testEveryUpdateAmendmentFunction() public { - address allocation = createDummyTokenOptionAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - - msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 2e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation, 2e18); - - msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.removeMetavestMilestone(allocation, 0); - - msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(allocation, 20e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(allocation, 20e18); - - msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); - callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); - } - - function testEveryUpdateAmendmentFunctionRestricted() public { - address allocation = createDummyRestrictedTokenAward(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - - msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 2e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation, 2e18); - - msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.removeMetavestMilestone(allocation, 0); - - msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(allocation, 20e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(allocation, 20e18); - - msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); - callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); - } - - function testEveryUpdateAmendmentFunctionVesting() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - - msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.removeMetavestMilestone(allocation, 0); - - msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(allocation, 20e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(allocation, 20e18); - - msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); - callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); - } - -// function testFailEveryUpdateAmendmentFunctionVesting() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(allocation, true); -// -// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 0); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.removeMetavestMilestone(allocation, 0); -// -// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestUnlockRate(allocation, 20e18); -// -// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestVestingRate(allocation, 20e18); -// -// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); -// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(authority); -// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); -// } -} \ No newline at end of file diff --git a/test/controller.t.sol b/test/controller.t.sol deleted file mode 100644 index 9188ac6..0000000 --- a/test/controller.t.sol +++ /dev/null @@ -1,2442 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; -import "../src/metavestController.sol"; -import "../src/VestingAllocation.sol"; -import "../src/TokenOptionAllocation.sol"; -import "../src/RestrictedTokenAllocation.sol"; -import "../src/interfaces/IAllocationFactory.sol"; -import "../src/VestingAllocationFactory.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "./mocks/MockCondition.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkCappedMinterFactory.sol"; -import {console} from "forge-std/console.sol"; - -abstract contract ERC20 { - - - /// @dev The total supply has overflowed. - error TotalSupplyOverflow(); - - /// @dev The allowance has overflowed. - error AllowanceOverflow(); - - /// @dev The allowance has underflowed. - error AllowanceUnderflow(); - - /// @dev Insufficient balance. - error InsufficientBalance(); - - /// @dev Insufficient allowance. - error InsufficientAllowance(); - - /// @dev The permit is invalid. - error InvalidPermit(); - - /// @dev The permit has expired. - error PermitExpired(); - - - /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 amount); - - /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. - uint256 private constant _TRANSFER_EVENT_SIGNATURE = - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; - - /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. - uint256 private constant _APPROVAL_EVENT_SIGNATURE = - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; - - - /// @dev The storage slot for the total supply. - uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; - - /// @dev The balance slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _BALANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let balanceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; - - /// @dev The allowance slot of (`owner`, `spender`) is given by: - /// ``` - /// mstore(0x20, spender) - /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let allowanceSlot := keccak256(0x0c, 0x34) - /// ``` - uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; - - /// @dev The nonce slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _NONCES_SLOT_SEED) - /// mstore(0x00, owner) - /// let nonceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _NONCES_SLOT_SEED = 0x38377508; - - - /// @dev Returns the name of the token. - function name() public view virtual returns (string memory); - - /// @dev Returns the symbol of the token. - function symbol() public view virtual returns (string memory); - - /// @dev Returns the decimals places of the token. - function decimals() public view virtual returns (uint8) { - return 18; - } - - - /// @dev Returns the amount of tokens in existence. - function totalSupply() public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - result := sload(_TOTAL_SUPPLY_SLOT) - } - } - - /// @dev Returns the amount of tokens owned by `owner`. - function balanceOf(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. - function allowance(address owner, address spender) - public - view - virtual - returns (uint256 result) - { - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x34)) - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - /// - /// Emits a {Approval} event. - function approve(address spender, uint256 amount) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically increases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Add to the allowance. - let allowanceAfter := add(allowanceBefore, difference) - // Revert upon overflow. - if lt(allowanceAfter, allowanceBefore) { - mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated allowance. - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically decreases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Revert if will underflow. - if lt(allowanceBefore, difference) { - mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - let allowanceAfter := sub(allowanceBefore, difference) - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Transfer `amount` tokens from the caller to `to`. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(msg.sender, to, amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, caller()) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) - } - _afterTokenTransfer(msg.sender, to, amount); - return true; - } - - /// @dev Transfers `amount` tokens from `from` to `to`. - /// - /// Note: Does not update the allowance if it is the maximum uint256 value. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the allowance slot and load its value. - mstore(0x20, caller()) - mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - return true; - } - - - /// @dev Returns the current nonce for `owner`. - /// This value is used to compute the signature for EIP-2612 permit. - function nonces(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, - /// authorized by a signed approval by `owner`. - /// - /// Emits a {Approval} event. - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - bytes32 domainSeparator = DOMAIN_SEPARATOR(); - /// @solidity memory-safe-assembly - assembly { - // Grab the free memory pointer. - let m := mload(0x40) - // Revert if the block timestamp greater than `deadline`. - if gt(timestamp(), deadline) { - mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. - revert(0x1c, 0x04) - } - // Clean the upper 96 bits. - owner := shr(96, shl(96, owner)) - spender := shr(96, shl(96, spender)) - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - let nonceSlot := keccak256(0x0c, 0x20) - let nonceValue := sload(nonceSlot) - // Increment and store the updated nonce. - sstore(nonceSlot, add(nonceValue, 1)) - // Prepare the inner hash. - // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. - // forgefmt: disable-next-item - mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) - mstore(add(m, 0x20), owner) - mstore(add(m, 0x40), spender) - mstore(add(m, 0x60), value) - mstore(add(m, 0x80), nonceValue) - mstore(add(m, 0xa0), deadline) - // Prepare the outer hash. - mstore(0, 0x1901) - mstore(0x20, domainSeparator) - mstore(0x40, keccak256(m, 0xc0)) - // Prepare the ecrecover calldata. - mstore(0, keccak256(0x1e, 0x42)) - mstore(0x20, and(0xff, v)) - mstore(0x40, r) - mstore(0x60, s) - pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) - // If the ecrecover fails, the returndatasize will be 0x00, - // `owner` will be be checked if it equals the hash at 0x00, - // which evaluates to false (i.e. 0), and we will revert. - // If the ecrecover succeeds, the returndatasize will be 0x20, - // `owner` will be compared against the returned address at 0x20. - if iszero(eq(mload(returndatasize()), owner)) { - mstore(0x00, 0xddafbaef) // `InvalidPermit()`. - revert(0x1c, 0x04) - } - // Compute the allowance slot and store the value. - // The `owner` is already at slot 0x20. - mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) - sstore(keccak256(0x2c, 0x34), value) - // Emit the {Approval} event. - log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) - mstore(0x40, m) // Restore the free memory pointer. - mstore(0x60, 0) // Restore the zero pointer. - } - } - - /// @dev Returns the EIP-2612 domains separator. - function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) // Grab the free memory pointer. - } - // We simply calculate it on-the-fly to allow for cases where the `name` may change. - bytes32 nameHash = keccak256(bytes(name())); - /// @solidity memory-safe-assembly - assembly { - let m := result - // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - // forgefmt: disable-next-item - mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) - mstore(add(m, 0x20), nameHash) - // `keccak256("1")`. - // forgefmt: disable-next-item - mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) - mstore(add(m, 0x60), chainid()) - mstore(add(m, 0x80), address()) - result := keccak256(m, 0xa0) - } - } - - - /// @dev Mints `amount` tokens to `to`, increasing the total supply. - /// - /// Emits a {Transfer} event. - function _mint(address to, uint256 amount) internal virtual { - _beforeTokenTransfer(address(0), to, amount); - /// @solidity memory-safe-assembly - assembly { - let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) - let totalSupplyAfter := add(totalSupplyBefore, amount) - // Revert if the total supply overflows. - if lt(totalSupplyAfter, totalSupplyBefore) { - mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) - } - _afterTokenTransfer(address(0), to, amount); - } - - /// @dev Burns `amount` tokens from `from`, reducing the total supply. - /// - /// Emits a {Transfer} event. - function _burn(address from, uint256 amount) internal virtual { - _beforeTokenTransfer(from, address(0), amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, from) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Subtract and store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) - // Emit the {Transfer} event. - mstore(0x00, amount) - log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) - } - _afterTokenTransfer(from, address(0), amount); - } - - /// @dev Moves `amount` of tokens from `from` to `to`. - function _transfer(address from, address to, uint256 amount) internal virtual { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - } - - - /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. - /// - /// Emits a {Approval} event. - function _approve(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - let owner_ := shl(96, owner) - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) - } - } - - - /// @dev Hook that is called before any transfer of tokens. - /// This includes minting and burning. - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - /// @dev Hook that is called after any transfer of tokens. - /// This includes minting and burning. - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} - -abstract contract ERC20Stable { - - /// @dev The total supply has overflowed. - error TotalSupplyOverflow(); - - /// @dev The allowance has overflowed. - error AllowanceOverflow(); - - /// @dev The allowance has underflowed. - error AllowanceUnderflow(); - - /// @dev Insufficient balance. - error InsufficientBalance(); - - /// @dev Insufficient allowance. - error InsufficientAllowance(); - - /// @dev The permit is invalid. - error InvalidPermit(); - - /// @dev The permit has expired. - error PermitExpired(); - - - /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 amount); - - /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. - uint256 private constant _TRANSFER_EVENT_SIGNATURE = - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; - - /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. - uint256 private constant _APPROVAL_EVENT_SIGNATURE = - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; - - /// @dev The storage slot for the total supply. - uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; - - /// @dev The balance slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _BALANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let balanceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; - - /// @dev The allowance slot of (`owner`, `spender`) is given by: - /// ``` - /// mstore(0x20, spender) - /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let allowanceSlot := keccak256(0x0c, 0x34) - /// ``` - uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; - - /// @dev The nonce slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _NONCES_SLOT_SEED) - /// mstore(0x00, owner) - /// let nonceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _NONCES_SLOT_SEED = 0x38377508; - - /// @dev Returns the name of the token. - function name() public view virtual returns (string memory); - - /// @dev Returns the symbol of the token. - function symbol() public view virtual returns (string memory); - - /// @dev Returns the decimals places of the token. - function decimals() public view virtual returns (uint8) { - return 6; - } - - /// @dev Returns the amount of tokens in existence. - function totalSupply() public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - result := sload(_TOTAL_SUPPLY_SLOT) - } - } - - /// @dev Returns the amount of tokens owned by `owner`. - function balanceOf(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. - function allowance(address owner, address spender) - public - view - virtual - returns (uint256 result) - { - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x34)) - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - /// - /// Emits a {Approval} event. - function approve(address spender, uint256 amount) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically increases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Add to the allowance. - let allowanceAfter := add(allowanceBefore, difference) - // Revert upon overflow. - if lt(allowanceAfter, allowanceBefore) { - mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated allowance. - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically decreases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Revert if will underflow. - if lt(allowanceBefore, difference) { - mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - let allowanceAfter := sub(allowanceBefore, difference) - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Transfer `amount` tokens from the caller to `to`. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(msg.sender, to, amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, caller()) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) - } - _afterTokenTransfer(msg.sender, to, amount); - return true; - } - - /// @dev Transfers `amount` tokens from `from` to `to`. - /// - /// Note: Does not update the allowance if it is the maximum uint256 value. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the allowance slot and load its value. - mstore(0x20, caller()) - mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - return true; - } - - - /// @dev Returns the current nonce for `owner`. - /// This value is used to compute the signature for EIP-2612 permit. - function nonces(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, - /// authorized by a signed approval by `owner`. - /// - /// Emits a {Approval} event. - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - bytes32 domainSeparator = DOMAIN_SEPARATOR(); - /// @solidity memory-safe-assembly - assembly { - // Grab the free memory pointer. - let m := mload(0x40) - // Revert if the block timestamp greater than `deadline`. - if gt(timestamp(), deadline) { - mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. - revert(0x1c, 0x04) - } - // Clean the upper 96 bits. - owner := shr(96, shl(96, owner)) - spender := shr(96, shl(96, spender)) - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - let nonceSlot := keccak256(0x0c, 0x20) - let nonceValue := sload(nonceSlot) - // Increment and store the updated nonce. - sstore(nonceSlot, add(nonceValue, 1)) - // Prepare the inner hash. - // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. - // forgefmt: disable-next-item - mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) - mstore(add(m, 0x20), owner) - mstore(add(m, 0x40), spender) - mstore(add(m, 0x60), value) - mstore(add(m, 0x80), nonceValue) - mstore(add(m, 0xa0), deadline) - // Prepare the outer hash. - mstore(0, 0x1901) - mstore(0x20, domainSeparator) - mstore(0x40, keccak256(m, 0xc0)) - // Prepare the ecrecover calldata. - mstore(0, keccak256(0x1e, 0x42)) - mstore(0x20, and(0xff, v)) - mstore(0x40, r) - mstore(0x60, s) - pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) - // If the ecrecover fails, the returndatasize will be 0x00, - // `owner` will be be checked if it equals the hash at 0x00, - // which evaluates to false (i.e. 0), and we will revert. - // If the ecrecover succeeds, the returndatasize will be 0x20, - // `owner` will be compared against the returned address at 0x20. - if iszero(eq(mload(returndatasize()), owner)) { - mstore(0x00, 0xddafbaef) // `InvalidPermit()`. - revert(0x1c, 0x04) - } - // Compute the allowance slot and store the value. - // The `owner` is already at slot 0x20. - mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) - sstore(keccak256(0x2c, 0x34), value) - // Emit the {Approval} event. - log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) - mstore(0x40, m) // Restore the free memory pointer. - mstore(0x60, 0) // Restore the zero pointer. - } - } - - /// @dev Returns the EIP-2612 domains separator. - function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) // Grab the free memory pointer. - } - // We simply calculate it on-the-fly to allow for cases where the `name` may change. - bytes32 nameHash = keccak256(bytes(name())); - /// @solidity memory-safe-assembly - assembly { - let m := result - // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - // forgefmt: disable-next-item - mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) - mstore(add(m, 0x20), nameHash) - // `keccak256("1")`. - // forgefmt: disable-next-item - mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) - mstore(add(m, 0x60), chainid()) - mstore(add(m, 0x80), address()) - result := keccak256(m, 0xa0) - } - } - - /// @dev Mints `amount` tokens to `to`, increasing the total supply. - /// - /// Emits a {Transfer} event. - function _mint(address to, uint256 amount) internal virtual { - _beforeTokenTransfer(address(0), to, amount); - /// @solidity memory-safe-assembly - assembly { - let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) - let totalSupplyAfter := add(totalSupplyBefore, amount) - // Revert if the total supply overflows. - if lt(totalSupplyAfter, totalSupplyBefore) { - mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) - } - _afterTokenTransfer(address(0), to, amount); - } - - /// @dev Burns `amount` tokens from `from`, reducing the total supply. - /// - /// Emits a {Transfer} event. - function _burn(address from, uint256 amount) internal virtual { - _beforeTokenTransfer(from, address(0), amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, from) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Subtract and store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) - // Emit the {Transfer} event. - mstore(0x00, amount) - log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) - } - _afterTokenTransfer(from, address(0), amount); - } - - - /// @dev Moves `amount` of tokens from `from` to `to`. - function _transfer(address from, address to, uint256 amount) internal virtual { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - } - - - /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. - /// - /// Emits a {Approval} event. - function _approve(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - let owner_ := shl(96, owner) - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) - } - } - - /// @dev Hook that is called before any transfer of tokens. - /// This includes minting and burning. - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - /// @dev Hook that is called after any transfer of tokens. - /// This includes minting and burning. - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} - -contract MockERC20 is ERC20 { - constructor(string memory name, string memory symbol) ERC20() {} - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - function name() public view override returns (string memory) { - return "Test Token"; - } - function symbol() public view override returns (string memory) { - return "TT"; - } -} - -contract MockERC20Stable is ERC20Stable { - constructor(string memory name, string memory symbol) ERC20Stable() {} - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - function name() public view override returns (string memory) { - return "Test Token"; - } - function symbol() public view override returns (string memory) { - return "TT"; - } -} - -contract MetaVestControllerTest is Test { - metavestController public controller; - MockERC20 public token; - MockERC20Stable public paymentToken; - - address public authority; - address public dao; - address public grantee; - address public transferee; - - function setUp() public { - authority = address(this); - dao = address(0x2); - grantee = address(0x3); - transferee = address(0x4); - - token = new MockERC20("Test Token", "TT"); - paymentToken = new MockERC20Stable("Payment Token", "PT"); - - VestingAllocationFactory factory = new VestingAllocationFactory(); - TokenOptionFactory tokenFactory = new TokenOptionFactory(); - RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); - - ZkTokenV2 zkToken = new ZkTokenV2(); - ZkCappedMinterFactory zkMinterFactory = new ZkCappedMinterFactory(0x073749a0f8ed0d49b1acfd4e0efdc59328c83d0c2eed9ee099a3979f0c332ff8); - - - controller = new metavestController( - authority, - dao, - address(factory), - address(tokenFactory), - address(restrictedTokenFactory), - address(zkMinterFactory), - address(zkToken) - ); - - token.mint(authority, 1000000e58); - paymentToken.mint(authority, 1000000e58); - paymentToken.transfer(grantee, 1e25); - - vm.prank(authority); - controller.createSet("testSet"); - } - - function testCreateVestingAllocation() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - - address vestingAllocation = controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - - assertEq(token.balanceOf(vestingAllocation), 1100e18); - // assertEq(controller.vestingAllocations(grantee, 0), vestingAllocation); - } - - function testCreateTokenOptionAllocation() public { - bytes32 bytecodeHash = keccak256(type(ZkCappedMinter).creationCode); - console.logBytes32(bytecodeHash); - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - - address tokenOptionAllocation = controller.createMetavest( - metavestController.metavestType.TokenOption, - grantee, - allocation, - milestones, - 1e18, - address(paymentToken), - 365 days, - 0 - ); - - assertEq(token.balanceOf(tokenOptionAllocation), 1100e18); - //assertEq(controller.tokenOptionAllocations(grantee, 0), tokenOptionAllocation); - } - - function testCreateRestrictedTokenAward() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - - address restrictedTokenAward = controller.createMetavest( - metavestController.metavestType.RestrictedTokenAward, - grantee, - allocation, - milestones, - 1e18, - address(paymentToken), - 365 days, - 0 - - ); - - assertEq(token.balanceOf(restrictedTokenAward), 1100e18); - //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); - } - - function testUpdateTransferability() public { - uint256 startTimestamp = block.timestamp; - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - //compute msg.data for updateMetavestTransferability(vestingAllocation, true) - bytes4 selector = controller.updateMetavestTransferability.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, true); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, true); - - controller.updateMetavestTransferability(vestingAllocation, true); - vm.prank(grantee); - RestrictedTokenAward(vestingAllocation).transferRights(transferee); - vm.prank(transferee); - RestrictedTokenAward(vestingAllocation).confirmTransfer(); - uint256 newTimestamp = startTimestamp + 100; // 101 - vm.warp(newTimestamp); - skip(10); - vm.prank(transferee); - uint256 balance = RestrictedTokenAward(vestingAllocation).getAmountWithdrawable(); - - - //warp ahead 100 blocks - - vm.prank(transferee); - RestrictedTokenAward(vestingAllocation).withdraw(balance); - - // assertTrue(BaseAllocation(vestingAllocation).transferable()); - } - - function testGetGovPower() public { - address vestingAllocation = createDummyVestingAllocation(); - BaseAllocation(vestingAllocation).getGoverningPower(); - } - - function testProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - } - - -// function testFailReProposeMajorityMetavestAmendment() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// vm.warp(block.timestamp + 1 days); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// vm.warp(block.timestamp + 30 days); -// /* -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true);*/ -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// } - - function testReProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - vm.warp(block.timestamp + 30 days); - - vm.prank(authority); - controller.cancelExpiredMajorityMetavestAmendment("testSet", msgSig); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - } - -// function testFailRemoveNonExistantMetaVestFromSet() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// vm.startPrank(authority); -// // controller.createSet("testSet"); -// controller.removeMetaVestFromSet("testSet", mockAllocation2); -// } - - - function testUpdateExercisePrice() public { - address tokenOptionAllocation = createDummyTokenOptionAllocation(); - - //compute msg.data for updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18) - bytes4 selector = controller.updateExerciseOrRepurchasePrice.selector; - bytes memory msgData = abi.encodeWithSelector(selector, tokenOptionAllocation, 2e18); - - controller.proposeMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, true); - - controller.updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18); - - assertEq(TokenOptionAllocation(tokenOptionAllocation).exercisePrice(), 2e18); - } - - function testRemoveMilestone() public { - address vestingAllocation = createDummyVestingAllocation(); - //create array of addresses and include vestingAllocation address - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - vm.prank(grantee); - //consent to amendment for the removemetavestmilestone method sig function consentToMetavestAmendment(address _metavest, bytes4 _msgSig, bool _inFavor) external { - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - controller.removeMetavestMilestone(vestingAllocation, 0); - - //BaseAllocation.Milestone memory milestone = BaseAllocation(vestingAllocation).milestones(0); - //assertEq(milestone.milestoneAward, 0); - } - - function testAddMilestone() public { - address vestingAllocation = createDummyVestingAllocation(); - - BaseAllocation.Milestone memory newMilestone = BaseAllocation.Milestone({ - milestoneAward: 50e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 50e18); - controller.addMetavestMilestone(vestingAllocation, newMilestone); - - // BaseAllocation.Milestone memory addedMilestone = BaseAllocation(vestingAllocation).milestones[0]; - // assertEq(addedMilestone.milestoneAward, 50e18); - } - - function testUpdateUnlockRate() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - - controller.updateMetavestUnlockRate(vestingAllocation, 20e18); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 20e18); - } - - function testUpdateUnlockRateZeroEmergency() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - - controller.updateMetavestUnlockRate(vestingAllocation, 0); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 0); - - controller.terminateMetavestVesting(vestingAllocation); - - controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 1e20); - } - -// function testFailUpdateUnlockRateZeroEmergency() public { -// address vestingAllocation = createDummyVestingAllocation(); -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = controller.updateMetavestUnlockRate.selector; -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); -// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); -// -// controller.terminateMetavestVesting(vestingAllocation); -// -// controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); -// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); -// assertEq(updatedAllocation.unlockRate, 1e20); -// } - -// function testFailUpdateUnlockRateZeroEmergencyTerminated() public { -// address vestingAllocation = createDummyVestingAllocation(); -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = controller.updateMetavestUnlockRate.selector; -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); -// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); -// -// controller.updateMetavestUnlockRate(vestingAllocation, 0); -// -// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); -// assertEq(updatedAllocation.unlockRate, 0); -// -// controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); -// updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); -// assertEq(updatedAllocation.unlockRate, 1e20); -// } - - function testUpdateVestingRate() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestVestingRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, true); - - controller.updateMetavestVestingRate(vestingAllocation, 20e18); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.vestingRate, 20e18); - } - - function testUpdateStopTimes() public { - - address vestingAllocation = createDummyRestrictedTokenAward(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, uint48(block.timestamp + 500 days)); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, true); - uint48 newShortStopTime = uint48(block.timestamp + 500 days); - - controller.updateMetavestStopTimes(vestingAllocation, newShortStopTime); - } - - function testTerminateVesting() public { - address vestingAllocation = createDummyVestingAllocation(); - - controller.terminateMetavestVesting(vestingAllocation); - - assertTrue(BaseAllocation(vestingAllocation).terminated()); - } - - function testRepurchaseTokens() public { - uint256 startingBalance = paymentToken.balanceOf(grantee); - address restrictedTokenAward = createDummyRestrictedTokenAward(); - uint256 repurchaseAmount = 5e18; - uint256 snapshot = token.balanceOf(authority); - uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); - controller.terminateMetavestVesting(restrictedTokenAward); - paymentToken.approve(address(restrictedTokenAward), payment); - vm.warp(block.timestamp + 20 days); - vm.prank(authority); - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); - - assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); - - vm.prank(grantee); - RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); - assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); - } - - function testRepurchaseTokensFuture() public { - uint256 startingBalance = paymentToken.balanceOf(grantee); - address restrictedTokenAward = createDummyRestrictedTokenAwardFuture(); - - uint256 snapshot = token.balanceOf(authority); - - controller.terminateMetavestVesting(restrictedTokenAward); - uint256 repurchaseAmount = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); - uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); - paymentToken.approve(address(restrictedTokenAward), payment); - vm.warp(block.timestamp + 20 days); - vm.prank(authority); - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); - - assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); - - vm.prank(grantee); - RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); - console.log(token.balanceOf(restrictedTokenAward)); - assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); - - } - - function testTerminateTokensFuture() public { - uint256 startingBalance = paymentToken.balanceOf(grantee); - address restrictedTokenAward = createDummyVestingAllocationLargeFuture(); - - controller.terminateMetavestVesting(restrictedTokenAward); - - console.log(token.balanceOf(restrictedTokenAward)); - } - - function testUpdateAuthority() public { - address newAuthority = address(0x4); - - controller.initiateAuthorityUpdate(newAuthority); - - vm.prank(newAuthority); - controller.acceptAuthorityRole(); - - assertEq(controller.authority(), newAuthority); - } - - function testUpdateDao() public { - address newDao = address(0x5); - - vm.prank(dao); - controller.initiateDaoUpdate(newDao); - - vm.prank(newDao); - controller.acceptDaoRole(); - - assertEq(controller.dao(), newDao); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocation() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationNoUnlock() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: false, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationSlowUnlock() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 5e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationLarge() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 0, - unlockingCliffCredit: 0, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationLargeFuture() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 0, - unlockingCliffCredit: 0, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp+2000), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp+2000) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - function createDummyTokenOptionAllocation() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 2000e18); - - return controller.createMetavest( - metavestController.metavestType.TokenOption, - grantee, - allocation, - milestones, - 5e17, - address(paymentToken), - 1 days, - 0 - ); - } - - - function createDummyRestrictedTokenAward() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.RestrictedTokenAward, - grantee, - allocation, - milestones, - 1e18, - address(paymentToken), - 1 days, - 0 - - ); - } - - function createDummyRestrictedTokenAwardFuture() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp+1000), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp+1000) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.RestrictedTokenAward, - grantee, - allocation, - milestones, - 1e18, - address(paymentToken), - 1 days, - 0 - - ); - } - - - function testGetMetaVestType() public { - address vestingAllocation = createDummyVestingAllocation(); - address tokenOptionAllocation = createDummyTokenOptionAllocation(); - address restrictedTokenAward = createDummyRestrictedTokenAward(); - - assertEq(controller.getMetaVestType(vestingAllocation), 1); - assertEq(controller.getMetaVestType(tokenOptionAllocation), 2); - assertEq(controller.getMetaVestType(restrictedTokenAward), 3); - } - - function testWithdrawFromController() public { - uint256 amount = 100e18; - token.transfer(address(controller), amount); - - uint256 initialBalance = token.balanceOf(authority); - controller.withdrawFromController(address(token)); - uint256 finalBalance = token.balanceOf(authority); - - assertEq(finalBalance - initialBalance, amount); - assertEq(token.balanceOf(address(controller)), 0); - } - -// function testFailWithdrawFromControllerNonAuthority() public { -// vm.prank(address(0x1234)); -// controller.withdrawFromController(address(token)); -// } - -// function testFailCreateMetavestWithZeroAddress() public { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(0), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); -// -// controller.createMetavest( -// metavestController.metavestType.Vesting, -// address(0), -// allocation, -// milestones, -// 0, -// address(0), -// 0, -// 0 -// -// ); -// } - -// function testFailCreateMetavestWithInsufficientApproval() public { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); -// -// // Not approving any tokens -// controller.createMetavest( -// metavestController.metavestType.Vesting, -// grantee, -// allocation, -// milestones, -// 0, -// address(0), -// 0, -// 0 -// -// ); -// } - - function testTerminateVestAndRecovers() public { - address vestingAllocation = createDummyVestingAllocation(); - uint256 snapshot = token.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(token.balanceOf(vestingAllocation), 0); - } - - function testTerminateVestAndRecoverSlowUnlock() public { - address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = token.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 25 seconds); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 25 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(token.balanceOf(vestingAllocation), 0); - } - - function testTerminateRecoverAll() public { - address vestingAllocation = createDummyVestingAllocationLarge(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(token.balanceOf(vestingAllocation), 0); - } - - function testTerminateRecoverChunksBefore() public { - address vestingAllocation = createDummyVestingAllocationLarge(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - vm.warp(block.timestamp + 25 seconds); - - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(token.balanceOf(vestingAllocation), 0); - } - - function testConfirmingMilestoneRestrictedTokenAllocation() public { - address vestingAllocation = createDummyRestrictedTokenAward(); - uint256 snapshot = token.balanceOf(authority); - RestrictedTokenAward(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - - function testConfirmingMilestoneTokenOption() public { - address vestingAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - TokenOptionAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - //exercise max available - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); - TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - - function testUnlockMilestoneNotUnlocked() public { - address vestingAllocation = createDummyVestingAllocationNoUnlock(); - uint256 snapshot = token.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - console.log(token.balanceOf(vestingAllocation)); - vm.warp(block.timestamp + 1050 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - console.log(token.balanceOf(vestingAllocation)); - vm.stopPrank(); - } - - function testTerminateTokenOptionAndRecover() public { - address tokenOptionAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - vm.prank(grantee); - ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); - vm.prank(grantee); - TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18); - controller.terminateMetavestVesting(tokenOptionAllocation); - vm.startPrank(grantee); - vm.warp(block.timestamp + 1 days + 25 seconds); - assertEq(TokenOptionAllocation(tokenOptionAllocation).getAmountExercisable(), 0); - TokenOptionAllocation(tokenOptionAllocation).withdraw(TokenOptionAllocation(tokenOptionAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(token.balanceOf(tokenOptionAllocation), 0); - vm.warp(block.timestamp + 365 days); - vm.prank(authority); - TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); - } - - function testTerminateEarlyTokenOptionAndRecover() public { - address tokenOptionAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 5 seconds); - // vm.prank(grantee); - /* ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); - vm.prank(grantee); - TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18);*/ - controller.terminateMetavestVesting(tokenOptionAllocation); - vm.warp(block.timestamp + 365 days); - vm.prank(authority); - TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); - } - - - function testTerminateRestrictedTokenAwardAndRecover() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); - uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); - vm.warp(block.timestamp + 20 days); - paymentToken.approve(address(restrictedTokenAward), payamt); - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); - - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); - assertEq(token.balanceOf(restrictedTokenAward), 0); - assertEq(paymentToken.balanceOf(restrictedTokenAward), 0); - } - - function testChangeVestingAndUnlockingRate() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - - bytes4 msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(restrictedTokenAward, 50e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 50e18); - - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - - } - - function testZeroReclaim() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - vm.warp(block.timestamp + 15 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 0); - - vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.warp(block.timestamp + 155 days); - uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); - uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); - paymentToken.approve(address(restrictedTokenAward), payamt); - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); - vm.stopPrank(); - vm.prank(grantee); - RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); - console.log(token.balanceOf(restrictedTokenAward)); - } - - function testZeroReclaimVesting() public { - address restrictedTokenAward = createDummyVestingAllocation(); - vm.warp(block.timestamp + 15 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 0); - - vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.stopPrank(); - console.log(token.balanceOf(restrictedTokenAward)); - } - - function testSlightReduc() public { - address restrictedTokenAward = createDummyVestingAllocation(); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 80e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 80e18); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.stopPrank(); - vm.warp(block.timestamp + 155 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - console.log(token.balanceOf(restrictedTokenAward)); - } - - function testLargeReduc() public { - address restrictedTokenAward = createDummyVestingAllocation(); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.stopPrank(); - vm.warp(block.timestamp + 155 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - console.log(token.balanceOf(restrictedTokenAward)); - } - - function testLargeReducOption() public { - address restrictedTokenAward = createDummyTokenOptionAllocation(); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(grantee); - //approve amount to exercise by getting amount to exercise and price - ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); - TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.stopPrank(); - vm.warp(block.timestamp + 155 seconds); - vm.startPrank(grantee); - ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); - TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - console.log(token.balanceOf(restrictedTokenAward)); - } - - - - function testReclaim() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - vm.warp(block.timestamp + 15 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - - vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.warp(block.timestamp + 155 days); - uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); - uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); - paymentToken.approve(address(restrictedTokenAward), payamt); - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); - vm.stopPrank(); - vm.prank(grantee); - RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); - console.log(token.balanceOf(restrictedTokenAward)); - } - - - -// function testFailUpdateExercisePriceForVesting() public { -// address vestingAllocation = createDummyVestingAllocation(); -// controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); -// } - -// function testFailRepurchaseTokensAfterExpiry() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// -// // Fast forward time to after the short stop date -// vm.warp(block.timestamp + 366 days); -// -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); -// } - -// function testFailRepurchaseTokensInsufficientAllowance() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// -// // Not approving any tokens -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); -// } - -// function testFailInitiateAuthorityUpdateNonAuthority() public { -// vm.prank(address(0x1234)); -// controller.initiateAuthorityUpdate(address(0x5678)); -// } - -// function testFailAcceptAuthorityRoleNonPendingAuthority() public { -// controller.initiateAuthorityUpdate(address(0x5678)); -// -// vm.prank(address(0x1234)); -// controller.acceptAuthorityRole(); -// } -// -// function testFailInitiateDaoUpdateNonDao() public { -// vm.prank(address(0x1234)); -// controller.initiateDaoUpdate(address(0x5678)); -// } -// -// function testFailAcceptDaoRoleNonPendingDao() public { -// vm.prank(dao); -// controller.initiateDaoUpdate(address(0x5678)); -// -// vm.prank(address(0x1234)); -// controller.acceptDaoRole(); -// } - - function testUpdateFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("testFunction()")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - - assertEq(controller.functionToConditions(functionSig, 0), address(condition)); - } - -// function testFailUpdateFunctionConditionNonDao() public { -// bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); -// address condition = address(0x1234); -// -// controller.updateFunctionCondition(condition, functionSig); -// } - - - function testRemoveFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - assert(controller.functionToConditions(functionSig, 0) == address(condition)); - vm.prank(dao); - controller.removeFunctionCondition(address(condition), functionSig); - } - -// function testFailCheckFunctionCondition() public { -// bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); -// /* constructor( -// address[] memory _signers, -// uint256 _threshold, -// Logic _logic -// ) */ -// address[] memory signers = new address[](2); -// signers[0] = address(0x1); -// signers[1] = address(0x2); -// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); -// -// vm.prank(dao); -// controller.updateFunctionCondition(address(condition), functionSig); -// assert(controller.functionToConditions(functionSig, 0) == address(condition)); -// //create a dummy metavest -// address vestingAllocation = createDummyVestingAllocation(); -// } - -// function testFailAddDuplicateCondition() public { -// bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); -// /* constructor( -// address[] memory _signers, -// uint256 _threshold, -// Logic _logic -// ) */ -// address[] memory signers = new address[](2); -// signers[0] = address(0x1); -// signers[1] = address(0x2); -// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); -// -// vm.prank(dao); -// controller.updateFunctionCondition(address(condition), functionSig); -// assert(controller.functionToConditions(functionSig, 0) == address(condition)); -// vm.prank(dao); -// controller.updateFunctionCondition(address(condition), functionSig); -// } -} \ No newline at end of file From 50103d6809f6b3c2896a36bd973c6cf256fd72af Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 30 Jul 2025 17:12:23 -0700 Subject: [PATCH 06/68] feat: solve circular dependency between MetaVesTController and ZkCappedMinter --- src/MetaVesTController.sol | 8 ++++++-- src/MetaVesTFactory.sol | 4 ++-- test/MetaVesTZkCappedMinterV2.t.sol | 32 +++++++++++++++-------------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index aab726f..ba4682a 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -97,6 +97,7 @@ contract metavestController is SafeTransferLib { event MetaVesTController_AddressAddedToSet(string set, address indexed grantee); event MetaVesTController_AddressRemovedFromSet(string set, address indexed grantee); event MetaVesTController_MetaVestCreated(address indexed metavest); + event MetaVesTController_ZkCappedMinterUpdated(address zkCappedMinter); /// /// ERRORS @@ -180,13 +181,12 @@ contract metavestController is SafeTransferLib { /// @param _authority address of the authority who can call the functions in this contract and update each MetaVesT in '_metavest', such as a BORG /// @param _dao DAO governance contract address which exercises control over ability of 'authority' to call certain functions via imposing /// conditions through 'updateFunctionCondition'. - constructor(address _authority, address _dao, address _vestingFactory, address _tokenOptionFactory, address _restrictedTokenFactory, address _zkCappedMinter) { + constructor(address _authority, address _dao, address _vestingFactory, address _tokenOptionFactory, address _restrictedTokenFactory) { if (_authority == address(0)) revert MetaVesTController_ZeroAddress(); authority = _authority; vestingFactory = _vestingFactory; tokenOptionFactory = _tokenOptionFactory; restrictedTokenFactory = _restrictedTokenFactory; - zkCappedMinter = _zkCappedMinter; dao = _dao; } @@ -765,4 +765,8 @@ contract metavestController is SafeTransferLib { emit MetaVesTController_AddressRemovedFromSet(_name, _metaVest); } + function setZkCappedMinter(address _zkCappedMinter) external onlyAuthority { + zkCappedMinter = _zkCappedMinter; + emit MetaVesTController_ZkCappedMinterUpdated(zkCappedMinter); + } } diff --git a/src/MetaVesTFactory.sol b/src/MetaVesTFactory.sol index 68c316f..dfbacaf 100644 --- a/src/MetaVesTFactory.sol +++ b/src/MetaVesTFactory.sol @@ -39,10 +39,10 @@ contract MetaVesTFactory { /// @dev conditionals are contained in the deployed MetaVesT, which is deployed in the MetaVesTController's constructor(); the MetaVesT within the MetaVesTController is immutable, but the 'authority' which has access control within the controller may replace itself /// @param _authority: address which initiates and may update each MetaVesT, such as a BORG or DAO /// @param _dao: contract address which token may be staked and used for voting, typically a DAO pool, governor, staking address. Submit address(0) for no such functionality. - function deployMetavestAndController(address _authority, address _dao, address _vestingAllocationFactory, address _tokenOptionFactory, address _restrictedTokenFactory, address _ZkCappedMinter ) external returns(address) { + function deployMetavestAndController(address _authority, address _dao, address _vestingAllocationFactory, address _tokenOptionFactory, address _restrictedTokenFactory ) external returns(address) { if(_vestingAllocationFactory == address(0) || _tokenOptionFactory == address(0) || _restrictedTokenFactory == address(0)) revert MetaVesTFactory_ZeroAddress(); - metavestController _controller = new metavestController(_authority, _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory, _ZkCappedMinter); + metavestController _controller = new metavestController(_authority, _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory); emit MetaVesT_Deployment(address(0), _authority, address(_controller), _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory); return address(_controller); } diff --git a/test/MetaVesTZkCappedMinterV2.t.sol b/test/MetaVesTZkCappedMinterV2.t.sol index 21f2acd..dd46ab9 100644 --- a/test/MetaVesTZkCappedMinterV2.t.sol +++ b/test/MetaVesTZkCappedMinterV2.t.sol @@ -28,6 +28,7 @@ contract MetaVesTZkCappedMinterV2Test is Test { metavestController controller; // Parameters + bytes32 salt = keccak256("MetaLexZkSyncTest"); uint256 cap = 1e6 ether; uint48 cappedMinterStartTime = uint48(block.timestamp + 30 days); uint48 cappedMinterExpirationTime = uint48(block.timestamp + 30 days + 365 days * 2); @@ -44,34 +45,35 @@ contract MetaVesTZkCappedMinterV2Test is Test { vm.startPrank(deployer); - // Deploy ZK Capped Minter v2 - - zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( - address(zkToken), - // TODO WIP derive address of MeteVesT controller - 0x49276208F85b2BA414B20fddE455a9a9711453aa, // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT - cap, - cappedMinterStartTime, - cappedMinterExpirationTime, - uint256(keccak256("MetaLexZkSyncTest")) - )); - // Deploy MetaVesT controller vestingAllocationFactory = new VestingAllocationFactory(); tokenOptionFactory = new TokenOptionFactory(); restrictedTokenFactory = new RestrictedTokenFactory(); - controller = new metavestController( + controller = new metavestController{salt: salt}( authority, dao, address(vestingAllocationFactory), address(tokenOptionFactory), - address(restrictedTokenFactory), - address(zkCappedMinter) + address(restrictedTokenFactory) ); + // Deploy ZK Capped Minter v2 + + zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + cap, + cappedMinterStartTime, + cappedMinterExpirationTime, + uint256(salt) + )); + vm.stopPrank(); + + vm.prank(authority); + controller.setZkCappedMinter(address(zkCappedMinter)); } // Test by forge test --zksync --fork-url https://zksync-sepolia.g.alchemy.com/v2/ --mc MetaVesTZkCappedMinterV2Test From 46f3dfd1435351f796f74e00963e39581fed2788 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 31 Jul 2025 09:23:56 -0700 Subject: [PATCH 07/68] feat: test multiple grantee --- .../zk-governance/IZkCappedMinterV2.sol | 1 + ...terV2.t.sol => ZkGuardianCompensation.sol} | 102 +++++++++++------- 2 files changed, 63 insertions(+), 40 deletions(-) rename test/{MetaVesTZkCappedMinterV2.t.sol => ZkGuardianCompensation.sol} (51%) diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2.sol b/src/interfaces/zk-governance/IZkCappedMinterV2.sol index 5706558..c971502 100644 --- a/src/interfaces/zk-governance/IZkCappedMinterV2.sol +++ b/src/interfaces/zk-governance/IZkCappedMinterV2.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.24; interface IZkCappedMinterV2 { function MINTER_ROLE() external returns (bytes32); + function START_TIME() external returns (uint48); function grantRole(bytes32 role, address account) external; diff --git a/test/MetaVesTZkCappedMinterV2.t.sol b/test/ZkGuardianCompensation.sol similarity index 51% rename from test/MetaVesTZkCappedMinterV2.t.sol rename to test/ZkGuardianCompensation.sol index dd46ab9..5e98cc6 100644 --- a/test/MetaVesTZkCappedMinterV2.t.sol +++ b/test/ZkGuardianCompensation.sol @@ -9,17 +9,16 @@ import "../src/interfaces/zk-governance/IZkTokenV1.sol"; import "forge-std/Test.sol"; // TODO this does not use the actual ZkCappedMinterV2 yet. Still v1 -contract MetaVesTZkCappedMinterV2Test is Test { +contract ZkGuardianCompensationTest is Test { address zkTokenAdmin; IZkTokenV1 zkToken; IZkCappedMinterV2Factory zkCappedMinterFactory; IZkCappedMinterV2 zkCappedMinter; address deployer = address(0x2); - address authority = address(0x3); - address dao = address(0x4); - address alice = address(0x5); - address bob = address(0x6); + address guardianSafe = address(0x3); + address alice = address(0x4); + address bob = address(0x5); VestingAllocationFactory vestingAllocationFactory; TokenOptionFactory tokenOptionFactory; @@ -52,8 +51,8 @@ contract MetaVesTZkCappedMinterV2Test is Test { restrictedTokenFactory = new RestrictedTokenFactory(); controller = new metavestController{salt: salt}( - authority, - dao, + guardianSafe, + guardianSafe, address(vestingAllocationFactory), address(tokenOptionFactory), address(restrictedTokenFactory) @@ -72,7 +71,7 @@ contract MetaVesTZkCappedMinterV2Test is Test { vm.stopPrank(); - vm.prank(authority); + vm.prank(guardianSafe); controller.setZkCappedMinter(address(zkCappedMinter)); } @@ -80,38 +79,51 @@ contract MetaVesTZkCappedMinterV2Test is Test { function test_GuardianCompensationYear1_2() public { // TODO guardians to sign agreements - // Create MetaVesT for Alice - - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( + // Create MetaVesT for Alice and Bob + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest( metavestController.metavestType.Vesting, alice, - allocation, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 10e18, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), milestones, 0, address(0), 0, 0 )); - assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it is minted on-the-fly)"); + assertEq(zkToken.balanceOf(address(vestingAllocationAlice)), 0, "Alice's vesting contract should not have any token (it mints on-demand)"); + + VestingAllocation vestingAllocationBob = VestingAllocation(controller.createMetavest( + metavestController.metavestType.Vesting, + bob, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 2000e18, + vestingCliffCredit: 200e18, + unlockingCliffCredit: 200e18, + vestingRate: 20e18, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 20e18, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + milestones, + 0, + address(0), + 0, + 0 + )); + assertEq(zkToken.balanceOf(address(vestingAllocationBob)), 0, "Vesting contract should not have any token (it mints on-demand)"); // Simulate TPP approval and ZK Token admin to grant our ZkCappedMinter access @@ -119,16 +131,26 @@ contract MetaVesTZkCappedMinterV2Test is Test { vm.prank(zkTokenAdmin); zkToken.grantRole(minterRole, address(zkCappedMinter)); - // Wait until capped minter start time + // Wait until capped minter start time and simulate grantee withdrawing cliff amounts skip(30 days); - // Simulate alice withdrawal - // Since there is a cliff and vesting/unlock starts immediately, the grantee should be able to withdraw the cliff amount + granteeWithdrawAndAsserts(vestingAllocationAlice, 100e18, "Alice cliff"); + granteeWithdrawAndAsserts(vestingAllocationBob, 200e18, "Bob cliff"); + + // Wait until fully vested and unlocked and simulate full withdrawal + skip(90 seconds); + + granteeWithdrawAndAsserts(vestingAllocationAlice, 900e18, "Alice full"); + granteeWithdrawAndAsserts(vestingAllocationBob, 1800e18, "Bob full"); + } - uint256 balanceBefore = zkToken.balanceOf(alice); - vm.prank(alice); - vestingAllocation.withdraw(100e18); - assertEq(zkToken.balanceOf(alice), balanceBefore + 100e18, "Grantee should be able to withdraw cliff amount"); - assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it is minted on-the-fly)"); + function granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) public { + address grantee = vestingAllocation.grantee(); + uint256 balanceBefore = zkToken.balanceOf(grantee); + assertEq(vestingAllocation.getAmountWithdrawable(), amount, string(abi.encodePacked(assertName, ": unexpected withdrawable amount after cliff"))); + vm.prank(grantee); + vestingAllocation.withdraw(amount); + assertEq(zkToken.balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); + assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); } } From c1ba2774d7b273ac1e74794f38f834b179ec0a4e Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 31 Jul 2025 09:53:55 -0700 Subject: [PATCH 08/68] test: fix old tests. Fixed add/remove milestones --- src/BaseAllocation.sol | 7 +- src/MetaVesTController.sol | 10 +- test/AuditBaseA.t.sol | 57 + test/AuditBaseA2.t.sol | 216 +++ test/AuditBaseC.t.sol | 61 + test/AuditBaseC3.t.sol | 87 ++ test/MetaVesTFactory.t.sol | 125 ++ test/ZkGuardianCompensation.sol | 2 +- test/amendement.t.sol | 1454 ++++++++++++++++++ test/controller.t.sol | 2439 +++++++++++++++++++++++++++++++ 10 files changed, 4445 insertions(+), 13 deletions(-) create mode 100644 test/AuditBaseA.t.sol create mode 100644 test/AuditBaseA2.t.sol create mode 100644 test/AuditBaseC.t.sol create mode 100644 test/AuditBaseC3.t.sol create mode 100644 test/MetaVesTFactory.t.sol create mode 100644 test/amendement.t.sol create mode 100644 test/controller.t.sol diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index 76c1337..1764cfa 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -271,8 +271,7 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ if(terminated) revert MetaVesT_AlreadyTerminated(); if (_milestoneIndex >= milestones.length) revert MetaVesT_MilestoneIndexOutOfRange(); uint256 _milestoneAward = milestones[_milestoneIndex].milestoneAward; - //transfer the milestone award back to the authority, we check in the controller to ensure only uncompleted milestones can be removed - safeTransfer(allocation.tokenContract, getAuthority(), _milestoneAward); + // No need to transfer the milestone award back to the authority since the tokens are minted on-demand delete milestones[_milestoneIndex]; milestones[_milestoneIndex] = milestones[milestones.length - 1]; milestones.pop(); @@ -312,11 +311,7 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ /// @param _amount - the amount of tokens to withdraw function withdraw(uint256 _amount) external nonReentrant onlyGrantee { if (_amount == 0) revert MetaVesT_ZeroAmount(); - - // TODO Update other places accordingly -// if (_amount > getAmountWithdrawable() || _amount > IERC20M(allocation.tokenContract).balanceOf(address(this))) revert MetaVesT_MoreThanAvailable(); if (_amount > getAmountWithdrawable()) revert MetaVesT_MoreThanAvailable(); - tokensWithdrawn += _amount; IZkCappedMinterV2(ZkCappedMinterAddress).mint(msg.sender, _amount); emit MetaVesT_Withdrawn(msg.sender, allocation.tokenContract, _amount); diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index ba4682a..31bea8a 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -240,10 +240,12 @@ contract metavestController is SafeTransferLib { } else if(_type == metavestType.TokenOption) { + // TODO WIP adopt ZkCappedMinter v2 newMetavest = createTokenOptionAllocation(_grantee, _exercisePrice, _paymentToken, _shortStopDuration, _allocation, _milestones); } else if(_type == metavestType.RestrictedTokenAward) { + // TODO WIP adopt ZkCappedMinter v2 newMetavest = createRestrictedTokenAward(_grantee, _exercisePrice, _paymentToken, _shortStopDuration, _allocation, _milestones); } else @@ -472,13 +474,9 @@ contract metavestController is SafeTransferLib { if (_milestone.milestoneAward == 0) revert MetaVesTController_ZeroAmount(); if (_milestone.conditionContracts.length > ARRAY_LENGTH_LIMIT) revert MetaVesTController_LengthMismatch(); if (_milestone.complete == true) revert MetaVesTController_MilestoneIndexCompletedOrDoesNotExist(); - if ( - IERC20M(_tokenContract).allowance(msg.sender, address(this)) < _milestone.milestoneAward || - IERC20M(_tokenContract).balanceOf(msg.sender) < _milestone.milestoneAward - ) revert MetaVesT_AmountNotApprovedForTransferFrom(); - // send the new milestoneAward to 'metavest' - safeTransferFrom(_tokenContract, msg.sender, _grant, _milestone.milestoneAward); + // No need to allocate token right now since they are minted on-demand + BaseAllocation(_grant).addMilestone(_milestone); } diff --git a/test/AuditBaseA.t.sol b/test/AuditBaseA.t.sol new file mode 100644 index 0000000..cd70c04 --- /dev/null +++ b/test/AuditBaseA.t.sol @@ -0,0 +1,57 @@ +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; + +import "../test/amendement.t.sol"; + +contract EvilGrant { + + function grantee () public view returns (address) { + return address(0x31337); + } + function getGoverningPower() public view returns (uint256) { + return 99999999999999999999999999999; + } +} + +contract Audit is MetaVestControllerTest { + function test_RevertIf_AuditArbitraryVote() public { + // template from testVoteOnMetavestAmendment + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(mockAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + address attacker = address(0x31337); + address evil_grant = address(new EvilGrant()); + + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); + + (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + console.log("attacker made vote and power is" , currentVotingPower); + } + + function test_RevertIf_AuditRemoveConfirmedMilestone() public { + // template from testRemoveMilestone + address vestingAllocation = createDummyVestingAllocation(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); + controller.removeMetavestMilestone(vestingAllocation, 0); + } + +} \ No newline at end of file diff --git a/test/AuditBaseA2.t.sol b/test/AuditBaseA2.t.sol new file mode 100644 index 0000000..b90f160 --- /dev/null +++ b/test/AuditBaseA2.t.sol @@ -0,0 +1,216 @@ +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; + +import "../test/amendement.t.sol"; + +contract EvilGrant { + + function grantee () public view returns (address) { + return address(0x31337); + } + function getGoverningPower() public view returns (uint256) { + return 99999999999999999999999999999; + } +} + +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract Audit is MetaVestControllerTest { + function test_RevertIf_AuditArbitraryVote() public { + // template from testVoteOnMetavestAmendment + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(mockAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + address attacker = address(0x31337); + address evil_grant = address(new EvilGrant()); + + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); + + (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + console.log("attacker made vote and power is" , currentVotingPower); + } + + function test_RevertIf_AuditRemoveConfirmedMilestone() public { + // template from testRemoveMilestone + address vestingAllocation = createDummyVestingAllocation(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); + controller.removeMetavestMilestone(vestingAllocation, 0); + } + + function testAuditProposeMajorityMetavestAmendmentExpire() public { + // template from testProposeMajorityMetavestAmendment + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + // proposal expired + uint256 AMENDMENT_TIME_LIMIT = 604800; + vm.warp(block.timestamp + AMENDMENT_TIME_LIMIT + 1); + + // MetaVesTController_AmendmentAlreadyPending even expired + vm.prank(authority); + vm.expectRevert(); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + } + +// function testAuditModifiedCalldataProposal() public { +// // template from testCreateSetWithThreeTokenOptionsAndChangeExercisePrice +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// vm.expectRevert(); +// controller.updateExerciseOrRepurchasePrice(allocation1, 999999999999999999999e18); +// +// // Bypass MetaVesTController_AmendmentNeitherMutualNorMajorityConsented +// vm.prank(authority); +// bytes memory p = abi.encodeWithSelector(msgSig, allocation1, 999999999999999999999e18, 2e18); +// address(controller).call(p); +// +// console.log('Modified excercise price: ', TokenOptionAllocation(allocation1).exercisePrice()); +// } + + function testAuditConsentToMetavestAmendmentInFlavor() public { + // template from testRemoveMilestone + address vestingAllocation = createDummyVestingAllocation(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, false); + // emit MetaVesTController_AmendmentConsentUpdated(msgSig: 0x75b89e4f00000000000000000000000000000000000000000000000000000000, grantee: ECAdd: [0x0000000000000000000000000000000000000006], inFavor: false) + console.log("expected inFavor: false"); + (,,bool inFavor) = controller.functionToGranteeToAmendmentPending(selector, vestingAllocation); + console.log("output: ", inFavor); + assertEq(inFavor, false); + + } + + function test_RevertIf_AuditProposeMajorityMetavestAmendmentNewGranteeDuringProposal() public { + // template from testProposeMajorityMetavestAmendment + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + address mockAllocation4 = createDummyVestingAllocation(); + address mockAllocation5 = createDummyVestingAllocation(); + address mockAllocation6 = createDummyVestingAllocation(); + address mockAllocation7 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.startPrank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation4); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation5); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation6); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation7); + vm.stopPrank(); + + vm.startPrank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation5, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation6, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation7, "testSet", msgSig, true); + vm.stopPrank(); + + (uint256 totalVotingPower, uint256 currentVotingPower,,,) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + console.log("totalVotingPower: ", totalVotingPower); + console.log("currentVotingPower: ", currentVotingPower); + } + + function testCreateSetAddVestingThenRemoveSet() public { + + // template from testCreateSetAddVestingThenRemoveSet + address allocation1 = createDummyVestingAllocation(); + address allocation2 = createDummyVestingAllocation(); + address allocation3 = createDummyVestingAllocation(); + + vm.startPrank(authority); + controller.createSet("testSetB"); + controller.addMetaVestToSet("testSetB", allocation1); + controller.addMetaVestToSet("testSetB", allocation2); + controller.addMetaVestToSet("testSetB", allocation3); + controller.removeSet("testSetB"); + controller.createSet("testSetB"); + vm.stopPrank(); + + } + +} \ No newline at end of file diff --git a/test/AuditBaseC.t.sol b/test/AuditBaseC.t.sol new file mode 100644 index 0000000..f1e6dad --- /dev/null +++ b/test/AuditBaseC.t.sol @@ -0,0 +1,61 @@ +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; + +import "../test/controller.t.sol"; + +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract Audit is MetaVestControllerTest { + + function testAuditTerminateFailAfterWithdrawFixCheck() public { + // template from testTerminateVestAndRecoverSlowUnlock + address vestingAllocation = createDummyVestingAllocationSlowUnlock(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.startPrank(grantee); + skip(25 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 1000e18 + 100e18 + 25 * 5e18, "Unexpected amount after cliff and milestone"); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + skip(25 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 25 * 5e18, "Unexpected amount after the second vesting period"); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + + controller.terminateMetavestVesting(vestingAllocation); + vm.warp(block.timestamp + 365 days); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), (10e18 - 5e18) * 50, "Unexpected amount after termination"); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + +// function testAuditTerminateFailAfterWithdrawFixCheckOptions() public { +// // template from testTerminateVestAndRecoverSlowUnlock +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// VestingAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 5 seconds); +// vm.startPrank(grantee); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.warp(block.timestamp + 5 seconds); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// vm.warp(block.timestamp + 5 seconds); +// controller.terminateMetavestVesting(vestingAllocation); +// +// vm.startPrank(grantee); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// vm.warp(block.timestamp + 365 days); +// +// vm.prank(authority); +// TokenOptionAllocation(vestingAllocation).recoverForfeitTokens(); +// //check balance of the vesting contract +// assertEq(token.balanceOf(vestingAllocation), 0); +// } +} \ No newline at end of file diff --git a/test/AuditBaseC3.t.sol b/test/AuditBaseC3.t.sol new file mode 100644 index 0000000..ff127d9 --- /dev/null +++ b/test/AuditBaseC3.t.sol @@ -0,0 +1,87 @@ +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; + +import "../test/controller.t.sol"; + +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract Audit is MetaVestControllerTest { + + function testAuditTerminateVestAndRecovers() public { + // template from testTerminateVestAndRecovers + address vestingAllocation = createDummyVestingAllocation(); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000e18, + unlockOnCompletion: false, + complete: false, + conditionContracts: new address[](0) + }); + + controller.addMetavestMilestone(vestingAllocation, milestones[0]); + VestingAllocation(vestingAllocation).confirmMilestone(1); + + skip(50 seconds); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + assertEq(VestingAllocation(vestingAllocation).getVestedTokenAmount(), 100e18 + 1000e18 + 10e18 * 50, "Unexpected vested amount after termination"); + assertEq(VestingAllocation(vestingAllocation).getUnlockedTokenAmount(), 100e18 + 5e18 * 50 + 5e18 * 50, "Unexpected unlocked amount after termination"); + + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + // assertEq(token.balanceOf(vestingAllocation), 0); + } + +// function test_RevertIf_AuditRounding() public { +// // template from testConfirmingMilestoneTokenOption +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// +// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e6)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e11)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(9.99e11)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e12)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1.1e12)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e13)); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1.1e12); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// +// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } +// +// function testAuditExcercisePrice() public { +// // template from testConfirmingMilestoneTokenOption +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// +// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e18); +// +// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } +} diff --git a/test/MetaVesTFactory.t.sol b/test/MetaVesTFactory.t.sol new file mode 100644 index 0000000..3b36d99 --- /dev/null +++ b/test/MetaVesTFactory.t.sol @@ -0,0 +1,125 @@ +//SPDX-License-Identifier: AGPL-3.0-only + +pragma solidity ^0.8.18; + +import "../src/MetaVesTController.sol"; +import "../src/MetaVesTFactory.sol"; +import "../src/RestrictedTokenFactory.sol"; +import "../src/TokenOptionFactory.sol"; +import "../src/VestingAllocationFactory.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "forge-std/Test.sol"; + +/// @dev foundry framework testing of MetaVesTFactory.sol +/// forge t --via-ir + +/// @notice test contract for MetaVesTFactory using Foundry +contract MetaVesTFactoryTest is Test { + // zkSync Era Sepolia @ 5576300 + address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; + IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); + IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); +// // zkSync Era mainnet +// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; +// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); +// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); + + IZkCappedMinterV2 zkCappedMinter; + + MetaVesTFactory internal factory; + metavestController controller; + address factoryAddr; + address dai_addr = 0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6; + VestingAllocationFactory _factory;// = new VestingAllocationFactory(); + RestrictedTokenFactory _factory2;// = new RestrictedTokenFactory(); + TokenOptionFactory _factory3;// = new TokenOptionFactory(); + + bytes32 salt = keccak256("MetaVesTFactoryTest"); + + event MetaVesT_Deployment( + address newMetaVesT, + address authority, + address controller, + address dao, + address paymentToken + ); + + function setUp() public { + _factory = new VestingAllocationFactory(); + _factory2 = new RestrictedTokenFactory(); + _factory3 = new TokenOptionFactory(); + factory = new MetaVesTFactory(); + factoryAddr = address(factory); + address _authority = address(0xa); + + address _dao = address(0xB); + address _paymentToken = address(0xC); + + controller = metavestController(factory.deployMetavestAndController(_authority, _dao, address(_factory), address(_factory2), address(_factory3))); + + // Deploy ZK Capped Minter v2 + zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + 1000e18, + uint48(block.timestamp), // start now + uint48(block.timestamp + 365 days * 2), + uint256(salt) + )); + + vm.prank(_authority); + controller.setZkCappedMinter(address(zkCappedMinter)); + } + + function testDeployMetavestAndController() public { + address _authority = address(0xa); + address _dao = address(0xB); + address _paymentToken = address(0xC); + address grantee = address(0xD); + RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); + + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10e18, + unlockStartTime: uint48(block.timestamp) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 100e18, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + //token.approve(address(controller), 1100e18); + + VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + 0, + address(0), + 0, + 0 + + )); + + assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); + assertEq(vestingAllocation.getAmountWithdrawable(), 100e18, "Should be able to withdraw cliff amount"); + } + + function test_RevertIf_ControllerZeroAddress() public { + address _authority = address(0); + address _dao = address(0); + address _paymentToken = address(0); + vm.expectRevert(abi.encodeWithSelector(MetaVesTFactory.MetaVesTFactory_ZeroAddress.selector)); + factory.deployMetavestAndController(_authority, _dao, address(0), address(0), address(0)); + } +} diff --git a/test/ZkGuardianCompensation.sol b/test/ZkGuardianCompensation.sol index 5e98cc6..283adff 100644 --- a/test/ZkGuardianCompensation.sol +++ b/test/ZkGuardianCompensation.sol @@ -33,7 +33,7 @@ contract ZkGuardianCompensationTest is Test { uint48 cappedMinterExpirationTime = uint48(block.timestamp + 30 days + 365 days * 2); function setUp() public { - // zkSync Era Sepolia + // zkSync Era Sepolia @ 5576300 zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); diff --git a/test/amendement.t.sol b/test/amendement.t.sol new file mode 100644 index 0000000..6ae2ca9 --- /dev/null +++ b/test/amendement.t.sol @@ -0,0 +1,1454 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/metavestController.sol"; +import "../src/BaseAllocation.sol"; +import "../src/RestrictedTokenAllocation.sol"; +import "../src/interfaces/IAllocationFactory.sol"; +import "../src/VestingAllocationFactory.sol"; +import "../src/TokenOptionFactory.sol"; +import "../src/RestrictedTokenFactory.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; + +abstract contract ERC20 { + + /// @dev The total supply has overflowed. + error TotalSupplyOverflow(); + + /// @dev The allowance has overflowed. + error AllowanceOverflow(); + + /// @dev The allowance has underflowed. + error AllowanceUnderflow(); + + /// @dev Insufficient balance. + error InsufficientBalance(); + + /// @dev Insufficient allowance. + error InsufficientAllowance(); + + /// @dev The permit is invalid. + error InvalidPermit(); + + /// @dev The permit has expired. + error PermitExpired(); + + /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. + event Transfer(address indexed from, address indexed to, uint256 amount); + + /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. + uint256 private constant _TRANSFER_EVENT_SIGNATURE = + 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; + + /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. + uint256 private constant _APPROVAL_EVENT_SIGNATURE = + 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; + + /// @dev The storage slot for the total supply. + uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; + + /// @dev The balance slot of `owner` is given by: + /// ``` + /// mstore(0x0c, _BALANCE_SLOT_SEED) + /// mstore(0x00, owner) + /// let balanceSlot := keccak256(0x0c, 0x20) + /// ``` + uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; + + /// @dev The allowance slot of (`owner`, `spender`) is given by: + /// ``` + /// mstore(0x20, spender) + /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) + /// mstore(0x00, owner) + /// let allowanceSlot := keccak256(0x0c, 0x34) + /// ``` + uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; + + /// @dev The nonce slot of `owner` is given by: + /// ``` + /// mstore(0x0c, _NONCES_SLOT_SEED) + /// mstore(0x00, owner) + /// let nonceSlot := keccak256(0x0c, 0x20) + /// ``` + uint256 private constant _NONCES_SLOT_SEED = 0x38377508; + + + /// @dev Returns the name of the token. + function name() public view virtual returns (string memory); + + /// @dev Returns the symbol of the token. + function symbol() public view virtual returns (string memory); + + /// @dev Returns the decimals places of the token. + function decimals() public view virtual returns (uint8) { + return 18; + } + + + /// @dev Returns the amount of tokens in existence. + function totalSupply() public view virtual returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := sload(_TOTAL_SUPPLY_SLOT) + } + } + + /// @dev Returns the amount of tokens owned by `owner`. + function balanceOf(address owner) public view virtual returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, owner) + result := sload(keccak256(0x0c, 0x20)) + } + } + + /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. + function allowance(address owner, address spender) + public + view + virtual + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, owner) + result := sload(keccak256(0x0c, 0x34)) + } + } + + /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + /// + /// Emits a {Approval} event. + function approve(address spender, uint256 amount) public virtual returns (bool) { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and store the amount. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, caller()) + sstore(keccak256(0x0c, 0x34), amount) + // Emit the {Approval} event. + mstore(0x00, amount) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) + } + return true; + } + + /// @dev Atomically increases the allowance granted to `spender` by the caller. + /// + /// Emits a {Approval} event. + function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and load its value. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, caller()) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowanceBefore := sload(allowanceSlot) + // Add to the allowance. + let allowanceAfter := add(allowanceBefore, difference) + // Revert upon overflow. + if lt(allowanceAfter, allowanceBefore) { + mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. + revert(0x1c, 0x04) + } + // Store the updated allowance. + sstore(allowanceSlot, allowanceAfter) + // Emit the {Approval} event. + mstore(0x00, allowanceAfter) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) + } + return true; + } + + /// @dev Atomically decreases the allowance granted to `spender` by the caller. + /// + /// Emits a {Approval} event. + function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and load its value. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, caller()) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowanceBefore := sload(allowanceSlot) + // Revert if will underflow. + if lt(allowanceBefore, difference) { + mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated allowance. + let allowanceAfter := sub(allowanceBefore, difference) + sstore(allowanceSlot, allowanceAfter) + // Emit the {Approval} event. + mstore(0x00, allowanceAfter) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) + } + return true; + } + + /// @dev Transfer `amount` tokens from the caller to `to`. + /// + /// Requirements: + /// - `from` must at least have `amount`. + /// + /// Emits a {Transfer} event. + function transfer(address to, uint256 amount) public virtual returns (bool) { + _beforeTokenTransfer(msg.sender, to, amount); + /// @solidity memory-safe-assembly + assembly { + // Compute the balance slot and load its value. + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, caller()) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Compute the balance slot of `to`. + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance of `to`. + // Will not overflow because the sum of all user balances + // cannot exceed the maximum uint256 value. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) + } + _afterTokenTransfer(msg.sender, to, amount); + return true; + } + + /// @dev Transfers `amount` tokens from `from` to `to`. + /// + /// Note: Does not update the allowance if it is the maximum uint256 value. + /// + /// Requirements: + /// - `from` must at least have `amount`. + /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. + /// + /// Emits a {Transfer} event. + function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { + _beforeTokenTransfer(from, to, amount); + /// @solidity memory-safe-assembly + assembly { + let from_ := shl(96, from) + // Compute the allowance slot and load its value. + mstore(0x20, caller()) + mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowance_ := sload(allowanceSlot) + // If the allowance is not the maximum uint256 value. + if iszero(eq(allowance_, not(0))) { + // Revert if the amount to be transferred exceeds the allowance. + if gt(amount, allowance_) { + mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated allowance. + sstore(allowanceSlot, sub(allowance_, amount)) + } + // Compute the balance slot and load its value. + mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Compute the balance slot of `to`. + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance of `to`. + // Will not overflow because the sum of all user balances + // cannot exceed the maximum uint256 value. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) + } + _afterTokenTransfer(from, to, amount); + return true; + } + + /// @dev Returns the current nonce for `owner`. + /// This value is used to compute the signature for EIP-2612 permit. + function nonces(address owner) public view virtual returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + // Compute the nonce slot and load its value. + mstore(0x0c, _NONCES_SLOT_SEED) + mstore(0x00, owner) + result := sload(keccak256(0x0c, 0x20)) + } + } + + /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, + /// authorized by a signed approval by `owner`. + /// + /// Emits a {Approval} event. + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + bytes32 domainSeparator = DOMAIN_SEPARATOR(); + /// @solidity memory-safe-assembly + assembly { + // Grab the free memory pointer. + let m := mload(0x40) + // Revert if the block timestamp greater than `deadline`. + if gt(timestamp(), deadline) { + mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. + revert(0x1c, 0x04) + } + // Clean the upper 96 bits. + owner := shr(96, shl(96, owner)) + spender := shr(96, shl(96, spender)) + // Compute the nonce slot and load its value. + mstore(0x0c, _NONCES_SLOT_SEED) + mstore(0x00, owner) + let nonceSlot := keccak256(0x0c, 0x20) + let nonceValue := sload(nonceSlot) + // Increment and store the updated nonce. + sstore(nonceSlot, add(nonceValue, 1)) + // Prepare the inner hash. + // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. + // forgefmt: disable-next-item + mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) + mstore(add(m, 0x20), owner) + mstore(add(m, 0x40), spender) + mstore(add(m, 0x60), value) + mstore(add(m, 0x80), nonceValue) + mstore(add(m, 0xa0), deadline) + // Prepare the outer hash. + mstore(0, 0x1901) + mstore(0x20, domainSeparator) + mstore(0x40, keccak256(m, 0xc0)) + // Prepare the ecrecover calldata. + mstore(0, keccak256(0x1e, 0x42)) + mstore(0x20, and(0xff, v)) + mstore(0x40, r) + mstore(0x60, s) + pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) + // If the ecrecover fails, the returndatasize will be 0x00, + // `owner` will be be checked if it equals the hash at 0x00, + // which evaluates to false (i.e. 0), and we will revert. + // If the ecrecover succeeds, the returndatasize will be 0x20, + // `owner` will be compared against the returned address at 0x20. + if iszero(eq(mload(returndatasize()), owner)) { + mstore(0x00, 0xddafbaef) // `InvalidPermit()`. + revert(0x1c, 0x04) + } + // Compute the allowance slot and store the value. + // The `owner` is already at slot 0x20. + mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) + sstore(keccak256(0x2c, 0x34), value) + // Emit the {Approval} event. + log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero pointer. + } + } + + /// @dev Returns the EIP-2612 domains separator. + function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) // Grab the free memory pointer. + } + // We simply calculate it on-the-fly to allow for cases where the `name` may change. + bytes32 nameHash = keccak256(bytes(name())); + /// @solidity memory-safe-assembly + assembly { + let m := result + // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. + // forgefmt: disable-next-item + mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) + mstore(add(m, 0x20), nameHash) + // `keccak256("1")`. + // forgefmt: disable-next-item + mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + result := keccak256(m, 0xa0) + } + } + + /// @dev Mints `amount` tokens to `to`, increasing the total supply. + /// + /// Emits a {Transfer} event. + function _mint(address to, uint256 amount) internal virtual { + _beforeTokenTransfer(address(0), to, amount); + /// @solidity memory-safe-assembly + assembly { + let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) + let totalSupplyAfter := add(totalSupplyBefore, amount) + // Revert if the total supply overflows. + if lt(totalSupplyAfter, totalSupplyBefore) { + mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. + revert(0x1c, 0x04) + } + // Store the updated total supply. + sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) + // Compute the balance slot and load its value. + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) + } + _afterTokenTransfer(address(0), to, amount); + } + + /// @dev Burns `amount` tokens from `from`, reducing the total supply. + /// + /// Emits a {Transfer} event. + function _burn(address from, uint256 amount) internal virtual { + _beforeTokenTransfer(from, address(0), amount); + /// @solidity memory-safe-assembly + assembly { + // Compute the balance slot and load its value. + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, from) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Subtract and store the updated total supply. + sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) + // Emit the {Transfer} event. + mstore(0x00, amount) + log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) + } + _afterTokenTransfer(from, address(0), amount); + } + + /// @dev Moves `amount` of tokens from `from` to `to`. + function _transfer(address from, address to, uint256 amount) internal virtual { + _beforeTokenTransfer(from, to, amount); + /// @solidity memory-safe-assembly + assembly { + let from_ := shl(96, from) + // Compute the balance slot and load its value. + mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Compute the balance slot of `to`. + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance of `to`. + // Will not overflow because the sum of all user balances + // cannot exceed the maximum uint256 value. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) + } + _afterTokenTransfer(from, to, amount); + } + + /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and load its value. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, owner) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowance_ := sload(allowanceSlot) + // If the allowance is not the maximum uint256 value. + if iszero(eq(allowance_, not(0))) { + // Revert if the amount to be transferred exceeds the allowance. + if gt(amount, allowance_) { + mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated allowance. + sstore(allowanceSlot, sub(allowance_, amount)) + } + } + } + + /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. + /// + /// Emits a {Approval} event. + function _approve(address owner, address spender, uint256 amount) internal virtual { + /// @solidity memory-safe-assembly + assembly { + let owner_ := shl(96, owner) + // Compute the allowance slot and store the amount. + mstore(0x20, spender) + mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) + sstore(keccak256(0x0c, 0x34), amount) + // Emit the {Approval} event. + mstore(0x00, amount) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) + } + } + + + /// @dev Hook that is called before any transfer of tokens. + /// This includes minting and burning. + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} + + /// @dev Hook that is called after any transfer of tokens. + /// This includes minting and burning. + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} +} + +contract MockERC20 is ERC20 { + constructor(string memory name, string memory symbol) ERC20() {} + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + function name() public view override returns (string memory) { + return "Test Token"; + } + function symbol() public view override returns (string memory) { + return "TT"; + } +} + +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract MetaVestControllerTest is Test { + // zkSync Era Sepolia @ 5576300 + address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; + IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); + IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); +// // zkSync Era mainnet +// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; +// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); +// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); + + IZkCappedMinterV2 zkCappedMinter; + + metavestController public controller; +// MockERC20 public paymentToken; + address public authority; + address public dao; + address public vestingFactory; + address public tokenOptionFactory; + address public restrictedTokenFactory; + address public grantee; + address public mockAllocation; + + bytes32 salt = keccak256("MetaVestControllerTest"); + + function setUp() public { + authority = address(this); + dao = address(2); + VestingAllocationFactory factory = new VestingAllocationFactory(); + TokenOptionFactory tokenFactory = new TokenOptionFactory(); + RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); + grantee = address(6); + +// paymentToken = new MockERC20("Payment Token", "PT"); + + controller = new metavestController( + authority, + dao, + address(factory), + address(tokenFactory), + address(restrictedTokenFactory) + ); + +// paymentToken.mint(authority, 1000000e58); +// +// paymentToken.transfer(address(grantee), 1000e25); + + vm.prank(authority); + controller.createSet("testSet"); + + // Deploy ZK Capped Minter v2 + zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + 1000e18, + uint48(block.timestamp), // start now + uint48(block.timestamp + 365 days * 2), + uint256(salt) + )); + + vm.prank(authority); + controller.setZkCappedMinter(address(zkCappedMinter)); + + mockAllocation = createDummyVestingAllocation(); + } + + function testProposeMetavestAmendment() public { + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); + + vm.prank(authority); + controller.proposeMetavestAmendment(address(mockAllocation), msgSig, callData); + + (bool isPending, bytes32 dataHash, bool inFavor) = controller.functionToGranteeToAmendmentPending(msgSig, address(mockAllocation)); + + assertTrue(isPending); + assertEq(dataHash, keccak256(callData)); + assertFalse(inFavor); + } + + function test_RevertIf_ProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + address mockAllocation4 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + controller.addMetaVestToSet("testSet", mockAllocation3); + controller.addMetaVestToSet("testSet", mockAllocation4); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + //log the current withdrawable + console.log(VestingAllocation(mockAllocation2).getAmountWithdrawable()); + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(mockAllocation2, true); + } + + function testQuickProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + address mockAllocation4 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + controller.addMetaVestToSet("testSet", mockAllocation3); + controller.addMetaVestToSet("testSet", mockAllocation4); + vm.warp(block.timestamp + 15 seconds); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.startPrank(grantee); + //log the current withdrawable + console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); + + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); + vm.stopPrank(); + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true); + } + + +// function testMajorityPowerMetavestAmendment() public { +// address mockAllocation2 = createDummyTokenOptionAllocation(); +// address mockAllocation3 = createDummyTokenOptionAllocation(); +// address mockAllocation4 = createDummyTokenOptionAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// controller.addMetaVestToSet("testSet", mockAllocation4); +// vm.warp(block.timestamp + 1 days); +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +// vm.stopPrank(); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } + +// function test_RevertIf_MajorityPowerMetavestAmendment() public { +// address mockAllocation2 = createDummyTokenOptionAllocation(); +// address mockAllocation3 = createDummyTokenOptionAllocation(); +// address mockAllocation4 = createDummyTokenOptionAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// controller.addMetaVestToSet("testSet", mockAllocation4); +// vm.warp(block.timestamp + 1 days); +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +// vm.stopPrank(); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } + + function testProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation3, true); + } + + function testProposeMajorityMetavestAmendmentReAdd() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation3, true); + + vm.prank(authority); + controller.removeMetaVestFromSet("testSet", mockAllocation3); + // vm.prank(authority); + // controller.updateMetavestTransferability(mockAllocation3, true); + vm.warp(block.timestamp + 90 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation3, true); + } + + function test_RevertIf_NoPassProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(mockAllocation2, true); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(mockAllocation3, true); + } + + function testVoteOnMetavestAmendment() public { + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(mockAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); + + (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + + } + + function test_RevertIf_VoteOnMetavestAmendmentTwice() public { + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(mockAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.startPrank(grantee); + controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AlreadyVoted.selector)); + controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); + vm.stopPrank(); + } + + function testSetManagement() public { + vm.startPrank(authority); + + // Test creating a new set + controller.createSet("newSet"); + + // Test adding a MetaVest to a set + controller.addMetaVestToSet("newSet", address(mockAllocation)); + + + // Test removing a MetaVest from a set + controller.removeMetaVestFromSet("newSet", address(mockAllocation)); + + + // Test removing a set + controller.removeSet("newSet"); + + + vm.stopPrank(); + } + + function test_RevertIf_CreateDuplicateSet() public { + vm.startPrank(authority); + controller.createSet("duplicateSet"); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetAlreadyExists.selector)); + controller.createSet("duplicateSet"); + vm.stopPrank(); + } + + function test_RevertIf_NonAuthorityCreateSet() public { + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); + controller.createSet("unauthorizedSet"); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation() internal returns (address) { + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10e18, + unlockStartTime: uint48(block.timestamp) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 100e18, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + return controller.createMetavest( + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + 0, + address(0), + 0, + 0 + + ); + } + +// function createDummyTokenOptionAllocation() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 1100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// ); +// } +// +// function createDummyRestrictedTokenAward() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 1100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// +// ); +// } + + //write a test for every consentcheck function in metavest controller + function testConsentCheck() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(allocation, true); + } + + function test_RevertIf_ConsentCheck() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(allocation, true); + } + + function test_RevertIf_ConsentCheckNoProposal() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); + } + + function test_RevertIf_ConsentCheckNoVote() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(allocation, true); + } + + function test_RevertIf_ConsentCheckNoUpdate() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); + } + + function test_RevertIf_ConsentCheckNoVoteUpdate() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); + } + +// function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +// +// // Check that the exercise price was updated +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +// } + +// function test_RevertIf_CreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// //vm.prank(grantee); +// // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// // vm.prank(grantee); +// // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +// +// // Check that the exercise price was updated +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +// } + + function test_RevertIf_consentToNoPendingAmendment() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_NoPendingAmendment.selector, msgSig, allocation)); + controller.consentToMetavestAmendment(allocation, msgSig, true); + } + +// function testEveryUpdateAmendmentFunction() public { +// address allocation = createDummyTokenOptionAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(allocation, true); +// +// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); +// +// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 0); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.removeMetavestMilestone(allocation, 0); +// +// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestUnlockRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); +// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); +// } + +// function testEveryUpdateAmendmentFunctionRestricted() public { +// address allocation = createDummyRestrictedTokenAward(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(allocation, true); +// +// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); +// +// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 0); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.removeMetavestMilestone(allocation, 0); +// +// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestUnlockRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); +// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); +// } + + function testEveryUpdateAmendmentFunctionVesting() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(allocation, true); + + msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + callData = abi.encodeWithSelector(msgSig, allocation, 0); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.removeMetavestMilestone(allocation, 0); + + msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestUnlockRate(allocation, 20e18); + + msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(allocation, 20e18); + + msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); + callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); + } + + function test_RevertIf_EveryUpdateAmendmentFunctionVesting() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(allocation, true); + + msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + callData = abi.encodeWithSelector(msgSig, allocation, 0); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.removeMetavestMilestone(allocation, 0); + + msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestUnlockRate(allocation, 20e18); + + msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(allocation, 20e18); + + msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); + callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); + } +} diff --git a/test/controller.t.sol b/test/controller.t.sol new file mode 100644 index 0000000..8dcfbf1 --- /dev/null +++ b/test/controller.t.sol @@ -0,0 +1,2439 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import "../src/RestrictedTokenAllocation.sol"; +import "../src/RestrictedTokenFactory.sol"; +import "../src/TokenOptionAllocation.sol"; +import "../src/TokenOptionFactory.sol"; +import "../src/VestingAllocation.sol"; +import "../src/VestingAllocationFactory.sol"; +import "../src/interfaces/IAllocationFactory.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "../src/metavestController.sol"; +import "./mocks/MockCondition.sol"; +import "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; + +abstract contract ERC20 { + + + /// @dev The total supply has overflowed. + error TotalSupplyOverflow(); + + /// @dev The allowance has overflowed. + error AllowanceOverflow(); + + /// @dev The allowance has underflowed. + error AllowanceUnderflow(); + + /// @dev Insufficient balance. + error InsufficientBalance(); + + /// @dev Insufficient allowance. + error InsufficientAllowance(); + + /// @dev The permit is invalid. + error InvalidPermit(); + + /// @dev The permit has expired. + error PermitExpired(); + + + /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. + event Transfer(address indexed from, address indexed to, uint256 amount); + + /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. + uint256 private constant _TRANSFER_EVENT_SIGNATURE = + 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; + + /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. + uint256 private constant _APPROVAL_EVENT_SIGNATURE = + 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; + + + /// @dev The storage slot for the total supply. + uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; + + /// @dev The balance slot of `owner` is given by: + /// ``` + /// mstore(0x0c, _BALANCE_SLOT_SEED) + /// mstore(0x00, owner) + /// let balanceSlot := keccak256(0x0c, 0x20) + /// ``` + uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; + + /// @dev The allowance slot of (`owner`, `spender`) is given by: + /// ``` + /// mstore(0x20, spender) + /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) + /// mstore(0x00, owner) + /// let allowanceSlot := keccak256(0x0c, 0x34) + /// ``` + uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; + + /// @dev The nonce slot of `owner` is given by: + /// ``` + /// mstore(0x0c, _NONCES_SLOT_SEED) + /// mstore(0x00, owner) + /// let nonceSlot := keccak256(0x0c, 0x20) + /// ``` + uint256 private constant _NONCES_SLOT_SEED = 0x38377508; + + + /// @dev Returns the name of the token. + function name() public view virtual returns (string memory); + + /// @dev Returns the symbol of the token. + function symbol() public view virtual returns (string memory); + + /// @dev Returns the decimals places of the token. + function decimals() public view virtual returns (uint8) { + return 18; + } + + + /// @dev Returns the amount of tokens in existence. + function totalSupply() public view virtual returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := sload(_TOTAL_SUPPLY_SLOT) + } + } + + /// @dev Returns the amount of tokens owned by `owner`. + function balanceOf(address owner) public view virtual returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, owner) + result := sload(keccak256(0x0c, 0x20)) + } + } + + /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. + function allowance(address owner, address spender) + public + view + virtual + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, owner) + result := sload(keccak256(0x0c, 0x34)) + } + } + + /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + /// + /// Emits a {Approval} event. + function approve(address spender, uint256 amount) public virtual returns (bool) { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and store the amount. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, caller()) + sstore(keccak256(0x0c, 0x34), amount) + // Emit the {Approval} event. + mstore(0x00, amount) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) + } + return true; + } + + /// @dev Atomically increases the allowance granted to `spender` by the caller. + /// + /// Emits a {Approval} event. + function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and load its value. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, caller()) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowanceBefore := sload(allowanceSlot) + // Add to the allowance. + let allowanceAfter := add(allowanceBefore, difference) + // Revert upon overflow. + if lt(allowanceAfter, allowanceBefore) { + mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. + revert(0x1c, 0x04) + } + // Store the updated allowance. + sstore(allowanceSlot, allowanceAfter) + // Emit the {Approval} event. + mstore(0x00, allowanceAfter) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) + } + return true; + } + + /// @dev Atomically decreases the allowance granted to `spender` by the caller. + /// + /// Emits a {Approval} event. + function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and load its value. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, caller()) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowanceBefore := sload(allowanceSlot) + // Revert if will underflow. + if lt(allowanceBefore, difference) { + mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated allowance. + let allowanceAfter := sub(allowanceBefore, difference) + sstore(allowanceSlot, allowanceAfter) + // Emit the {Approval} event. + mstore(0x00, allowanceAfter) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) + } + return true; + } + + /// @dev Transfer `amount` tokens from the caller to `to`. + /// + /// Requirements: + /// - `from` must at least have `amount`. + /// + /// Emits a {Transfer} event. + function transfer(address to, uint256 amount) public virtual returns (bool) { + _beforeTokenTransfer(msg.sender, to, amount); + /// @solidity memory-safe-assembly + assembly { + // Compute the balance slot and load its value. + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, caller()) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Compute the balance slot of `to`. + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance of `to`. + // Will not overflow because the sum of all user balances + // cannot exceed the maximum uint256 value. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) + } + _afterTokenTransfer(msg.sender, to, amount); + return true; + } + + /// @dev Transfers `amount` tokens from `from` to `to`. + /// + /// Note: Does not update the allowance if it is the maximum uint256 value. + /// + /// Requirements: + /// - `from` must at least have `amount`. + /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. + /// + /// Emits a {Transfer} event. + function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { + _beforeTokenTransfer(from, to, amount); + /// @solidity memory-safe-assembly + assembly { + let from_ := shl(96, from) + // Compute the allowance slot and load its value. + mstore(0x20, caller()) + mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowance_ := sload(allowanceSlot) + // If the allowance is not the maximum uint256 value. + if iszero(eq(allowance_, not(0))) { + // Revert if the amount to be transferred exceeds the allowance. + if gt(amount, allowance_) { + mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated allowance. + sstore(allowanceSlot, sub(allowance_, amount)) + } + // Compute the balance slot and load its value. + mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Compute the balance slot of `to`. + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance of `to`. + // Will not overflow because the sum of all user balances + // cannot exceed the maximum uint256 value. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) + } + _afterTokenTransfer(from, to, amount); + return true; + } + + + /// @dev Returns the current nonce for `owner`. + /// This value is used to compute the signature for EIP-2612 permit. + function nonces(address owner) public view virtual returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + // Compute the nonce slot and load its value. + mstore(0x0c, _NONCES_SLOT_SEED) + mstore(0x00, owner) + result := sload(keccak256(0x0c, 0x20)) + } + } + + /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, + /// authorized by a signed approval by `owner`. + /// + /// Emits a {Approval} event. + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + bytes32 domainSeparator = DOMAIN_SEPARATOR(); + /// @solidity memory-safe-assembly + assembly { + // Grab the free memory pointer. + let m := mload(0x40) + // Revert if the block timestamp greater than `deadline`. + if gt(timestamp(), deadline) { + mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. + revert(0x1c, 0x04) + } + // Clean the upper 96 bits. + owner := shr(96, shl(96, owner)) + spender := shr(96, shl(96, spender)) + // Compute the nonce slot and load its value. + mstore(0x0c, _NONCES_SLOT_SEED) + mstore(0x00, owner) + let nonceSlot := keccak256(0x0c, 0x20) + let nonceValue := sload(nonceSlot) + // Increment and store the updated nonce. + sstore(nonceSlot, add(nonceValue, 1)) + // Prepare the inner hash. + // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. + // forgefmt: disable-next-item + mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) + mstore(add(m, 0x20), owner) + mstore(add(m, 0x40), spender) + mstore(add(m, 0x60), value) + mstore(add(m, 0x80), nonceValue) + mstore(add(m, 0xa0), deadline) + // Prepare the outer hash. + mstore(0, 0x1901) + mstore(0x20, domainSeparator) + mstore(0x40, keccak256(m, 0xc0)) + // Prepare the ecrecover calldata. + mstore(0, keccak256(0x1e, 0x42)) + mstore(0x20, and(0xff, v)) + mstore(0x40, r) + mstore(0x60, s) + pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) + // If the ecrecover fails, the returndatasize will be 0x00, + // `owner` will be be checked if it equals the hash at 0x00, + // which evaluates to false (i.e. 0), and we will revert. + // If the ecrecover succeeds, the returndatasize will be 0x20, + // `owner` will be compared against the returned address at 0x20. + if iszero(eq(mload(returndatasize()), owner)) { + mstore(0x00, 0xddafbaef) // `InvalidPermit()`. + revert(0x1c, 0x04) + } + // Compute the allowance slot and store the value. + // The `owner` is already at slot 0x20. + mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) + sstore(keccak256(0x2c, 0x34), value) + // Emit the {Approval} event. + log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero pointer. + } + } + + /// @dev Returns the EIP-2612 domains separator. + function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) // Grab the free memory pointer. + } + // We simply calculate it on-the-fly to allow for cases where the `name` may change. + bytes32 nameHash = keccak256(bytes(name())); + /// @solidity memory-safe-assembly + assembly { + let m := result + // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. + // forgefmt: disable-next-item + mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) + mstore(add(m, 0x20), nameHash) + // `keccak256("1")`. + // forgefmt: disable-next-item + mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + result := keccak256(m, 0xa0) + } + } + + + /// @dev Mints `amount` tokens to `to`, increasing the total supply. + /// + /// Emits a {Transfer} event. + function _mint(address to, uint256 amount) internal virtual { + _beforeTokenTransfer(address(0), to, amount); + /// @solidity memory-safe-assembly + assembly { + let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) + let totalSupplyAfter := add(totalSupplyBefore, amount) + // Revert if the total supply overflows. + if lt(totalSupplyAfter, totalSupplyBefore) { + mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. + revert(0x1c, 0x04) + } + // Store the updated total supply. + sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) + // Compute the balance slot and load its value. + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) + } + _afterTokenTransfer(address(0), to, amount); + } + + /// @dev Burns `amount` tokens from `from`, reducing the total supply. + /// + /// Emits a {Transfer} event. + function _burn(address from, uint256 amount) internal virtual { + _beforeTokenTransfer(from, address(0), amount); + /// @solidity memory-safe-assembly + assembly { + // Compute the balance slot and load its value. + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, from) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Subtract and store the updated total supply. + sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) + // Emit the {Transfer} event. + mstore(0x00, amount) + log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) + } + _afterTokenTransfer(from, address(0), amount); + } + + /// @dev Moves `amount` of tokens from `from` to `to`. + function _transfer(address from, address to, uint256 amount) internal virtual { + _beforeTokenTransfer(from, to, amount); + /// @solidity memory-safe-assembly + assembly { + let from_ := shl(96, from) + // Compute the balance slot and load its value. + mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Compute the balance slot of `to`. + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance of `to`. + // Will not overflow because the sum of all user balances + // cannot exceed the maximum uint256 value. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) + } + _afterTokenTransfer(from, to, amount); + } + + + /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and load its value. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, owner) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowance_ := sload(allowanceSlot) + // If the allowance is not the maximum uint256 value. + if iszero(eq(allowance_, not(0))) { + // Revert if the amount to be transferred exceeds the allowance. + if gt(amount, allowance_) { + mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated allowance. + sstore(allowanceSlot, sub(allowance_, amount)) + } + } + } + + /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. + /// + /// Emits a {Approval} event. + function _approve(address owner, address spender, uint256 amount) internal virtual { + /// @solidity memory-safe-assembly + assembly { + let owner_ := shl(96, owner) + // Compute the allowance slot and store the amount. + mstore(0x20, spender) + mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) + sstore(keccak256(0x0c, 0x34), amount) + // Emit the {Approval} event. + mstore(0x00, amount) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) + } + } + + + /// @dev Hook that is called before any transfer of tokens. + /// This includes minting and burning. + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} + + /// @dev Hook that is called after any transfer of tokens. + /// This includes minting and burning. + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} +} + +abstract contract ERC20Stable { + + /// @dev The total supply has overflowed. + error TotalSupplyOverflow(); + + /// @dev The allowance has overflowed. + error AllowanceOverflow(); + + /// @dev The allowance has underflowed. + error AllowanceUnderflow(); + + /// @dev Insufficient balance. + error InsufficientBalance(); + + /// @dev Insufficient allowance. + error InsufficientAllowance(); + + /// @dev The permit is invalid. + error InvalidPermit(); + + /// @dev The permit has expired. + error PermitExpired(); + + + /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. + event Transfer(address indexed from, address indexed to, uint256 amount); + + /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. + uint256 private constant _TRANSFER_EVENT_SIGNATURE = + 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; + + /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. + uint256 private constant _APPROVAL_EVENT_SIGNATURE = + 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; + + /// @dev The storage slot for the total supply. + uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; + + /// @dev The balance slot of `owner` is given by: + /// ``` + /// mstore(0x0c, _BALANCE_SLOT_SEED) + /// mstore(0x00, owner) + /// let balanceSlot := keccak256(0x0c, 0x20) + /// ``` + uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; + + /// @dev The allowance slot of (`owner`, `spender`) is given by: + /// ``` + /// mstore(0x20, spender) + /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) + /// mstore(0x00, owner) + /// let allowanceSlot := keccak256(0x0c, 0x34) + /// ``` + uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; + + /// @dev The nonce slot of `owner` is given by: + /// ``` + /// mstore(0x0c, _NONCES_SLOT_SEED) + /// mstore(0x00, owner) + /// let nonceSlot := keccak256(0x0c, 0x20) + /// ``` + uint256 private constant _NONCES_SLOT_SEED = 0x38377508; + + /// @dev Returns the name of the token. + function name() public view virtual returns (string memory); + + /// @dev Returns the symbol of the token. + function symbol() public view virtual returns (string memory); + + /// @dev Returns the decimals places of the token. + function decimals() public view virtual returns (uint8) { + return 6; + } + + /// @dev Returns the amount of tokens in existence. + function totalSupply() public view virtual returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := sload(_TOTAL_SUPPLY_SLOT) + } + } + + /// @dev Returns the amount of tokens owned by `owner`. + function balanceOf(address owner) public view virtual returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, owner) + result := sload(keccak256(0x0c, 0x20)) + } + } + + /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. + function allowance(address owner, address spender) + public + view + virtual + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, owner) + result := sload(keccak256(0x0c, 0x34)) + } + } + + /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + /// + /// Emits a {Approval} event. + function approve(address spender, uint256 amount) public virtual returns (bool) { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and store the amount. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, caller()) + sstore(keccak256(0x0c, 0x34), amount) + // Emit the {Approval} event. + mstore(0x00, amount) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) + } + return true; + } + + /// @dev Atomically increases the allowance granted to `spender` by the caller. + /// + /// Emits a {Approval} event. + function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and load its value. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, caller()) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowanceBefore := sload(allowanceSlot) + // Add to the allowance. + let allowanceAfter := add(allowanceBefore, difference) + // Revert upon overflow. + if lt(allowanceAfter, allowanceBefore) { + mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. + revert(0x1c, 0x04) + } + // Store the updated allowance. + sstore(allowanceSlot, allowanceAfter) + // Emit the {Approval} event. + mstore(0x00, allowanceAfter) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) + } + return true; + } + + /// @dev Atomically decreases the allowance granted to `spender` by the caller. + /// + /// Emits a {Approval} event. + function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and load its value. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, caller()) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowanceBefore := sload(allowanceSlot) + // Revert if will underflow. + if lt(allowanceBefore, difference) { + mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated allowance. + let allowanceAfter := sub(allowanceBefore, difference) + sstore(allowanceSlot, allowanceAfter) + // Emit the {Approval} event. + mstore(0x00, allowanceAfter) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) + } + return true; + } + + /// @dev Transfer `amount` tokens from the caller to `to`. + /// + /// Requirements: + /// - `from` must at least have `amount`. + /// + /// Emits a {Transfer} event. + function transfer(address to, uint256 amount) public virtual returns (bool) { + _beforeTokenTransfer(msg.sender, to, amount); + /// @solidity memory-safe-assembly + assembly { + // Compute the balance slot and load its value. + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, caller()) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Compute the balance slot of `to`. + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance of `to`. + // Will not overflow because the sum of all user balances + // cannot exceed the maximum uint256 value. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) + } + _afterTokenTransfer(msg.sender, to, amount); + return true; + } + + /// @dev Transfers `amount` tokens from `from` to `to`. + /// + /// Note: Does not update the allowance if it is the maximum uint256 value. + /// + /// Requirements: + /// - `from` must at least have `amount`. + /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. + /// + /// Emits a {Transfer} event. + function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { + _beforeTokenTransfer(from, to, amount); + /// @solidity memory-safe-assembly + assembly { + let from_ := shl(96, from) + // Compute the allowance slot and load its value. + mstore(0x20, caller()) + mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowance_ := sload(allowanceSlot) + // If the allowance is not the maximum uint256 value. + if iszero(eq(allowance_, not(0))) { + // Revert if the amount to be transferred exceeds the allowance. + if gt(amount, allowance_) { + mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated allowance. + sstore(allowanceSlot, sub(allowance_, amount)) + } + // Compute the balance slot and load its value. + mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Compute the balance slot of `to`. + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance of `to`. + // Will not overflow because the sum of all user balances + // cannot exceed the maximum uint256 value. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) + } + _afterTokenTransfer(from, to, amount); + return true; + } + + + /// @dev Returns the current nonce for `owner`. + /// This value is used to compute the signature for EIP-2612 permit. + function nonces(address owner) public view virtual returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + // Compute the nonce slot and load its value. + mstore(0x0c, _NONCES_SLOT_SEED) + mstore(0x00, owner) + result := sload(keccak256(0x0c, 0x20)) + } + } + + /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, + /// authorized by a signed approval by `owner`. + /// + /// Emits a {Approval} event. + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + bytes32 domainSeparator = DOMAIN_SEPARATOR(); + /// @solidity memory-safe-assembly + assembly { + // Grab the free memory pointer. + let m := mload(0x40) + // Revert if the block timestamp greater than `deadline`. + if gt(timestamp(), deadline) { + mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. + revert(0x1c, 0x04) + } + // Clean the upper 96 bits. + owner := shr(96, shl(96, owner)) + spender := shr(96, shl(96, spender)) + // Compute the nonce slot and load its value. + mstore(0x0c, _NONCES_SLOT_SEED) + mstore(0x00, owner) + let nonceSlot := keccak256(0x0c, 0x20) + let nonceValue := sload(nonceSlot) + // Increment and store the updated nonce. + sstore(nonceSlot, add(nonceValue, 1)) + // Prepare the inner hash. + // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. + // forgefmt: disable-next-item + mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) + mstore(add(m, 0x20), owner) + mstore(add(m, 0x40), spender) + mstore(add(m, 0x60), value) + mstore(add(m, 0x80), nonceValue) + mstore(add(m, 0xa0), deadline) + // Prepare the outer hash. + mstore(0, 0x1901) + mstore(0x20, domainSeparator) + mstore(0x40, keccak256(m, 0xc0)) + // Prepare the ecrecover calldata. + mstore(0, keccak256(0x1e, 0x42)) + mstore(0x20, and(0xff, v)) + mstore(0x40, r) + mstore(0x60, s) + pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) + // If the ecrecover fails, the returndatasize will be 0x00, + // `owner` will be be checked if it equals the hash at 0x00, + // which evaluates to false (i.e. 0), and we will revert. + // If the ecrecover succeeds, the returndatasize will be 0x20, + // `owner` will be compared against the returned address at 0x20. + if iszero(eq(mload(returndatasize()), owner)) { + mstore(0x00, 0xddafbaef) // `InvalidPermit()`. + revert(0x1c, 0x04) + } + // Compute the allowance slot and store the value. + // The `owner` is already at slot 0x20. + mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) + sstore(keccak256(0x2c, 0x34), value) + // Emit the {Approval} event. + log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero pointer. + } + } + + /// @dev Returns the EIP-2612 domains separator. + function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) // Grab the free memory pointer. + } + // We simply calculate it on-the-fly to allow for cases where the `name` may change. + bytes32 nameHash = keccak256(bytes(name())); + /// @solidity memory-safe-assembly + assembly { + let m := result + // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. + // forgefmt: disable-next-item + mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) + mstore(add(m, 0x20), nameHash) + // `keccak256("1")`. + // forgefmt: disable-next-item + mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + result := keccak256(m, 0xa0) + } + } + + /// @dev Mints `amount` tokens to `to`, increasing the total supply. + /// + /// Emits a {Transfer} event. + function _mint(address to, uint256 amount) internal virtual { + _beforeTokenTransfer(address(0), to, amount); + /// @solidity memory-safe-assembly + assembly { + let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) + let totalSupplyAfter := add(totalSupplyBefore, amount) + // Revert if the total supply overflows. + if lt(totalSupplyAfter, totalSupplyBefore) { + mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. + revert(0x1c, 0x04) + } + // Store the updated total supply. + sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) + // Compute the balance slot and load its value. + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) + } + _afterTokenTransfer(address(0), to, amount); + } + + /// @dev Burns `amount` tokens from `from`, reducing the total supply. + /// + /// Emits a {Transfer} event. + function _burn(address from, uint256 amount) internal virtual { + _beforeTokenTransfer(from, address(0), amount); + /// @solidity memory-safe-assembly + assembly { + // Compute the balance slot and load its value. + mstore(0x0c, _BALANCE_SLOT_SEED) + mstore(0x00, from) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Subtract and store the updated total supply. + sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) + // Emit the {Transfer} event. + mstore(0x00, amount) + log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) + } + _afterTokenTransfer(from, address(0), amount); + } + + + /// @dev Moves `amount` of tokens from `from` to `to`. + function _transfer(address from, address to, uint256 amount) internal virtual { + _beforeTokenTransfer(from, to, amount); + /// @solidity memory-safe-assembly + assembly { + let from_ := shl(96, from) + // Compute the balance slot and load its value. + mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) + let fromBalanceSlot := keccak256(0x0c, 0x20) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Compute the balance slot of `to`. + mstore(0x00, to) + let toBalanceSlot := keccak256(0x0c, 0x20) + // Add and store the updated balance of `to`. + // Will not overflow because the sum of all user balances + // cannot exceed the maximum uint256 value. + sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) + // Emit the {Transfer} event. + mstore(0x20, amount) + log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) + } + _afterTokenTransfer(from, to, amount); + } + + + /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + /// @solidity memory-safe-assembly + assembly { + // Compute the allowance slot and load its value. + mstore(0x20, spender) + mstore(0x0c, _ALLOWANCE_SLOT_SEED) + mstore(0x00, owner) + let allowanceSlot := keccak256(0x0c, 0x34) + let allowance_ := sload(allowanceSlot) + // If the allowance is not the maximum uint256 value. + if iszero(eq(allowance_, not(0))) { + // Revert if the amount to be transferred exceeds the allowance. + if gt(amount, allowance_) { + mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated allowance. + sstore(allowanceSlot, sub(allowance_, amount)) + } + } + } + + /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. + /// + /// Emits a {Approval} event. + function _approve(address owner, address spender, uint256 amount) internal virtual { + /// @solidity memory-safe-assembly + assembly { + let owner_ := shl(96, owner) + // Compute the allowance slot and store the amount. + mstore(0x20, spender) + mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) + sstore(keccak256(0x0c, 0x34), amount) + // Emit the {Approval} event. + mstore(0x00, amount) + log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) + } + } + + /// @dev Hook that is called before any transfer of tokens. + /// This includes minting and burning. + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} + + /// @dev Hook that is called after any transfer of tokens. + /// This includes minting and burning. + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} +} + +contract MockERC20 is ERC20 { + constructor(string memory name, string memory symbol) ERC20() {} + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + function name() public view override returns (string memory) { + return "Test Token"; + } + function symbol() public view override returns (string memory) { + return "TT"; + } +} + +contract MockERC20Stable is ERC20Stable { + constructor(string memory name, string memory symbol) ERC20Stable() {} + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + function name() public view override returns (string memory) { + return "Test Token"; + } + function symbol() public view override returns (string memory) { + return "TT"; + } +} + +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract MetaVestControllerTest is Test { + // zkSync Era Sepolia @ 5576300 + address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; + IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); + IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); +// // zkSync Era mainnet +// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; +// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); +// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); + + IZkCappedMinterV2 zkCappedMinter; + + metavestController public controller; + MockERC20Stable public paymentToken; + + address public authority; + address public dao; + address public grantee; + address public transferee; + + bytes32 salt = keccak256("MetaVesTFactoryTest"); + + function setUp() public { + authority = address(this); + dao = address(0x2); + grantee = address(0x3); + transferee = address(0x4); + + paymentToken = new MockERC20Stable("Payment Token", "PT"); + + VestingAllocationFactory factory = new VestingAllocationFactory(); + TokenOptionFactory tokenFactory = new TokenOptionFactory(); + RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); + + controller = new metavestController( + authority, + dao, + address(factory), + address(tokenFactory), + address(restrictedTokenFactory) + ); + + // Deploy ZK Capped Minter v2 + zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + 9999e18, + uint48(block.timestamp), // start now + uint48(block.timestamp + 365 days * 2), + uint256(salt) + )); + + vm.startPrank(authority); + controller.setZkCappedMinter(address(zkCappedMinter)); + controller.createSet("testSet"); + vm.stopPrank(); + + // Simulate TPP approval and ZK Token admin to grant our ZkCappedMinter access + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + } + + function testCreateVestingAllocation() public { + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10e18, + unlockStartTime: uint48(block.timestamp) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 100e18, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + 0, + address(0), + 0, + 0 + + )); + + assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); + assertEq(vestingAllocation.getAmountWithdrawable(), 100e18, "Should be able to withdraw cliff amount"); + } + +// function testCreateTokenOptionAllocation() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// address tokenOptionAllocation = controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// ); +// +// assertEq(zkToken.balanceOf(address(tokenOptionAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// //assertEq(controller.tokenOptionAllocations(grantee, 0), tokenOptionAllocation); +// } + +// function testCreateRestrictedTokenAward() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// address restrictedTokenAward = controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// +// ); +// +// assertEq(zkToken.balanceOf(address(restrictedTokenAward)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); +// } + + function testUpdateTransferability() public { + uint256 startTimestamp = block.timestamp; + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + //compute msg.data for updateMetavestTransferability(vestingAllocation, true) + bytes4 selector = controller.updateMetavestTransferability.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, true); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, true); + + controller.updateMetavestTransferability(vestingAllocation, true); + vm.prank(grantee); + RestrictedTokenAward(vestingAllocation).transferRights(transferee); + vm.prank(transferee); + RestrictedTokenAward(vestingAllocation).confirmTransfer(); + uint256 newTimestamp = startTimestamp + 100; // 101 + vm.warp(newTimestamp); + skip(10); + vm.prank(transferee); + uint256 balance = RestrictedTokenAward(vestingAllocation).getAmountWithdrawable(); + + + //warp ahead 100 blocks + + vm.prank(transferee); + RestrictedTokenAward(vestingAllocation).withdraw(balance); + + // assertTrue(BaseAllocation(vestingAllocation).transferable()); + } + + function testGetGovPower() public { + address vestingAllocation = createDummyVestingAllocation(); + BaseAllocation(vestingAllocation).getGoverningPower(); + } + + function testProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true); + } + + + function test_RevertIf_ReProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + vm.warp(block.timestamp + 30 days); + /* + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true);*/ + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + } + + function testReProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + vm.warp(block.timestamp + 30 days); + + vm.prank(authority); + controller.cancelExpiredMajorityMetavestAmendment("testSet", msgSig); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + } + + function test_RevertIf_RemoveNonExistantMetaVestFromSet() public { + address mockAllocation2 = createDummyVestingAllocation(); + vm.startPrank(authority); + // controller.createSet("testSet"); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_MetaVestNotInSet.selector)); + controller.removeMetaVestFromSet("testSet", mockAllocation2); + } + + +// function testUpdateExercisePrice() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// +// //compute msg.data for updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18) +// bytes4 selector = controller.updateExerciseOrRepurchasePrice.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, tokenOptionAllocation, 2e18); +// +// controller.proposeMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, msgData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, true); +// +// controller.updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18); +// +// assertEq(TokenOptionAllocation(tokenOptionAllocation).exercisePrice(), 2e18); +// } + + function testRemoveMilestone() public { + address vestingAllocation = createDummyVestingAllocation(); + //create array of addresses and include vestingAllocation address + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + vm.prank(grantee); + //consent to amendment for the removemetavestmilestone method sig function consentToMetavestAmendment(address _metavest, bytes4 _msgSig, bool _inFavor) external { + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + controller.removeMetavestMilestone(vestingAllocation, 0); + + //BaseAllocation.Milestone memory milestone = BaseAllocation(vestingAllocation).milestones(0); + //assertEq(milestone.milestoneAward, 0); + } + + function testAddMilestone() public { + address vestingAllocation = createDummyVestingAllocation(); + + BaseAllocation.Milestone memory newMilestone = BaseAllocation.Milestone({ + milestoneAward: 50e18, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + controller.addMetavestMilestone(vestingAllocation, newMilestone); + + // BaseAllocation.Milestone memory addedMilestone = BaseAllocation(vestingAllocation).milestones[0]; + // assertEq(addedMilestone.milestoneAward, 50e18); + } + + function testUpdateUnlockRate() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + + controller.updateMetavestUnlockRate(vestingAllocation, 20e18); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 20e18); + } + + function testUpdateUnlockRateZeroEmergency() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + + controller.updateMetavestUnlockRate(vestingAllocation, 0); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 0); + + controller.terminateMetavestVesting(vestingAllocation); + + controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); + updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 1e20); + } + + function test_RevertIf_UpdateUnlockRateZeroEmergency() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + + controller.terminateMetavestVesting(vestingAllocation); + + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); + controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 10e18); + } + + function test_RevertIf_UpdateUnlockRateZeroEmergencyTerminated() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + + controller.updateMetavestUnlockRate(vestingAllocation, 0); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 0); + + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); + controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); + updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 0); + } + + function testUpdateVestingRate() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestVestingRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, true); + + controller.updateMetavestVestingRate(vestingAllocation, 20e18); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.vestingRate, 20e18); + } + +// function testUpdateStopTimes() public { +// +// address vestingAllocation = createDummyRestrictedTokenAward(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, uint48(block.timestamp + 500 days)); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, msgData); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, true); +// uint48 newShortStopTime = uint48(block.timestamp + 500 days); +// +// controller.updateMetavestStopTimes(vestingAllocation, newShortStopTime); +// } + + function testTerminateVesting() public { + address vestingAllocation = createDummyVestingAllocation(); + + controller.terminateMetavestVesting(vestingAllocation); + + assertTrue(BaseAllocation(vestingAllocation).terminated()); + } + +// function testRepurchaseTokens() public { +// uint256 startingBalance = paymentToken.balanceOf(grantee); +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 repurchaseAmount = 5e18; +// uint256 snapshot = token.balanceOf(authority); +// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); +// controller.terminateMetavestVesting(restrictedTokenAward); +// paymentToken.approve(address(restrictedTokenAward), payment); +// vm.warp(block.timestamp + 20 days); +// vm.prank(authority); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +// +// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +// +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +// } + +// function testRepurchaseTokensFuture() public { +// uint256 startingBalance = paymentToken.balanceOf(grantee); +// address restrictedTokenAward = createDummyRestrictedTokenAwardFuture(); +// +// uint256 snapshot = token.balanceOf(authority); +// +// controller.terminateMetavestVesting(restrictedTokenAward); +// uint256 repurchaseAmount = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); +// paymentToken.approve(address(restrictedTokenAward), payment); +// vm.warp(block.timestamp + 20 days); +// vm.prank(authority); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +// +// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +// +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); +// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +// +// } + + function testTerminateTokensFuture() public { + uint256 startingBalance = paymentToken.balanceOf(grantee); + address vestingAllocation = createDummyVestingAllocationLargeFuture(); + + controller.terminateMetavestVesting(vestingAllocation); + } + + function testUpdateAuthority() public { + address newAuthority = address(0x4); + + controller.initiateAuthorityUpdate(newAuthority); + + vm.prank(newAuthority); + controller.acceptAuthorityRole(); + + assertEq(controller.authority(), newAuthority); + } + + function testUpdateDao() public { + address newDao = address(0x5); + + vm.prank(dao); + controller.initiateDaoUpdate(newDao); + + vm.prank(newDao); + controller.acceptDaoRole(); + + assertEq(controller.dao(), newDao); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation() internal returns (address) { + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10e18, + unlockStartTime: uint48(block.timestamp) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000e18, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + return controller.createMetavest( + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + 0, + address(0), + 0, + 0 + + ); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationNoUnlock() internal returns (address) { + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10e18, + unlockStartTime: uint48(block.timestamp) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000e18, + unlockOnCompletion: false, + complete: false, + conditionContracts: new address[](0) + }); + + return controller.createMetavest( + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + 0, + address(0), + 0, + 0 + + ); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationSlowUnlock() internal returns (address) { + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp), + unlockRate: 5e18, + unlockStartTime: uint48(block.timestamp) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000e18, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + return controller.createMetavest( + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + 0, + address(0), + 0, + 0 + + ); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationLarge() internal returns (address) { + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 0, + unlockingCliffCredit: 0, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10e18, + unlockStartTime: uint48(block.timestamp) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + + return controller.createMetavest( + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + 0, + address(0), + 0, + 0 + + ); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationLargeFuture() internal returns (address) { + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 0, + unlockingCliffCredit: 0, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp+2000), + unlockRate: 10e18, + unlockStartTime: uint48(block.timestamp+2000) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + + return controller.createMetavest( + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + 0, + address(0), + 0, + 0 + + ); + } + +// function createDummyTokenOptionAllocation() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 1000e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 2000e18); +// +// return controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 5e17, +// address(paymentToken), +// 1 days, +// 0 +// ); +// } + + +// function createDummyRestrictedTokenAward() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 1000e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 2100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 1 days, +// 0 +// +// ); +// } +// +// function createDummyRestrictedTokenAwardFuture() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp+1000), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp+1000) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 1000e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 2100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 1 days, +// 0 +// +// ); +// } + + + function testGetMetaVestType() public { + address vestingAllocation = createDummyVestingAllocation(); +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// address restrictedTokenAward = createDummyRestrictedTokenAward(); + + assertEq(controller.getMetaVestType(vestingAllocation), 1); +// assertEq(controller.getMetaVestType(tokenOptionAllocation), 2); +// assertEq(controller.getMetaVestType(restrictedTokenAward), 3); + } + +// function testWithdrawFromController() public { +// uint256 amount = 100e18; +// token.transfer(address(controller), amount); +// +// uint256 initialBalance = token.balanceOf(authority); +// controller.withdrawFromController(address(token)); +// uint256 finalBalance = token.balanceOf(authority); +// +// assertEq(finalBalance - initialBalance, amount); +// assertEq(token.balanceOf(address(controller)), 0); +// } + + function test_RevertIf_CreateMetavestWithZeroAddress() public { + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(0), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10e18, + unlockStartTime: uint48(block.timestamp) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ZeroAddress.selector)); + controller.createMetavest( + metavestController.metavestType.Vesting, + address(0), + allocation, + milestones, + 0, + address(0), + 0, + 0 + + ); + } + + function test_RevertIf_CreateMetavestWithInsufficientApproval() public { + BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10e18, + unlockStartTime: uint48(block.timestamp) + }); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + // Not approving any tokens + controller.createMetavest( + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + 0, + address(0), + 0, + 0 + + ); + } + + function testTerminateVestAndRecovers() public { + address vestingAllocation = createDummyVestingAllocation(); + uint256 snapshot = zkToken.balanceOf(authority); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.warp(block.timestamp + 50 seconds); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(authority), 0); + } + + function testTerminateVestAndRecoverSlowUnlock() public { + address vestingAllocation = createDummyVestingAllocationSlowUnlock(); + uint256 snapshot = zkToken.balanceOf(authority); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.warp(block.timestamp + 25 seconds); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.warp(block.timestamp + 25 seconds); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(vestingAllocation), 0); + } + + function testTerminateRecoverAll() public { + address vestingAllocation = createDummyVestingAllocationLarge(); + uint256 snapshot = zkToken.balanceOf(authority); + vm.warp(block.timestamp + 25 seconds); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(authority), 0); + } + + function testTerminateRecoverChunksBefore() public { + address vestingAllocation = createDummyVestingAllocationLarge(); + uint256 snapshot = zkToken.balanceOf(authority); + vm.warp(block.timestamp + 25 seconds); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + vm.warp(block.timestamp + 25 seconds); + + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(authority), 0); + } + +// function testConfirmingMilestoneRestrictedTokenAllocation() public { +// address vestingAllocation = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// RestrictedTokenAward(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } +// +// function testConfirmingMilestoneTokenOption() public { +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } + + function testUnlockMilestoneNotUnlocked() public { + address vestingAllocation = createDummyVestingAllocationNoUnlock(); + uint256 snapshot = zkToken.balanceOf(authority); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.warp(block.timestamp + 50 seconds); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.warp(block.timestamp + 1050 seconds); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + +// function testTerminateTokenOptionAndRecover() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// vm.prank(grantee); +// ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); +// vm.prank(grantee); +// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18); +// controller.terminateMetavestVesting(tokenOptionAllocation); +// vm.startPrank(grantee); +// vm.warp(block.timestamp + 1 days + 25 seconds); +// assertEq(TokenOptionAllocation(tokenOptionAllocation).getAmountExercisable(), 0); +// TokenOptionAllocation(tokenOptionAllocation).withdraw(TokenOptionAllocation(tokenOptionAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// assertEq(token.balanceOf(tokenOptionAllocation), 0); +// vm.warp(block.timestamp + 365 days); +// vm.prank(authority); +// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +// } + +// function testTerminateEarlyTokenOptionAndRecover() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 5 seconds); +// // vm.prank(grantee); +// /* ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); +// vm.prank(grantee); +// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18);*/ +// controller.terminateMetavestVesting(tokenOptionAllocation); +// vm.warp(block.timestamp + 365 days); +// vm.prank(authority); +// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +// } + + +// function testTerminateRestrictedTokenAwardAndRecover() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// vm.warp(block.timestamp + 20 days); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// assertEq(token.balanceOf(restrictedTokenAward), 0); +// assertEq(paymentToken.balanceOf(restrictedTokenAward), 0); +// } + +// function testChangeVestingAndUnlockingRate() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// +// bytes4 msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestUnlockRate(restrictedTokenAward, 50e18); +// +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(restrictedTokenAward, 50e18); +// +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// +// } + +// function testZeroReclaim() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// vm.warp(block.timestamp + 15 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// //create call data to propose setting vesting to 0 +// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(restrictedTokenAward, 0); +// +// vm.startPrank(authority); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.warp(block.timestamp + 155 days); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// vm.stopPrank(); +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); +// } + + function testZeroReclaimVesting() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.warp(block.timestamp + 15 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + //create call data to propose setting vesting to 0 + bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 0); + + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 0); + + vm.startPrank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.stopPrank(); + } + + function testSlightReduc() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + //create call data to propose setting vesting to 0 + bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 80e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 80e18); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.stopPrank(); + vm.warp(block.timestamp + 155 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + + function testLargeReduc() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + //create call data to propose setting vesting to 0 + bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 10e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 10e18); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.stopPrank(); + vm.warp(block.timestamp + 155 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + +// function testLargeReducOption() public { +// address restrictedTokenAward = createDummyTokenOptionAllocation(); +// vm.warp(block.timestamp + 5 seconds); +// vm.startPrank(grantee); +// //approve amount to exercise by getting amount to exercise and price +// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); +// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// //create call data to propose setting vesting to 0 +// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); +// vm.warp(block.timestamp + 5 seconds); +// vm.startPrank(authority); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.stopPrank(); +// vm.warp(block.timestamp + 155 seconds); +// vm.startPrank(grantee); +// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); +// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// console.log(token.balanceOf(restrictedTokenAward)); +// } + + + +// function testReclaim() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// vm.warp(block.timestamp + 15 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// +// vm.startPrank(authority); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.warp(block.timestamp + 155 days); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// vm.stopPrank(); +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); +// } + + + +// function test_RevertIf_UpdateExercisePriceForVesting() public { +// address vestingAllocation = createDummyVestingAllocation(); +// controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); +// } + +// function test_RevertIf_RepurchaseTokensAfterExpiry() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// +// // Fast forward time to after the short stop date +// vm.warp(block.timestamp + 366 days); +// +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +// } + +// function test_RevertIf_RepurchaseTokensInsufficientAllowance() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// +// // Not approving any tokens +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +// } + + function test_RevertIf_InitiateAuthorityUpdateNonAuthority() public { + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); + controller.initiateAuthorityUpdate(address(0x5678)); + } + + function test_RevertIf_AcceptAuthorityRoleNonPendingAuthority() public { + controller.initiateAuthorityUpdate(address(0x5678)); + + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingAuthority.selector)); + controller.acceptAuthorityRole(); + } + + function test_RevertIf_InitiateDaoUpdateNonDao() public { + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); + controller.initiateDaoUpdate(address(0x5678)); + } + + function test_RevertIf_AcceptDaoRoleNonPendingDao() public { + vm.prank(dao); + controller.initiateDaoUpdate(address(0x5678)); + + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingDao.selector)); + controller.acceptDaoRole(); + } + + function testUpdateFunctionCondition() public { + bytes4 functionSig = bytes4(keccak256("testFunction()")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + + assertEq(controller.functionToConditions(functionSig, 0), address(condition)); + } + + function test_RevertIf_UpdateFunctionConditionNonDao() public { + bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); + address condition = address(0x1234); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); + controller.updateFunctionCondition(condition, functionSig); + } + + + function testRemoveFunctionCondition() public { + bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + assert(controller.functionToConditions(functionSig, 0) == address(condition)); + vm.prank(dao); + controller.removeFunctionCondition(address(condition), functionSig); + } + + function test_RevertIf_CheckFunctionCondition() public { + bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + assert(controller.functionToConditions(functionSig, 0) == address(condition)); + //create a dummy metavest + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ConditionNotSatisfied.selector, condition)); + address vestingAllocation = createDummyVestingAllocation(); + } + + function test_RevertIf_AddDuplicateCondition() public { + bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + assert(controller.functionToConditions(functionSig, 0) == address(condition)); + vm.prank(dao); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_DuplicateCondition.selector)); + controller.updateFunctionCondition(address(condition), functionSig); + } +} From 02b9fd58b1398c1c272ed785335afb5c7ba79e68 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 31 Jul 2025 16:49:42 -0700 Subject: [PATCH 09/68] wip: test: disable breaking tests due to upcoming changes --- test/AuditBaseA.t.sol | 76 +- test/AuditBaseA2.t.sol | 358 +++--- test/AuditBaseC.t.sol | 86 +- test/AuditBaseC3.t.sol | 140 +-- test/MetaVesTFactory.t.sol | 214 ++-- test/amendement.t.sol | 1372 +++++++++++----------- test/controller.t.sol | 2278 ++++++++++++++++++------------------ 7 files changed, 2262 insertions(+), 2262 deletions(-) diff --git a/test/AuditBaseA.t.sol b/test/AuditBaseA.t.sol index cd70c04..3bbfde0 100644 --- a/test/AuditBaseA.t.sol +++ b/test/AuditBaseA.t.sol @@ -15,43 +15,43 @@ contract EvilGrant { } contract Audit is MetaVestControllerTest { - function test_RevertIf_AuditArbitraryVote() public { - // template from testVoteOnMetavestAmendment - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - address attacker = address(0x31337); - address evil_grant = address(new EvilGrant()); - - vm.prank(attacker); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("attacker made vote and power is" , currentVotingPower); - } - - function test_RevertIf_AuditRemoveConfirmedMilestone() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); - controller.removeMetavestMilestone(vestingAllocation, 0); - } +// function test_RevertIf_AuditArbitraryVote() public { +// // template from testVoteOnMetavestAmendment +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", address(mockAllocation)); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// address attacker = address(0x31337); +// address evil_grant = address(new EvilGrant()); +// +// vm.prank(attacker); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); +// +// (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); +// console.log("attacker made vote and power is" , currentVotingPower); +// } +// +// function test_RevertIf_AuditRemoveConfirmedMilestone() public { +// // template from testRemoveMilestone +// address vestingAllocation = createDummyVestingAllocation(); +// VestingAllocation(vestingAllocation).confirmMilestone(0); +// +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); +// controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); +// controller.removeMetavestMilestone(vestingAllocation, 0); +// } } \ No newline at end of file diff --git a/test/AuditBaseA2.t.sol b/test/AuditBaseA2.t.sol index b90f160..a150577 100644 --- a/test/AuditBaseA2.t.sol +++ b/test/AuditBaseA2.t.sol @@ -16,201 +16,201 @@ contract EvilGrant { // TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract Audit is MetaVestControllerTest { - function test_RevertIf_AuditArbitraryVote() public { - // template from testVoteOnMetavestAmendment - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - address attacker = address(0x31337); - address evil_grant = address(new EvilGrant()); - - vm.prank(attacker); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("attacker made vote and power is" , currentVotingPower); - } - - function test_RevertIf_AuditRemoveConfirmedMilestone() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); - controller.removeMetavestMilestone(vestingAllocation, 0); - } - - function testAuditProposeMajorityMetavestAmendmentExpire() public { - // template from testProposeMajorityMetavestAmendment - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - // proposal expired - uint256 AMENDMENT_TIME_LIMIT = 604800; - vm.warp(block.timestamp + AMENDMENT_TIME_LIMIT + 1); - - // MetaVesTController_AmendmentAlreadyPending even expired - vm.prank(authority); - vm.expectRevert(); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - } - -// function testAuditModifiedCalldataProposal() public { -// // template from testCreateSetWithThreeTokenOptionsAndChangeExercisePrice -// address allocation1 = createDummyTokenOptionAllocation(); -// address allocation2 = createDummyTokenOptionAllocation(); -// address allocation3 = createDummyTokenOptionAllocation(); +// function test_RevertIf_AuditArbitraryVote() public { +// // template from testVoteOnMetavestAmendment +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", address(mockAllocation)); // // vm.prank(authority); -// controller.addMetaVestToSet("testSet", allocation1); -// controller.addMetaVestToSet("testSet", allocation2); -// controller.addMetaVestToSet("testSet", allocation3); -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); -// vm.warp(block.timestamp + 25 seconds); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); // +// address attacker = address(0x31337); +// address evil_grant = address(new EvilGrant()); // -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(allocation1), 2000e18); -// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// vm.prank(attacker); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); // -// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); +// console.log("attacker made vote and power is" , currentVotingPower); +// } // -// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); -// vm.stopPrank(); -// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// function test_RevertIf_AuditRemoveConfirmedMilestone() public { +// // template from testRemoveMilestone +// address vestingAllocation = createDummyVestingAllocation(); +// VestingAllocation(vestingAllocation).confirmMilestone(0); // -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); +// controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); // // vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); +// controller.removeMetavestMilestone(vestingAllocation, 0); +// } // -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// function testAuditProposeMajorityMetavestAmendmentExpire() public { +// // template from testProposeMajorityMetavestAmendment +// address mockAllocation2 = createDummyVestingAllocation(); +// address mockAllocation3 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); // // vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// vm.warp(block.timestamp + 1 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// // proposal expired +// uint256 AMENDMENT_TIME_LIMIT = 604800; +// vm.warp(block.timestamp + AMENDMENT_TIME_LIMIT + 1); +// +// // MetaVesTController_AmendmentAlreadyPending even expired +// vm.prank(authority); // vm.expectRevert(); -// controller.updateExerciseOrRepurchasePrice(allocation1, 999999999999999999999e18); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// } +// +//// function testAuditModifiedCalldataProposal() public { +//// // template from testCreateSetWithThreeTokenOptionsAndChangeExercisePrice +//// address allocation1 = createDummyTokenOptionAllocation(); +//// address allocation2 = createDummyTokenOptionAllocation(); +//// address allocation3 = createDummyTokenOptionAllocation(); +//// +//// vm.prank(authority); +//// controller.addMetaVestToSet("testSet", allocation1); +//// controller.addMetaVestToSet("testSet", allocation2); +//// controller.addMetaVestToSet("testSet", allocation3); +//// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +//// vm.warp(block.timestamp + 25 seconds); +//// +//// +//// vm.startPrank(grantee); +//// ERC20(paymentToken).approve(address(allocation1), 2000e18); +//// ERC20(paymentToken).approve(address(allocation2), 2000e18); +//// +//// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +//// +//// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +//// vm.stopPrank(); +//// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +//// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +//// +//// vm.prank(authority); +//// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +//// +//// vm.prank(grantee); +//// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +//// +//// vm.prank(authority); +//// vm.expectRevert(); +//// controller.updateExerciseOrRepurchasePrice(allocation1, 999999999999999999999e18); +//// +//// // Bypass MetaVesTController_AmendmentNeitherMutualNorMajorityConsented +//// vm.prank(authority); +//// bytes memory p = abi.encodeWithSelector(msgSig, allocation1, 999999999999999999999e18, 2e18); +//// address(controller).call(p); +//// +//// console.log('Modified excercise price: ', TokenOptionAllocation(allocation1).exercisePrice()); +//// } +// +// function testAuditConsentToMetavestAmendmentInFlavor() public { +// // template from testRemoveMilestone +// address vestingAllocation = createDummyVestingAllocation(); +// VestingAllocation(vestingAllocation).confirmMilestone(0); +// +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); +// controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, false); +// // emit MetaVesTController_AmendmentConsentUpdated(msgSig: 0x75b89e4f00000000000000000000000000000000000000000000000000000000, grantee: ECAdd: [0x0000000000000000000000000000000000000006], inFavor: false) +// console.log("expected inFavor: false"); +// (,,bool inFavor) = controller.functionToGranteeToAmendmentPending(selector, vestingAllocation); +// console.log("output: ", inFavor); +// assertEq(inFavor, false); +// +// } +// +// function test_RevertIf_AuditProposeMajorityMetavestAmendmentNewGranteeDuringProposal() public { +// // template from testProposeMajorityMetavestAmendment +// address mockAllocation2 = createDummyVestingAllocation(); +// address mockAllocation3 = createDummyVestingAllocation(); +// address mockAllocation4 = createDummyVestingAllocation(); +// address mockAllocation5 = createDummyVestingAllocation(); +// address mockAllocation6 = createDummyVestingAllocation(); +// address mockAllocation7 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); // -// // Bypass MetaVesTController_AmendmentNeitherMutualNorMajorityConsented // vm.prank(authority); -// bytes memory p = abi.encodeWithSelector(msgSig, allocation1, 999999999999999999999e18, 2e18); -// address(controller).call(p); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// vm.warp(block.timestamp + 1 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.startPrank(authority); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); +// controller.addMetaVestToSet("testSet", mockAllocation4); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); +// controller.addMetaVestToSet("testSet", mockAllocation5); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); +// controller.addMetaVestToSet("testSet", mockAllocation6); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); +// controller.addMetaVestToSet("testSet", mockAllocation7); +// vm.stopPrank(); +// +// vm.startPrank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(mockAllocation5, "testSet", msgSig, true); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(mockAllocation6, "testSet", msgSig, true); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(mockAllocation7, "testSet", msgSig, true); +// vm.stopPrank(); +// +// (uint256 totalVotingPower, uint256 currentVotingPower,,,) = controller.functionToSetMajorityProposal(msgSig, "testSet"); +// console.log("totalVotingPower: ", totalVotingPower); +// console.log("currentVotingPower: ", currentVotingPower); +// } +// +// function testCreateSetAddVestingThenRemoveSet() public { +// +// // template from testCreateSetAddVestingThenRemoveSet +// address allocation1 = createDummyVestingAllocation(); +// address allocation2 = createDummyVestingAllocation(); +// address allocation3 = createDummyVestingAllocation(); +// +// vm.startPrank(authority); +// controller.createSet("testSetB"); +// controller.addMetaVestToSet("testSetB", allocation1); +// controller.addMetaVestToSet("testSetB", allocation2); +// controller.addMetaVestToSet("testSetB", allocation3); +// controller.removeSet("testSetB"); +// controller.createSet("testSetB"); +// vm.stopPrank(); // -// console.log('Modified excercise price: ', TokenOptionAllocation(allocation1).exercisePrice()); // } - - function testAuditConsentToMetavestAmendmentInFlavor() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, false); - // emit MetaVesTController_AmendmentConsentUpdated(msgSig: 0x75b89e4f00000000000000000000000000000000000000000000000000000000, grantee: ECAdd: [0x0000000000000000000000000000000000000006], inFavor: false) - console.log("expected inFavor: false"); - (,,bool inFavor) = controller.functionToGranteeToAmendmentPending(selector, vestingAllocation); - console.log("output: ", inFavor); - assertEq(inFavor, false); - - } - - function test_RevertIf_AuditProposeMajorityMetavestAmendmentNewGranteeDuringProposal() public { - // template from testProposeMajorityMetavestAmendment - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - address mockAllocation4 = createDummyVestingAllocation(); - address mockAllocation5 = createDummyVestingAllocation(); - address mockAllocation6 = createDummyVestingAllocation(); - address mockAllocation7 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.startPrank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.addMetaVestToSet("testSet", mockAllocation5); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.addMetaVestToSet("testSet", mockAllocation6); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.addMetaVestToSet("testSet", mockAllocation7); - vm.stopPrank(); - - vm.startPrank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(mockAllocation5, "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(mockAllocation6, "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(mockAllocation7, "testSet", msgSig, true); - vm.stopPrank(); - - (uint256 totalVotingPower, uint256 currentVotingPower,,,) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("totalVotingPower: ", totalVotingPower); - console.log("currentVotingPower: ", currentVotingPower); - } - - function testCreateSetAddVestingThenRemoveSet() public { - - // template from testCreateSetAddVestingThenRemoveSet - address allocation1 = createDummyVestingAllocation(); - address allocation2 = createDummyVestingAllocation(); - address allocation3 = createDummyVestingAllocation(); - - vm.startPrank(authority); - controller.createSet("testSetB"); - controller.addMetaVestToSet("testSetB", allocation1); - controller.addMetaVestToSet("testSetB", allocation2); - controller.addMetaVestToSet("testSetB", allocation3); - controller.removeSet("testSetB"); - controller.createSet("testSetB"); - vm.stopPrank(); - - } } \ No newline at end of file diff --git a/test/AuditBaseC.t.sol b/test/AuditBaseC.t.sol index f1e6dad..b0521dd 100644 --- a/test/AuditBaseC.t.sol +++ b/test/AuditBaseC.t.sol @@ -7,55 +7,55 @@ import "../test/controller.t.sol"; // TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract Audit is MetaVestControllerTest { - function testAuditTerminateFailAfterWithdrawFixCheck() public { - // template from testTerminateVestAndRecoverSlowUnlock - address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.startPrank(grantee); - skip(25 seconds); - assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 1000e18 + 100e18 + 25 * 5e18, "Unexpected amount after cliff and milestone"); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - skip(25 seconds); - assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 25 * 5e18, "Unexpected amount after the second vesting period"); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - - controller.terminateMetavestVesting(vestingAllocation); - vm.warp(block.timestamp + 365 days); - assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), (10e18 - 5e18) * 50, "Unexpected amount after termination"); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - -// function testAuditTerminateFailAfterWithdrawFixCheckOptions() public { +// function testAuditTerminateFailAfterWithdrawFixCheck() public { // // template from testTerminateVestAndRecoverSlowUnlock -// address vestingAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); +// address vestingAllocation = createDummyVestingAllocationSlowUnlock(); // VestingAllocation(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 5 seconds); // vm.startPrank(grantee); -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.warp(block.timestamp + 5 seconds); -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// skip(25 seconds); +// assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 1000e18 + 100e18 + 25 * 5e18, "Unexpected amount after cliff and milestone"); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// skip(25 seconds); +// assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 25 * 5e18, "Unexpected amount after the second vesting period"); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// vm.warp(block.timestamp + 5 seconds); -// controller.terminateMetavestVesting(vestingAllocation); // +// controller.terminateMetavestVesting(vestingAllocation); +// vm.warp(block.timestamp + 365 days); +// assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), (10e18 - 5e18) * 50, "Unexpected amount after termination"); // vm.startPrank(grantee); -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// vm.warp(block.timestamp + 365 days); -// -// vm.prank(authority); -// TokenOptionAllocation(vestingAllocation).recoverForfeitTokens(); -// //check balance of the vesting contract -// assertEq(token.balanceOf(vestingAllocation), 0); // } +// +//// function testAuditTerminateFailAfterWithdrawFixCheckOptions() public { +//// // template from testTerminateVestAndRecoverSlowUnlock +//// address vestingAllocation = createDummyTokenOptionAllocation(); +//// uint256 snapshot = token.balanceOf(authority); +//// VestingAllocation(vestingAllocation).confirmMilestone(0); +//// vm.warp(block.timestamp + 5 seconds); +//// vm.startPrank(grantee); +//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +//// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +//// vm.warp(block.timestamp + 5 seconds); +//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +//// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +//// vm.stopPrank(); +//// vm.warp(block.timestamp + 5 seconds); +//// controller.terminateMetavestVesting(vestingAllocation); +//// +//// vm.startPrank(grantee); +//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +//// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +//// vm.stopPrank(); +//// vm.warp(block.timestamp + 365 days); +//// +//// vm.prank(authority); +//// TokenOptionAllocation(vestingAllocation).recoverForfeitTokens(); +//// //check balance of the vesting contract +//// assertEq(token.balanceOf(vestingAllocation), 0); +//// } } \ No newline at end of file diff --git a/test/AuditBaseC3.t.sol b/test/AuditBaseC3.t.sol index ff127d9..89f1819 100644 --- a/test/AuditBaseC3.t.sol +++ b/test/AuditBaseC3.t.sol @@ -7,81 +7,81 @@ import "../test/controller.t.sol"; // TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract Audit is MetaVestControllerTest { - function testAuditTerminateVestAndRecovers() public { - // template from testTerminateVestAndRecovers - address vestingAllocation = createDummyVestingAllocation(); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: false, - complete: false, - conditionContracts: new address[](0) - }); - - controller.addMetavestMilestone(vestingAllocation, milestones[0]); - VestingAllocation(vestingAllocation).confirmMilestone(1); - - skip(50 seconds); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - assertEq(VestingAllocation(vestingAllocation).getVestedTokenAmount(), 100e18 + 1000e18 + 10e18 * 50, "Unexpected vested amount after termination"); - assertEq(VestingAllocation(vestingAllocation).getUnlockedTokenAmount(), 100e18 + 5e18 * 50 + 5e18 * 50, "Unexpected unlocked amount after termination"); - - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - // assertEq(token.balanceOf(vestingAllocation), 0); - } - -// function test_RevertIf_AuditRounding() public { -// // template from testConfirmingMilestoneTokenOption -// address vestingAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); -// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 50 seconds); -// vm.startPrank(grantee); -// //exercise max available -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// function testAuditTerminateVestAndRecovers() public { +// // template from testTerminateVestAndRecovers +// address vestingAllocation = createDummyVestingAllocation(); // -// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); -// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e6)); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e11)); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(9.99e11)); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e12)); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1.1e12)); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e13)); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1.1e12); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 1000e18, +// unlockOnCompletion: false, +// complete: false, +// conditionContracts: new address[](0) +// }); // -// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); -// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); -// } +// controller.addMetavestMilestone(vestingAllocation, milestones[0]); +// VestingAllocation(vestingAllocation).confirmMilestone(1); // -// function testAuditExcercisePrice() public { -// // template from testConfirmingMilestoneTokenOption -// address vestingAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); -// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 50 seconds); +// skip(50 seconds); +// controller.terminateMetavestVesting(vestingAllocation); // vm.startPrank(grantee); -// //exercise max available -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// -// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); -// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// assertEq(VestingAllocation(vestingAllocation).getVestedTokenAmount(), 100e18 + 1000e18 + 10e18 * 50, "Unexpected vested amount after termination"); +// assertEq(VestingAllocation(vestingAllocation).getUnlockedTokenAmount(), 100e18 + 5e18 * 50 + 5e18 * 50, "Unexpected unlocked amount after termination"); // -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e18); -// -// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); -// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); +// // assertEq(token.balanceOf(vestingAllocation), 0); // } +// +//// function test_RevertIf_AuditRounding() public { +//// // template from testConfirmingMilestoneTokenOption +//// address vestingAllocation = createDummyTokenOptionAllocation(); +//// uint256 snapshot = token.balanceOf(authority); +//// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +//// vm.warp(block.timestamp + 50 seconds); +//// vm.startPrank(grantee); +//// //exercise max available +//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +//// +//// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); +//// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e6)); +//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e11)); +//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(9.99e11)); +//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e12)); +//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1.1e12)); +//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e13)); +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1.1e12); +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +//// +//// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); +//// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +//// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +//// vm.stopPrank(); +//// } +//// +//// function testAuditExcercisePrice() public { +//// // template from testConfirmingMilestoneTokenOption +//// address vestingAllocation = createDummyTokenOptionAllocation(); +//// uint256 snapshot = token.balanceOf(authority); +//// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +//// vm.warp(block.timestamp + 50 seconds); +//// vm.startPrank(grantee); +//// //exercise max available +//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +//// +//// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); +//// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +//// +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e18); +//// +//// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); +//// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +//// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +//// vm.stopPrank(); +//// } } diff --git a/test/MetaVesTFactory.t.sol b/test/MetaVesTFactory.t.sol index 3b36d99..0138255 100644 --- a/test/MetaVesTFactory.t.sol +++ b/test/MetaVesTFactory.t.sol @@ -15,111 +15,111 @@ import "forge-std/Test.sol"; /// @notice test contract for MetaVesTFactory using Foundry contract MetaVesTFactoryTest is Test { - // zkSync Era Sepolia @ 5576300 - address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; - IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); - IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); -// // zkSync Era mainnet -// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; -// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); -// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); - - IZkCappedMinterV2 zkCappedMinter; - - MetaVesTFactory internal factory; - metavestController controller; - address factoryAddr; - address dai_addr = 0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6; - VestingAllocationFactory _factory;// = new VestingAllocationFactory(); - RestrictedTokenFactory _factory2;// = new RestrictedTokenFactory(); - TokenOptionFactory _factory3;// = new TokenOptionFactory(); - - bytes32 salt = keccak256("MetaVesTFactoryTest"); - - event MetaVesT_Deployment( - address newMetaVesT, - address authority, - address controller, - address dao, - address paymentToken - ); - - function setUp() public { - _factory = new VestingAllocationFactory(); - _factory2 = new RestrictedTokenFactory(); - _factory3 = new TokenOptionFactory(); - factory = new MetaVesTFactory(); - factoryAddr = address(factory); - address _authority = address(0xa); - - address _dao = address(0xB); - address _paymentToken = address(0xC); - - controller = metavestController(factory.deployMetavestAndController(_authority, _dao, address(_factory), address(_factory2), address(_factory3))); - - // Deploy ZK Capped Minter v2 - zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( - address(zkToken), - address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT - 1000e18, - uint48(block.timestamp), // start now - uint48(block.timestamp + 365 days * 2), - uint256(salt) - )); - - vm.prank(_authority); - controller.setZkCappedMinter(address(zkCappedMinter)); - } - - function testDeployMetavestAndController() public { - address _authority = address(0xa); - address _dao = address(0xB); - address _paymentToken = address(0xC); - address grantee = address(0xD); - RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); - - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - //token.approve(address(controller), 1100e18); - - VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - )); - - assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); - assertEq(vestingAllocation.getAmountWithdrawable(), 100e18, "Should be able to withdraw cliff amount"); - } - - function test_RevertIf_ControllerZeroAddress() public { - address _authority = address(0); - address _dao = address(0); - address _paymentToken = address(0); - vm.expectRevert(abi.encodeWithSelector(MetaVesTFactory.MetaVesTFactory_ZeroAddress.selector)); - factory.deployMetavestAndController(_authority, _dao, address(0), address(0), address(0)); - } +// // zkSync Era Sepolia @ 5576300 +// address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; +// IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); +// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); +//// // zkSync Era mainnet +//// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; +//// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); +//// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); +// +// IZkCappedMinterV2 zkCappedMinter; +// +// MetaVesTFactory internal factory; +// metavestController controller; +// address factoryAddr; +// address dai_addr = 0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6; +// VestingAllocationFactory _factory;// = new VestingAllocationFactory(); +// RestrictedTokenFactory _factory2;// = new RestrictedTokenFactory(); +// TokenOptionFactory _factory3;// = new TokenOptionFactory(); +// +// bytes32 salt = keccak256("MetaVesTFactoryTest"); +// +// event MetaVesT_Deployment( +// address newMetaVesT, +// address authority, +// address controller, +// address dao, +// address paymentToken +// ); +// +// function setUp() public { +// _factory = new VestingAllocationFactory(); +// _factory2 = new RestrictedTokenFactory(); +// _factory3 = new TokenOptionFactory(); +// factory = new MetaVesTFactory(); +// factoryAddr = address(factory); +// address _authority = address(0xa); +// +// address _dao = address(0xB); +// address _paymentToken = address(0xC); +// +// controller = metavestController(factory.deployMetavestAndController(_authority, _dao, address(_factory), address(_factory2), address(_factory3))); +// +// // Deploy ZK Capped Minter v2 +// zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( +// address(zkToken), +// address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT +// 1000e18, +// uint48(block.timestamp), // start now +// uint48(block.timestamp + 365 days * 2), +// uint256(salt) +// )); +// +// vm.prank(_authority); +// controller.setZkCappedMinter(address(zkCappedMinter)); +// } +// +// function testDeployMetavestAndController() public { +// address _authority = address(0xa); +// address _dao = address(0xB); +// address _paymentToken = address(0xC); +// address grantee = address(0xD); +// RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); +// +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// //token.approve(address(controller), 1100e18); +// +// VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( +// metavestController.metavestType.Vesting, +// grantee, +// allocation, +// milestones, +// 0, +// address(0), +// 0, +// 0 +// +// )); +// +// assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// assertEq(vestingAllocation.getAmountWithdrawable(), 100e18, "Should be able to withdraw cliff amount"); +// } +// +// function test_RevertIf_ControllerZeroAddress() public { +// address _authority = address(0); +// address _dao = address(0); +// address _paymentToken = address(0); +// vm.expectRevert(abi.encodeWithSelector(MetaVesTFactory.MetaVesTFactory_ZeroAddress.selector)); +// factory.deployMetavestAndController(_authority, _dao, address(0), address(0), address(0)); +// } } diff --git a/test/amendement.t.sol b/test/amendement.t.sol index 6ae2ca9..123ec51 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -549,141 +549,88 @@ contract MockERC20 is ERC20 { // TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract MetaVestControllerTest is Test { - // zkSync Era Sepolia @ 5576300 - address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; - IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); - IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); -// // zkSync Era mainnet -// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; -// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); -// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); - - IZkCappedMinterV2 zkCappedMinter; - - metavestController public controller; -// MockERC20 public paymentToken; - address public authority; - address public dao; - address public vestingFactory; - address public tokenOptionFactory; - address public restrictedTokenFactory; - address public grantee; - address public mockAllocation; - - bytes32 salt = keccak256("MetaVestControllerTest"); - - function setUp() public { - authority = address(this); - dao = address(2); - VestingAllocationFactory factory = new VestingAllocationFactory(); - TokenOptionFactory tokenFactory = new TokenOptionFactory(); - RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); - grantee = address(6); - -// paymentToken = new MockERC20("Payment Token", "PT"); - - controller = new metavestController( - authority, - dao, - address(factory), - address(tokenFactory), - address(restrictedTokenFactory) - ); - -// paymentToken.mint(authority, 1000000e58); +// // zkSync Era Sepolia @ 5576300 +// address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; +// IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); +// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); +//// // zkSync Era mainnet +//// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; +//// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); +//// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); +// +// IZkCappedMinterV2 zkCappedMinter; +// +// metavestController public controller; +//// MockERC20 public paymentToken; +// address public authority; +// address public dao; +// address public vestingFactory; +// address public tokenOptionFactory; +// address public restrictedTokenFactory; +// address public grantee; +// address public mockAllocation; +// +// bytes32 salt = keccak256("MetaVestControllerTest"); +// +// function setUp() public { +// authority = address(this); +// dao = address(2); +// VestingAllocationFactory factory = new VestingAllocationFactory(); +// TokenOptionFactory tokenFactory = new TokenOptionFactory(); +// RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); +// grantee = address(6); +// +//// paymentToken = new MockERC20("Payment Token", "PT"); +// +// controller = new metavestController( +// authority, +// dao, +// address(factory), +// address(tokenFactory), +// address(restrictedTokenFactory) +// ); // -// paymentToken.transfer(address(grantee), 1000e25); - - vm.prank(authority); - controller.createSet("testSet"); - - // Deploy ZK Capped Minter v2 - zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( - address(zkToken), - address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT - 1000e18, - uint48(block.timestamp), // start now - uint48(block.timestamp + 365 days * 2), - uint256(salt) - )); - - vm.prank(authority); - controller.setZkCappedMinter(address(zkCappedMinter)); - - mockAllocation = createDummyVestingAllocation(); - } - - function testProposeMetavestAmendment() public { - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.proposeMetavestAmendment(address(mockAllocation), msgSig, callData); - - (bool isPending, bytes32 dataHash, bool inFavor) = controller.functionToGranteeToAmendmentPending(msgSig, address(mockAllocation)); - - assertTrue(isPending); - assertEq(dataHash, keccak256(callData)); - assertFalse(inFavor); - } - - function test_RevertIf_ProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - address mockAllocation4 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - //log the current withdrawable - console.log(VestingAllocation(mockAllocation2).getAmountWithdrawable()); - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.updateMetavestTransferability(mockAllocation2, true); - } - - function testQuickProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - address mockAllocation4 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.warp(block.timestamp + 15 seconds); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.startPrank(grantee); - //log the current withdrawable - console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); - - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - vm.stopPrank(); - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - } - - -// function testMajorityPowerMetavestAmendment() public { -// address mockAllocation2 = createDummyTokenOptionAllocation(); -// address mockAllocation3 = createDummyTokenOptionAllocation(); -// address mockAllocation4 = createDummyTokenOptionAllocation(); +//// paymentToken.mint(authority, 1000000e58); +//// +//// paymentToken.transfer(address(grantee), 1000e25); +// +// vm.prank(authority); +// controller.createSet("testSet"); +// +// // Deploy ZK Capped Minter v2 +// zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( +// address(zkToken), +// address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT +// 1000e18, +// uint48(block.timestamp), // start now +// uint48(block.timestamp + 365 days * 2), +// uint256(salt) +// )); +// +// vm.prank(authority); +// controller.setZkCappedMinter(address(zkCappedMinter)); +// +// mockAllocation = createDummyVestingAllocation(); +// } +// +// function testProposeMetavestAmendment() public { +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(address(mockAllocation), msgSig, callData); +// +// (bool isPending, bytes32 dataHash, bool inFavor) = controller.functionToGranteeToAmendmentPending(msgSig, address(mockAllocation)); +// +// assertTrue(isPending); +// assertEq(dataHash, keccak256(callData)); +// assertFalse(inFavor); +// } +// +// function test_RevertIf_ProposeMajorityMetavestAmendment() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// address mockAllocation3 = createDummyVestingAllocation(); +// address mockAllocation4 = createDummyVestingAllocation(); // bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); // bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); // @@ -692,277 +639,263 @@ contract MetaVestControllerTest is Test { // controller.addMetaVestToSet("testSet", mockAllocation3); // controller.addMetaVestToSet("testSet", mockAllocation4); // vm.warp(block.timestamp + 1 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// //log the current withdrawable +// console.log(VestingAllocation(mockAllocation2).getAmountWithdrawable()); +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } +// +// function testQuickProposeMajorityMetavestAmendment() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// address mockAllocation3 = createDummyVestingAllocation(); +// address mockAllocation4 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// controller.addMetaVestToSet("testSet", mockAllocation4); +// vm.warp(block.timestamp + 15 seconds); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// // vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); -// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +// //log the current withdrawable +// console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); +// +// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); +// +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); // vm.stopPrank(); // vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } +// +// +//// function testMajorityPowerMetavestAmendment() public { +//// address mockAllocation2 = createDummyTokenOptionAllocation(); +//// address mockAllocation3 = createDummyTokenOptionAllocation(); +//// address mockAllocation4 = createDummyTokenOptionAllocation(); +//// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +//// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +//// +//// vm.prank(authority); +//// controller.addMetaVestToSet("testSet", mockAllocation2); +//// controller.addMetaVestToSet("testSet", mockAllocation3); +//// controller.addMetaVestToSet("testSet", mockAllocation4); +//// vm.warp(block.timestamp + 1 days); +//// vm.startPrank(grantee); +//// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +//// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +//// vm.stopPrank(); +//// vm.prank(authority); +//// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +//// vm.prank(grantee); +//// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestTransferability(mockAllocation2, true); +//// } +// +//// function test_RevertIf_MajorityPowerMetavestAmendment() public { +//// address mockAllocation2 = createDummyTokenOptionAllocation(); +//// address mockAllocation3 = createDummyTokenOptionAllocation(); +//// address mockAllocation4 = createDummyTokenOptionAllocation(); +//// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +//// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +//// +//// vm.prank(authority); +//// controller.addMetaVestToSet("testSet", mockAllocation2); +//// controller.addMetaVestToSet("testSet", mockAllocation3); +//// controller.addMetaVestToSet("testSet", mockAllocation4); +//// vm.warp(block.timestamp + 1 days); +//// vm.startPrank(grantee); +//// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +//// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +//// vm.stopPrank(); +//// vm.prank(authority); +//// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +//// vm.prank(grantee); +//// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestTransferability(mockAllocation2, true); +//// vm.prank(authority); +//// controller.updateMetavestTransferability(mockAllocation2, true); +//// } +// +// function testProposeMajorityMetavestAmendment() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// address mockAllocation3 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// vm.warp(block.timestamp + 1 days); +// vm.prank(authority); // controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); // // vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); // // vm.prank(authority); // controller.updateMetavestTransferability(mockAllocation2, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation3, true); // } - -// function test_RevertIf_MajorityPowerMetavestAmendment() public { -// address mockAllocation2 = createDummyTokenOptionAllocation(); -// address mockAllocation3 = createDummyTokenOptionAllocation(); -// address mockAllocation4 = createDummyTokenOptionAllocation(); +// +// function testProposeMajorityMetavestAmendmentReAdd() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// address mockAllocation3 = createDummyVestingAllocation(); // bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); // bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); // // vm.prank(authority); // controller.addMetaVestToSet("testSet", mockAllocation2); // controller.addMetaVestToSet("testSet", mockAllocation3); -// controller.addMetaVestToSet("testSet", mockAllocation4); // vm.warp(block.timestamp + 1 days); -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); -// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); -// vm.stopPrank(); // vm.prank(authority); // controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); // // vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation3, true); +// +// vm.prank(authority); +// controller.removeMetaVestFromSet("testSet", mockAllocation3); +// // vm.prank(authority); +// // controller.updateMetavestTransferability(mockAllocation3, true); +// vm.warp(block.timestamp + 90 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// // vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); // // vm.prank(authority); // controller.updateMetavestTransferability(mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation3, true); +// } +// +// function test_RevertIf_NoPassProposeMajorityMetavestAmendment() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// address mockAllocation3 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// // vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// vm.warp(block.timestamp + 1 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(authority); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); // controller.updateMetavestTransferability(mockAllocation2, true); +// +// vm.prank(authority); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); +// controller.updateMetavestTransferability(mockAllocation3, true); // } - - function testProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation3, true); - } - - function testProposeMajorityMetavestAmendmentReAdd() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation3, true); - - vm.prank(authority); - controller.removeMetaVestFromSet("testSet", mockAllocation3); - // vm.prank(authority); - // controller.updateMetavestTransferability(mockAllocation3, true); - vm.warp(block.timestamp + 90 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation3); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation3, true); - } - - function test_RevertIf_NoPassProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.updateMetavestTransferability(mockAllocation2, true); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.updateMetavestTransferability(mockAllocation3, true); - } - - function testVoteOnMetavestAmendment() public { - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - - } - - function test_RevertIf_VoteOnMetavestAmendmentTwice() public { - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.startPrank(grantee); - controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AlreadyVoted.selector)); - controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); - vm.stopPrank(); - } - - function testSetManagement() public { - vm.startPrank(authority); - - // Test creating a new set - controller.createSet("newSet"); - - // Test adding a MetaVest to a set - controller.addMetaVestToSet("newSet", address(mockAllocation)); - - - // Test removing a MetaVest from a set - controller.removeMetaVestFromSet("newSet", address(mockAllocation)); - - - // Test removing a set - controller.removeSet("newSet"); - - - vm.stopPrank(); - } - - function test_RevertIf_CreateDuplicateSet() public { - vm.startPrank(authority); - controller.createSet("duplicateSet"); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetAlreadyExists.selector)); - controller.createSet("duplicateSet"); - vm.stopPrank(); - } - - function test_RevertIf_NonAuthorityCreateSet() public { - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); - controller.createSet("unauthorizedSet"); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocation() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - -// function createDummyTokenOptionAllocation() internal returns (address) { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); // -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 100e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); +// function testVoteOnMetavestAmendment() public { +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); // -// token.approve(address(controller), 1100e18); +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", address(mockAllocation)); // -// return controller.createMetavest( -// metavestController.metavestType.TokenOption, -// grantee, -// allocation, -// milestones, -// 1e18, -// address(paymentToken), -// 365 days, -// 0 -// ); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); +// +// (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); +// +// } +// +// function test_RevertIf_VoteOnMetavestAmendmentTwice() public { +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", address(mockAllocation)); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.startPrank(grantee); +// controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AlreadyVoted.selector)); +// controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); +// vm.stopPrank(); +// } +// +// function testSetManagement() public { +// vm.startPrank(authority); +// +// // Test creating a new set +// controller.createSet("newSet"); +// +// // Test adding a MetaVest to a set +// controller.addMetaVestToSet("newSet", address(mockAllocation)); +// +// +// // Test removing a MetaVest from a set +// controller.removeMetaVestFromSet("newSet", address(mockAllocation)); +// +// +// // Test removing a set +// controller.removeSet("newSet"); +// +// +// vm.stopPrank(); // } // -// function createDummyRestrictedTokenAward() internal returns (address) { +// function test_RevertIf_CreateDuplicateSet() public { +// vm.startPrank(authority); +// controller.createSet("duplicateSet"); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetAlreadyExists.selector)); +// controller.createSet("duplicateSet"); +// vm.stopPrank(); +// } +// +// function test_RevertIf_NonAuthorityCreateSet() public { +// vm.prank(grantee); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); +// controller.createSet("unauthorizedSet"); +// } +// +// // Helper functions to create dummy allocations for testing +// function createDummyVestingAllocation() internal returns (address) { // BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), +// tokenContract: address(zkToken), // tokenStreamTotal: 1000e18, // vestingCliffCredit: 100e18, // unlockingCliffCredit: 100e18, @@ -980,206 +913,158 @@ contract MetaVestControllerTest is Test { // conditionContracts: new address[](0) // }); // -// token.approve(address(controller), 1100e18); -// // return controller.createMetavest( -// metavestController.metavestType.RestrictedTokenAward, +// metavestController.metavestType.Vesting, // grantee, // allocation, // milestones, -// 1e18, -// address(paymentToken), -// 365 days, +// 0, +// address(0), +// 0, // 0 // // ); // } - - //write a test for every consentcheck function in metavest controller - function testConsentCheck() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - } - - function test_RevertIf_ConsentCheck() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.updateMetavestTransferability(allocation, true); - } - - function test_RevertIf_ConsentCheckNoProposal() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); - } - - function test_RevertIf_ConsentCheckNoVote() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.updateMetavestTransferability(allocation, true); - } - - function test_RevertIf_ConsentCheckNoUpdate() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); - } - - function test_RevertIf_ConsentCheckNoVoteUpdate() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); - } - -// function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { -// address allocation1 = createDummyTokenOptionAllocation(); -// address allocation2 = createDummyTokenOptionAllocation(); -// address allocation3 = createDummyTokenOptionAllocation(); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", allocation1); -// controller.addMetaVestToSet("testSet", allocation2); -// controller.addMetaVestToSet("testSet", allocation3); -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); -// vm.warp(block.timestamp + 25 seconds); -// // -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(allocation1), 2000e18); -// ERC20(paymentToken).approve(address(allocation2), 2000e18); -// -// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); -// -// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); -// vm.stopPrank(); -// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +//// function createDummyTokenOptionAllocation() internal returns (address) { +//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +//// tokenContract: address(token), +//// tokenStreamTotal: 1000e18, +//// vestingCliffCredit: 100e18, +//// unlockingCliffCredit: 100e18, +//// vestingRate: 10e18, +//// vestingStartTime: uint48(block.timestamp), +//// unlockRate: 10e18, +//// unlockStartTime: uint48(block.timestamp) +//// }); +//// +//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +//// milestones[0] = BaseAllocation.Milestone({ +//// milestoneAward: 100e18, +//// unlockOnCompletion: true, +//// complete: false, +//// conditionContracts: new address[](0) +//// }); +//// +//// token.approve(address(controller), 1100e18); +//// +//// return controller.createMetavest( +//// metavestController.metavestType.TokenOption, +//// grantee, +//// allocation, +//// milestones, +//// 1e18, +//// address(paymentToken), +//// 365 days, +//// 0 +//// ); +//// } +//// +//// function createDummyRestrictedTokenAward() internal returns (address) { +//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +//// tokenContract: address(token), +//// tokenStreamTotal: 1000e18, +//// vestingCliffCredit: 100e18, +//// unlockingCliffCredit: 100e18, +//// vestingRate: 10e18, +//// vestingStartTime: uint48(block.timestamp), +//// unlockRate: 10e18, +//// unlockStartTime: uint48(block.timestamp) +//// }); +//// +//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +//// milestones[0] = BaseAllocation.Milestone({ +//// milestoneAward: 100e18, +//// unlockOnCompletion: true, +//// complete: false, +//// conditionContracts: new address[](0) +//// }); +//// +//// token.approve(address(controller), 1100e18); +//// +//// return controller.createMetavest( +//// metavestController.metavestType.RestrictedTokenAward, +//// grantee, +//// allocation, +//// milestones, +//// 1e18, +//// address(paymentToken), +//// 365 days, +//// 0 +//// +//// ); +//// } +// +// //write a test for every consentcheck function in metavest controller +// function testConsentCheck() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); // // vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); // // vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// controller.consentToMetavestAmendment(allocation, msgSig, true); // // vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +// controller.updateMetavestTransferability(allocation, true); +// } // -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +// function test_RevertIf_ConsentCheck() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); // // vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); // -// // Check that the exercise price was updated -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); -// } - -// function test_RevertIf_CreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { -// address allocation1 = createDummyTokenOptionAllocation(); -// address allocation2 = createDummyTokenOptionAllocation(); -// address allocation3 = createDummyTokenOptionAllocation(); +// vm.prank(grantee); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); // // vm.prank(authority); -// controller.addMetaVestToSet("testSet", allocation1); -// controller.addMetaVestToSet("testSet", allocation2); -// controller.addMetaVestToSet("testSet", allocation3); -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); -// vm.warp(block.timestamp + 25 seconds); -// +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); +// controller.updateMetavestTransferability(allocation, true); +// } // -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(allocation1), 2000e18); -// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// function test_RevertIf_ConsentCheckNoProposal() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); // -// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// vm.prank(grantee); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); +// } // -// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); -// vm.stopPrank(); -// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// function test_RevertIf_ConsentCheckNoVote() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); // // vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// //vm.prank(grantee); -// // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); -// -// // vm.prank(grantee); -// // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); // // vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); +// controller.updateMetavestTransferability(allocation, true); +// } // -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +// function test_RevertIf_ConsentCheckNoUpdate() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); // // vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); // -// // Check that the exercise price was updated -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +// vm.prank(grantee); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); // } - - function test_RevertIf_consentToNoPendingAmendment() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_NoPendingAmendment.selector, msgSig, allocation)); - controller.consentToMetavestAmendment(allocation, msgSig, true); - } - -// function testEveryUpdateAmendmentFunction() public { -// address allocation = createDummyTokenOptionAllocation(); +// +// function test_RevertIf_ConsentCheckNoVoteUpdate() public { +// address allocation = createDummyVestingAllocation(); // bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); // bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); // @@ -1187,13 +1072,266 @@ contract MetaVestControllerTest is Test { // controller.proposeMetavestAmendment(allocation, msgSig, callData); // // vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); +// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); +// } // -// vm.prank(authority); -// controller.updateMetavestTransferability(allocation, true); +//// function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +//// address allocation1 = createDummyTokenOptionAllocation(); +//// address allocation2 = createDummyTokenOptionAllocation(); +//// address allocation3 = createDummyTokenOptionAllocation(); +//// +//// vm.prank(authority); +//// controller.addMetaVestToSet("testSet", allocation1); +//// controller.addMetaVestToSet("testSet", allocation2); +//// controller.addMetaVestToSet("testSet", allocation3); +//// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +//// vm.warp(block.timestamp + 25 seconds); +//// +//// +//// vm.startPrank(grantee); +//// ERC20(paymentToken).approve(address(allocation1), 2000e18); +//// ERC20(paymentToken).approve(address(allocation2), 2000e18); +//// +//// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +//// +//// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +//// vm.stopPrank(); +//// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +//// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +//// +//// vm.prank(authority); +//// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +//// +//// vm.prank(grantee); +//// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +//// +//// vm.prank(authority); +//// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +//// +//// vm.prank(authority); +//// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +//// +//// // Check that the exercise price was updated +//// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +//// } +// +//// function test_RevertIf_CreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +//// address allocation1 = createDummyTokenOptionAllocation(); +//// address allocation2 = createDummyTokenOptionAllocation(); +//// address allocation3 = createDummyTokenOptionAllocation(); +//// +//// vm.prank(authority); +//// controller.addMetaVestToSet("testSet", allocation1); +//// controller.addMetaVestToSet("testSet", allocation2); +//// controller.addMetaVestToSet("testSet", allocation3); +//// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +//// vm.warp(block.timestamp + 25 seconds); +//// +//// +//// vm.startPrank(grantee); +//// ERC20(paymentToken).approve(address(allocation1), 2000e18); +//// ERC20(paymentToken).approve(address(allocation2), 2000e18); +//// +//// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +//// +//// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +//// vm.stopPrank(); +//// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +//// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +//// +//// vm.prank(authority); +//// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +//// +//// //vm.prank(grantee); +//// // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +//// +//// // vm.prank(grantee); +//// // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +//// +//// vm.prank(authority); +//// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +//// +//// vm.prank(authority); +//// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +//// +//// // Check that the exercise price was updated +//// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +//// } +// +// function test_RevertIf_consentToNoPendingAmendment() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); // -// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); +// vm.prank(grantee); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_NoPendingAmendment.selector, msgSig, allocation)); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// } +// +//// function testEveryUpdateAmendmentFunction() public { +//// address allocation = createDummyTokenOptionAllocation(); +//// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +//// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestTransferability(allocation, true); +//// +//// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +//// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); +//// +//// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +//// callData = abi.encodeWithSelector(msgSig, allocation, 0); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.removeMetavestMilestone(allocation, 0); +//// +//// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +//// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestUnlockRate(allocation, 20e18); +//// +//// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +//// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestVestingRate(allocation, 20e18); +//// +//// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); +//// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); +//// } +// +//// function testEveryUpdateAmendmentFunctionRestricted() public { +//// address allocation = createDummyRestrictedTokenAward(); +//// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +//// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestTransferability(allocation, true); +//// +//// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +//// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); +//// +//// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +//// callData = abi.encodeWithSelector(msgSig, allocation, 0); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.removeMetavestMilestone(allocation, 0); +//// +//// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +//// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestUnlockRate(allocation, 20e18); +//// +//// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +//// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestVestingRate(allocation, 20e18); +//// +//// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); +//// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(allocation, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(allocation, msgSig, true); +//// +//// vm.prank(authority); +//// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); +//// } +// +// function testEveryUpdateAmendmentFunctionVesting() public { +// address allocation = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); // // vm.prank(authority); // controller.proposeMetavestAmendment(allocation, msgSig, callData); @@ -1202,7 +1340,7 @@ contract MetaVestControllerTest is Test { // controller.consentToMetavestAmendment(allocation, msgSig, true); // // vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); +// controller.updateMetavestTransferability(allocation, true); // // msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); // callData = abi.encodeWithSelector(msgSig, allocation, 0); @@ -1252,9 +1390,9 @@ contract MetaVestControllerTest is Test { // vm.prank(authority); // controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); // } - -// function testEveryUpdateAmendmentFunctionRestricted() public { -// address allocation = createDummyRestrictedTokenAward(); +// +// function test_RevertIf_EveryUpdateAmendmentFunctionVesting() public { +// address allocation = createDummyVestingAllocation(); // bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); // bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); // @@ -1267,18 +1405,6 @@ contract MetaVestControllerTest is Test { // vm.prank(authority); // controller.updateMetavestTransferability(allocation, true); // -// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); -// // msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); // callData = abi.encodeWithSelector(msgSig, allocation, 0); // @@ -1321,134 +1447,8 @@ contract MetaVestControllerTest is Test { // vm.prank(authority); // controller.proposeMetavestAmendment(allocation, msgSig, callData); // -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// // vm.prank(authority); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); // controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); // } - - function testEveryUpdateAmendmentFunctionVesting() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - - msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.removeMetavestMilestone(allocation, 0); - - msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(allocation, 20e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(allocation, 20e18); - - msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); - callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); - } - - function test_RevertIf_EveryUpdateAmendmentFunctionVesting() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - - msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.removeMetavestMilestone(allocation, 0); - - msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(allocation, 20e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(allocation, 20e18); - - msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); - callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); - } } diff --git a/test/controller.t.sol b/test/controller.t.sol index 8dcfbf1..9b2f5ec 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -1095,105 +1095,69 @@ contract MockERC20Stable is ERC20Stable { // TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract MetaVestControllerTest is Test { - // zkSync Era Sepolia @ 5576300 - address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; - IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); - IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); -// // zkSync Era mainnet -// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; -// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); -// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); - - IZkCappedMinterV2 zkCappedMinter; - - metavestController public controller; - MockERC20Stable public paymentToken; - - address public authority; - address public dao; - address public grantee; - address public transferee; - - bytes32 salt = keccak256("MetaVesTFactoryTest"); - - function setUp() public { - authority = address(this); - dao = address(0x2); - grantee = address(0x3); - transferee = address(0x4); - - paymentToken = new MockERC20Stable("Payment Token", "PT"); - - VestingAllocationFactory factory = new VestingAllocationFactory(); - TokenOptionFactory tokenFactory = new TokenOptionFactory(); - RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); - - controller = new metavestController( - authority, - dao, - address(factory), - address(tokenFactory), - address(restrictedTokenFactory) - ); - - // Deploy ZK Capped Minter v2 - zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( - address(zkToken), - address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT - 9999e18, - uint48(block.timestamp), // start now - uint48(block.timestamp + 365 days * 2), - uint256(salt) - )); - - vm.startPrank(authority); - controller.setZkCappedMinter(address(zkCappedMinter)); - controller.createSet("testSet"); - vm.stopPrank(); - - // Simulate TPP approval and ZK Token admin to grant our ZkCappedMinter access - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - } - - function testCreateVestingAllocation() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - )); - - assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); - assertEq(vestingAllocation.getAmountWithdrawable(), 100e18, "Should be able to withdraw cliff amount"); - } - -// function testCreateTokenOptionAllocation() public { +// // zkSync Era Sepolia @ 5576300 +// address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; +// IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); +// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); +//// // zkSync Era mainnet +//// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; +//// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); +//// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); +// +// IZkCappedMinterV2 zkCappedMinter; +// +// metavestController public controller; +// MockERC20Stable public paymentToken; +// +// address public authority; +// address public dao; +// address public grantee; +// address public transferee; +// +// bytes32 salt = keccak256("MetaVesTFactoryTest"); +// +// function setUp() public { +// authority = address(this); +// dao = address(0x2); +// grantee = address(0x3); +// transferee = address(0x4); +// +// paymentToken = new MockERC20Stable("Payment Token", "PT"); +// +// VestingAllocationFactory factory = new VestingAllocationFactory(); +// TokenOptionFactory tokenFactory = new TokenOptionFactory(); +// RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); +// +// controller = new metavestController( +// authority, +// dao, +// address(factory), +// address(tokenFactory), +// address(restrictedTokenFactory) +// ); +// +// // Deploy ZK Capped Minter v2 +// zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( +// address(zkToken), +// address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT +// 9999e18, +// uint48(block.timestamp), // start now +// uint48(block.timestamp + 365 days * 2), +// uint256(salt) +// )); +// +// vm.startPrank(authority); +// controller.setZkCappedMinter(address(zkCappedMinter)); +// controller.createSet("testSet"); +// vm.stopPrank(); +// +// // Simulate TPP approval and ZK Token admin to grant our ZkCappedMinter access +// bytes32 minterRole = zkToken.MINTER_ROLE(); +// vm.prank(zkTokenAdmin); +// zkToken.grantRole(minterRole, address(zkCappedMinter)); +// } +// +// function testCreateVestingAllocation() public { // BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ // tokenContract: address(zkToken), // tokenStreamTotal: 1000e18, @@ -1213,566 +1177,443 @@ contract MetaVestControllerTest is Test { // conditionContracts: new address[](0) // }); // -// address tokenOptionAllocation = controller.createMetavest( -// metavestController.metavestType.TokenOption, +// VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( +// metavestController.metavestType.Vesting, // grantee, // allocation, // milestones, -// 1e18, -// address(paymentToken), -// 365 days, +// 0, +// address(0), +// 0, // 0 -// ); // -// assertEq(zkToken.balanceOf(address(tokenOptionAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); -// //assertEq(controller.tokenOptionAllocations(grantee, 0), tokenOptionAllocation); +// )); +// +// assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// assertEq(vestingAllocation.getAmountWithdrawable(), 100e18, "Should be able to withdraw cliff amount"); // } - -// function testCreateRestrictedTokenAward() public { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); // -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 100e18, +//// function testCreateTokenOptionAllocation() public { +//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +//// tokenContract: address(zkToken), +//// tokenStreamTotal: 1000e18, +//// vestingCliffCredit: 100e18, +//// unlockingCliffCredit: 100e18, +//// vestingRate: 10e18, +//// vestingStartTime: uint48(block.timestamp), +//// unlockRate: 10e18, +//// unlockStartTime: uint48(block.timestamp) +//// }); +//// +//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +//// milestones[0] = BaseAllocation.Milestone({ +//// milestoneAward: 100e18, +//// unlockOnCompletion: true, +//// complete: false, +//// conditionContracts: new address[](0) +//// }); +//// +//// address tokenOptionAllocation = controller.createMetavest( +//// metavestController.metavestType.TokenOption, +//// grantee, +//// allocation, +//// milestones, +//// 1e18, +//// address(paymentToken), +//// 365 days, +//// 0 +//// ); +//// +//// assertEq(zkToken.balanceOf(address(tokenOptionAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); +//// //assertEq(controller.tokenOptionAllocations(grantee, 0), tokenOptionAllocation); +//// } +// +//// function testCreateRestrictedTokenAward() public { +//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +//// tokenContract: address(zkToken), +//// tokenStreamTotal: 1000e18, +//// vestingCliffCredit: 100e18, +//// unlockingCliffCredit: 100e18, +//// vestingRate: 10e18, +//// vestingStartTime: uint48(block.timestamp), +//// unlockRate: 10e18, +//// unlockStartTime: uint48(block.timestamp) +//// }); +//// +//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +//// milestones[0] = BaseAllocation.Milestone({ +//// milestoneAward: 100e18, +//// unlockOnCompletion: true, +//// complete: false, +//// conditionContracts: new address[](0) +//// }); +//// +//// address restrictedTokenAward = controller.createMetavest( +//// metavestController.metavestType.RestrictedTokenAward, +//// grantee, +//// allocation, +//// milestones, +//// 1e18, +//// address(paymentToken), +//// 365 days, +//// 0 +//// +//// ); +//// +//// assertEq(zkToken.balanceOf(address(restrictedTokenAward)), 0, "Vesting contract should not have any token (it mints on-demand)"); +//// //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); +//// } +// +// function testUpdateTransferability() public { +// uint256 startTimestamp = block.timestamp; +// address vestingAllocation = createDummyVestingAllocation(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// //compute msg.data for updateMetavestTransferability(vestingAllocation, true) +// bytes4 selector = controller.updateMetavestTransferability.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, true); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, msgData); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, true); +// +// controller.updateMetavestTransferability(vestingAllocation, true); +// vm.prank(grantee); +// RestrictedTokenAward(vestingAllocation).transferRights(transferee); +// vm.prank(transferee); +// RestrictedTokenAward(vestingAllocation).confirmTransfer(); +// uint256 newTimestamp = startTimestamp + 100; // 101 +// vm.warp(newTimestamp); +// skip(10); +// vm.prank(transferee); +// uint256 balance = RestrictedTokenAward(vestingAllocation).getAmountWithdrawable(); +// +// +// //warp ahead 100 blocks +// +// vm.prank(transferee); +// RestrictedTokenAward(vestingAllocation).withdraw(balance); +// +// // assertTrue(BaseAllocation(vestingAllocation).transferable()); +// } +// +// function testGetGovPower() public { +// address vestingAllocation = createDummyVestingAllocation(); +// BaseAllocation(vestingAllocation).getGoverningPower(); +// } +// +// function testProposeMajorityMetavestAmendment() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// vm.warp(block.timestamp + 1 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } +// +// +// function test_RevertIf_ReProposeMajorityMetavestAmendment() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// vm.warp(block.timestamp + 1 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// vm.warp(block.timestamp + 30 days); +// /* +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true);*/ +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// } +// +// function testReProposeMajorityMetavestAmendment() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// vm.warp(block.timestamp + 1 days); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// vm.warp(block.timestamp + 30 days); +// +// vm.prank(authority); +// controller.cancelExpiredMajorityMetavestAmendment("testSet", msgSig); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// } +// +// function test_RevertIf_RemoveNonExistantMetaVestFromSet() public { +// address mockAllocation2 = createDummyVestingAllocation(); +// vm.startPrank(authority); +// // controller.createSet("testSet"); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_MetaVestNotInSet.selector)); +// controller.removeMetaVestFromSet("testSet", mockAllocation2); +// } +// +// +//// function testUpdateExercisePrice() public { +//// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +//// +//// //compute msg.data for updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18) +//// bytes4 selector = controller.updateExerciseOrRepurchasePrice.selector; +//// bytes memory msgData = abi.encodeWithSelector(selector, tokenOptionAllocation, 2e18); +//// +//// controller.proposeMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, msgData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, true); +//// +//// controller.updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18); +//// +//// assertEq(TokenOptionAllocation(tokenOptionAllocation).exercisePrice(), 2e18); +//// } +// +// function testRemoveMilestone() public { +// address vestingAllocation = createDummyVestingAllocation(); +// //create array of addresses and include vestingAllocation address +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); +// controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); +// vm.prank(grantee); +// //consent to amendment for the removemetavestmilestone method sig function consentToMetavestAmendment(address _metavest, bytes4 _msgSig, bool _inFavor) external { +// controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); +// controller.removeMetavestMilestone(vestingAllocation, 0); +// +// //BaseAllocation.Milestone memory milestone = BaseAllocation(vestingAllocation).milestones(0); +// //assertEq(milestone.milestoneAward, 0); +// } +// +// function testAddMilestone() public { +// address vestingAllocation = createDummyVestingAllocation(); +// +// BaseAllocation.Milestone memory newMilestone = BaseAllocation.Milestone({ +// milestoneAward: 50e18, // unlockOnCompletion: true, // complete: false, // conditionContracts: new address[](0) // }); // -// address restrictedTokenAward = controller.createMetavest( -// metavestController.metavestType.RestrictedTokenAward, -// grantee, -// allocation, -// milestones, -// 1e18, -// address(paymentToken), -// 365 days, -// 0 -// -// ); +// controller.addMetavestMilestone(vestingAllocation, newMilestone); // -// assertEq(zkToken.balanceOf(address(restrictedTokenAward)), 0, "Vesting contract should not have any token (it mints on-demand)"); -// //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); +// // BaseAllocation.Milestone memory addedMilestone = BaseAllocation(vestingAllocation).milestones[0]; +// // assertEq(addedMilestone.milestoneAward, 50e18); // } - - function testUpdateTransferability() public { - uint256 startTimestamp = block.timestamp; - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - //compute msg.data for updateMetavestTransferability(vestingAllocation, true) - bytes4 selector = controller.updateMetavestTransferability.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, true); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, true); - - controller.updateMetavestTransferability(vestingAllocation, true); - vm.prank(grantee); - RestrictedTokenAward(vestingAllocation).transferRights(transferee); - vm.prank(transferee); - RestrictedTokenAward(vestingAllocation).confirmTransfer(); - uint256 newTimestamp = startTimestamp + 100; // 101 - vm.warp(newTimestamp); - skip(10); - vm.prank(transferee); - uint256 balance = RestrictedTokenAward(vestingAllocation).getAmountWithdrawable(); - - - //warp ahead 100 blocks - - vm.prank(transferee); - RestrictedTokenAward(vestingAllocation).withdraw(balance); - - // assertTrue(BaseAllocation(vestingAllocation).transferable()); - } - - function testGetGovPower() public { - address vestingAllocation = createDummyVestingAllocation(); - BaseAllocation(vestingAllocation).getGoverningPower(); - } - - function testProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - } - - - function test_RevertIf_ReProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - vm.warp(block.timestamp + 30 days); - /* - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true);*/ - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - } - - function testReProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - vm.warp(block.timestamp + 30 days); - - vm.prank(authority); - controller.cancelExpiredMajorityMetavestAmendment("testSet", msgSig); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - } - - function test_RevertIf_RemoveNonExistantMetaVestFromSet() public { - address mockAllocation2 = createDummyVestingAllocation(); - vm.startPrank(authority); - // controller.createSet("testSet"); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_MetaVestNotInSet.selector)); - controller.removeMetaVestFromSet("testSet", mockAllocation2); - } - - -// function testUpdateExercisePrice() public { -// address tokenOptionAllocation = createDummyTokenOptionAllocation(); // -// //compute msg.data for updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18) -// bytes4 selector = controller.updateExerciseOrRepurchasePrice.selector; -// bytes memory msgData = abi.encodeWithSelector(selector, tokenOptionAllocation, 2e18); +// function testUpdateUnlockRate() public { +// address vestingAllocation = createDummyVestingAllocation(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = controller.updateMetavestUnlockRate.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); // -// controller.proposeMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, msgData); +// controller.updateMetavestUnlockRate(vestingAllocation, 20e18); // +// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); +// assertEq(updatedAllocation.unlockRate, 20e18); +// } +// +// function testUpdateUnlockRateZeroEmergency() public { +// address vestingAllocation = createDummyVestingAllocation(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = controller.updateMetavestUnlockRate.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); // vm.prank(grantee); -// controller.consentToMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, true); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); // -// controller.updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18); +// controller.updateMetavestUnlockRate(vestingAllocation, 0); // -// assertEq(TokenOptionAllocation(tokenOptionAllocation).exercisePrice(), 2e18); +// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); +// assertEq(updatedAllocation.unlockRate, 0); +// +// controller.terminateMetavestVesting(vestingAllocation); +// +// controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); +// updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); +// assertEq(updatedAllocation.unlockRate, 1e20); // } - - function testRemoveMilestone() public { - address vestingAllocation = createDummyVestingAllocation(); - //create array of addresses and include vestingAllocation address - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - vm.prank(grantee); - //consent to amendment for the removemetavestmilestone method sig function consentToMetavestAmendment(address _metavest, bytes4 _msgSig, bool _inFavor) external { - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - controller.removeMetavestMilestone(vestingAllocation, 0); - - //BaseAllocation.Milestone memory milestone = BaseAllocation(vestingAllocation).milestones(0); - //assertEq(milestone.milestoneAward, 0); - } - - function testAddMilestone() public { - address vestingAllocation = createDummyVestingAllocation(); - - BaseAllocation.Milestone memory newMilestone = BaseAllocation.Milestone({ - milestoneAward: 50e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - controller.addMetavestMilestone(vestingAllocation, newMilestone); - - // BaseAllocation.Milestone memory addedMilestone = BaseAllocation(vestingAllocation).milestones[0]; - // assertEq(addedMilestone.milestoneAward, 50e18); - } - - function testUpdateUnlockRate() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - - controller.updateMetavestUnlockRate(vestingAllocation, 20e18); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 20e18); - } - - function testUpdateUnlockRateZeroEmergency() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - - controller.updateMetavestUnlockRate(vestingAllocation, 0); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 0); - - controller.terminateMetavestVesting(vestingAllocation); - - controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 1e20); - } - - function test_RevertIf_UpdateUnlockRateZeroEmergency() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - - controller.terminateMetavestVesting(vestingAllocation); - - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); - controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 10e18); - } - - function test_RevertIf_UpdateUnlockRateZeroEmergencyTerminated() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - - controller.updateMetavestUnlockRate(vestingAllocation, 0); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 0); - - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); - controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 0); - } - - function testUpdateVestingRate() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestVestingRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, true); - - controller.updateMetavestVestingRate(vestingAllocation, 20e18); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.vestingRate, 20e18); - } - -// function testUpdateStopTimes() public { // -// address vestingAllocation = createDummyRestrictedTokenAward(); -// address[] memory addresses = new address[](1); +// function test_RevertIf_UpdateUnlockRateZeroEmergency() public { +// address vestingAllocation = createDummyVestingAllocation(); +// address[] memory addresses = new address[](1); // addresses[0] = vestingAllocation; -// bytes4 selector = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, uint48(block.timestamp + 500 days)); -// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, msgData); +// bytes4 selector = controller.updateMetavestUnlockRate.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); // vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, true); -// uint48 newShortStopTime = uint48(block.timestamp + 500 days); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); // -// controller.updateMetavestStopTimes(vestingAllocation, newShortStopTime); +// controller.terminateMetavestVesting(vestingAllocation); +// +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); +// controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); +// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); +// assertEq(updatedAllocation.unlockRate, 10e18); // } - - function testTerminateVesting() public { - address vestingAllocation = createDummyVestingAllocation(); - - controller.terminateMetavestVesting(vestingAllocation); - - assertTrue(BaseAllocation(vestingAllocation).terminated()); - } - -// function testRepurchaseTokens() public { -// uint256 startingBalance = paymentToken.balanceOf(grantee); -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// uint256 repurchaseAmount = 5e18; -// uint256 snapshot = token.balanceOf(authority); -// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); -// controller.terminateMetavestVesting(restrictedTokenAward); -// paymentToken.approve(address(restrictedTokenAward), payment); -// vm.warp(block.timestamp + 20 days); -// vm.prank(authority); -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); // -// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +// function test_RevertIf_UpdateUnlockRateZeroEmergencyTerminated() public { +// address vestingAllocation = createDummyVestingAllocation(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = controller.updateMetavestUnlockRate.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); +// +// controller.updateMetavestUnlockRate(vestingAllocation, 0); // +// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); +// assertEq(updatedAllocation.unlockRate, 0); +// +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); +// controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); +// updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); +// assertEq(updatedAllocation.unlockRate, 0); +// } +// +// function testUpdateVestingRate() public { +// address vestingAllocation = createDummyVestingAllocation(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = controller.updateMetavestVestingRate.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, msgData); // vm.prank(grantee); -// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, true); +// +// controller.updateMetavestVestingRate(vestingAllocation, 20e18); +// +// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); +// assertEq(updatedAllocation.vestingRate, 20e18); // } - -// function testRepurchaseTokensFuture() public { +// +//// function testUpdateStopTimes() public { +//// +//// address vestingAllocation = createDummyRestrictedTokenAward(); +//// address[] memory addresses = new address[](1); +//// addresses[0] = vestingAllocation; +//// bytes4 selector = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); +//// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, uint48(block.timestamp + 500 days)); +//// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, msgData); +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, true); +//// uint48 newShortStopTime = uint48(block.timestamp + 500 days); +//// +//// controller.updateMetavestStopTimes(vestingAllocation, newShortStopTime); +//// } +// +// function testTerminateVesting() public { +// address vestingAllocation = createDummyVestingAllocation(); +// +// controller.terminateMetavestVesting(vestingAllocation); +// +// assertTrue(BaseAllocation(vestingAllocation).terminated()); +// } +// +//// function testRepurchaseTokens() public { +//// uint256 startingBalance = paymentToken.balanceOf(grantee); +//// address restrictedTokenAward = createDummyRestrictedTokenAward(); +//// uint256 repurchaseAmount = 5e18; +//// uint256 snapshot = token.balanceOf(authority); +//// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); +//// controller.terminateMetavestVesting(restrictedTokenAward); +//// paymentToken.approve(address(restrictedTokenAward), payment); +//// vm.warp(block.timestamp + 20 days); +//// vm.prank(authority); +//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +//// +//// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +//// +//// vm.prank(grantee); +//// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +//// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +//// } +// +//// function testRepurchaseTokensFuture() public { +//// uint256 startingBalance = paymentToken.balanceOf(grantee); +//// address restrictedTokenAward = createDummyRestrictedTokenAwardFuture(); +//// +//// uint256 snapshot = token.balanceOf(authority); +//// +//// controller.terminateMetavestVesting(restrictedTokenAward); +//// uint256 repurchaseAmount = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +//// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); +//// paymentToken.approve(address(restrictedTokenAward), payment); +//// vm.warp(block.timestamp + 20 days); +//// vm.prank(authority); +//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +//// +//// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +//// +//// vm.prank(grantee); +//// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +//// console.log(token.balanceOf(restrictedTokenAward)); +//// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +//// +//// } +// +// function testTerminateTokensFuture() public { // uint256 startingBalance = paymentToken.balanceOf(grantee); -// address restrictedTokenAward = createDummyRestrictedTokenAwardFuture(); +// address vestingAllocation = createDummyVestingAllocationLargeFuture(); // -// uint256 snapshot = token.balanceOf(authority); +// controller.terminateMetavestVesting(vestingAllocation); +// } // -// controller.terminateMetavestVesting(restrictedTokenAward); -// uint256 repurchaseAmount = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); -// paymentToken.approve(address(restrictedTokenAward), payment); -// vm.warp(block.timestamp + 20 days); -// vm.prank(authority); -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +// function testUpdateAuthority() public { +// address newAuthority = address(0x4); // -// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +// controller.initiateAuthorityUpdate(newAuthority); // -// vm.prank(grantee); -// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -// console.log(token.balanceOf(restrictedTokenAward)); -// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +// vm.prank(newAuthority); +// controller.acceptAuthorityRole(); // +// assertEq(controller.authority(), newAuthority); // } - - function testTerminateTokensFuture() public { - uint256 startingBalance = paymentToken.balanceOf(grantee); - address vestingAllocation = createDummyVestingAllocationLargeFuture(); - - controller.terminateMetavestVesting(vestingAllocation); - } - - function testUpdateAuthority() public { - address newAuthority = address(0x4); - - controller.initiateAuthorityUpdate(newAuthority); - - vm.prank(newAuthority); - controller.acceptAuthorityRole(); - - assertEq(controller.authority(), newAuthority); - } - - function testUpdateDao() public { - address newDao = address(0x5); - - vm.prank(dao); - controller.initiateDaoUpdate(newDao); - - vm.prank(newDao); - controller.acceptDaoRole(); - - assertEq(controller.dao(), newDao); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocation() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationNoUnlock() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: false, - complete: false, - conditionContracts: new address[](0) - }); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationSlowUnlock() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 5e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationLarge() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 0, - unlockingCliffCredit: 0, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationLargeFuture() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 0, - unlockingCliffCredit: 0, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp+2000), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp+2000) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - -// function createDummyTokenOptionAllocation() internal returns (address) { +// +// function testUpdateDao() public { +// address newDao = address(0x5); +// +// vm.prank(dao); +// controller.initiateDaoUpdate(newDao); +// +// vm.prank(newDao); +// controller.acceptDaoRole(); +// +// assertEq(controller.dao(), newDao); +// } +// +// // Helper functions to create dummy allocations for testing +// function createDummyVestingAllocation() internal returns (address) { // BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), +// tokenContract: address(zkToken), // tokenStreamTotal: 1000e18, // vestingCliffCredit: 100e18, // unlockingCliffCredit: 100e18, @@ -1790,24 +1631,23 @@ contract MetaVestControllerTest is Test { // conditionContracts: new address[](0) // }); // -// token.approve(address(controller), 2000e18); -// // return controller.createMetavest( -// metavestController.metavestType.TokenOption, +// metavestController.metavestType.Vesting, // grantee, // allocation, // milestones, -// 5e17, -// address(paymentToken), -// 1 days, +// 0, +// address(0), +// 0, // 0 +// // ); // } - - -// function createDummyRestrictedTokenAward() internal returns (address) { +// +// // Helper functions to create dummy allocations for testing +// function createDummyVestingAllocationNoUnlock() internal returns (address) { // BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), +// tokenContract: address(zkToken), // tokenStreamTotal: 1000e18, // vestingCliffCredit: 100e18, // unlockingCliffCredit: 100e18, @@ -1820,36 +1660,35 @@ contract MetaVestControllerTest is Test { // BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); // milestones[0] = BaseAllocation.Milestone({ // milestoneAward: 1000e18, -// unlockOnCompletion: true, +// unlockOnCompletion: false, // complete: false, // conditionContracts: new address[](0) // }); // -// token.approve(address(controller), 2100e18); -// // return controller.createMetavest( -// metavestController.metavestType.RestrictedTokenAward, +// metavestController.metavestType.Vesting, // grantee, // allocation, // milestones, -// 1e18, -// address(paymentToken), -// 1 days, +// 0, +// address(0), +// 0, // 0 // // ); // } // -// function createDummyRestrictedTokenAwardFuture() internal returns (address) { +// // Helper functions to create dummy allocations for testing +// function createDummyVestingAllocationSlowUnlock() internal returns (address) { // BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), +// tokenContract: address(zkToken), // tokenStreamTotal: 1000e18, // vestingCliffCredit: 100e18, // unlockingCliffCredit: 100e18, // vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp+1000), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp+1000) +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 5e18, +// unlockStartTime: uint48(block.timestamp) // }); // // BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); @@ -1860,580 +1699,741 @@ contract MetaVestControllerTest is Test { // conditionContracts: new address[](0) // }); // -// token.approve(address(controller), 2100e18); +// return controller.createMetavest( +// metavestController.metavestType.Vesting, +// grantee, +// allocation, +// milestones, +// 0, +// address(0), +// 0, +// 0 +// +// ); +// } +// +// // Helper functions to create dummy allocations for testing +// function createDummyVestingAllocationLarge() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 0, +// unlockingCliffCredit: 0, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); +// // // return controller.createMetavest( -// metavestController.metavestType.RestrictedTokenAward, +// metavestController.metavestType.Vesting, // grantee, // allocation, // milestones, -// 1e18, -// address(paymentToken), -// 1 days, +// 0, +// address(0), +// 0, // 0 // // ); // } - - - function testGetMetaVestType() public { - address vestingAllocation = createDummyVestingAllocation(); -// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -// address restrictedTokenAward = createDummyRestrictedTokenAward(); - - assertEq(controller.getMetaVestType(vestingAllocation), 1); -// assertEq(controller.getMetaVestType(tokenOptionAllocation), 2); -// assertEq(controller.getMetaVestType(restrictedTokenAward), 3); - } - -// function testWithdrawFromController() public { -// uint256 amount = 100e18; -// token.transfer(address(controller), amount); // -// uint256 initialBalance = token.balanceOf(authority); -// controller.withdrawFromController(address(token)); -// uint256 finalBalance = token.balanceOf(authority); +// // Helper functions to create dummy allocations for testing +// function createDummyVestingAllocationLargeFuture() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 0, +// unlockingCliffCredit: 0, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp+2000), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp+2000) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); +// +// +// return controller.createMetavest( +// metavestController.metavestType.Vesting, +// grantee, +// allocation, +// milestones, +// 0, +// address(0), +// 0, +// 0 // -// assertEq(finalBalance - initialBalance, amount); -// assertEq(token.balanceOf(address(controller)), 0); +// ); // } - - function test_RevertIf_CreateMetavestWithZeroAddress() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(0), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ZeroAddress.selector)); - controller.createMetavest( - metavestController.metavestType.Vesting, - address(0), - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - function test_RevertIf_CreateMetavestWithInsufficientApproval() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - // Not approving any tokens - controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - } - - function testTerminateVestAndRecovers() public { - address vestingAllocation = createDummyVestingAllocation(); - uint256 snapshot = zkToken.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(zkToken.balanceOf(authority), 0); - } - - function testTerminateVestAndRecoverSlowUnlock() public { - address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = zkToken.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 25 seconds); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 25 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(zkToken.balanceOf(vestingAllocation), 0); - } - - function testTerminateRecoverAll() public { - address vestingAllocation = createDummyVestingAllocationLarge(); - uint256 snapshot = zkToken.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(zkToken.balanceOf(authority), 0); - } - - function testTerminateRecoverChunksBefore() public { - address vestingAllocation = createDummyVestingAllocationLarge(); - uint256 snapshot = zkToken.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - vm.warp(block.timestamp + 25 seconds); - - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(zkToken.balanceOf(authority), 0); - } - -// function testConfirmingMilestoneRestrictedTokenAllocation() public { -// address vestingAllocation = createDummyRestrictedTokenAward(); -// uint256 snapshot = token.balanceOf(authority); -// RestrictedTokenAward(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 50 seconds); -// vm.startPrank(grantee); -// RestrictedTokenAward(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); +// +//// function createDummyTokenOptionAllocation() internal returns (address) { +//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +//// tokenContract: address(token), +//// tokenStreamTotal: 1000e18, +//// vestingCliffCredit: 100e18, +//// unlockingCliffCredit: 100e18, +//// vestingRate: 10e18, +//// vestingStartTime: uint48(block.timestamp), +//// unlockRate: 10e18, +//// unlockStartTime: uint48(block.timestamp) +//// }); +//// +//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +//// milestones[0] = BaseAllocation.Milestone({ +//// milestoneAward: 1000e18, +//// unlockOnCompletion: true, +//// complete: false, +//// conditionContracts: new address[](0) +//// }); +//// +//// token.approve(address(controller), 2000e18); +//// +//// return controller.createMetavest( +//// metavestController.metavestType.TokenOption, +//// grantee, +//// allocation, +//// milestones, +//// 5e17, +//// address(paymentToken), +//// 1 days, +//// 0 +//// ); +//// } +// +// +//// function createDummyRestrictedTokenAward() internal returns (address) { +//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +//// tokenContract: address(token), +//// tokenStreamTotal: 1000e18, +//// vestingCliffCredit: 100e18, +//// unlockingCliffCredit: 100e18, +//// vestingRate: 10e18, +//// vestingStartTime: uint48(block.timestamp), +//// unlockRate: 10e18, +//// unlockStartTime: uint48(block.timestamp) +//// }); +//// +//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +//// milestones[0] = BaseAllocation.Milestone({ +//// milestoneAward: 1000e18, +//// unlockOnCompletion: true, +//// complete: false, +//// conditionContracts: new address[](0) +//// }); +//// +//// token.approve(address(controller), 2100e18); +//// +//// return controller.createMetavest( +//// metavestController.metavestType.RestrictedTokenAward, +//// grantee, +//// allocation, +//// milestones, +//// 1e18, +//// address(paymentToken), +//// 1 days, +//// 0 +//// +//// ); +//// } +//// +//// function createDummyRestrictedTokenAwardFuture() internal returns (address) { +//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +//// tokenContract: address(token), +//// tokenStreamTotal: 1000e18, +//// vestingCliffCredit: 100e18, +//// unlockingCliffCredit: 100e18, +//// vestingRate: 10e18, +//// vestingStartTime: uint48(block.timestamp+1000), +//// unlockRate: 10e18, +//// unlockStartTime: uint48(block.timestamp+1000) +//// }); +//// +//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +//// milestones[0] = BaseAllocation.Milestone({ +//// milestoneAward: 1000e18, +//// unlockOnCompletion: true, +//// complete: false, +//// conditionContracts: new address[](0) +//// }); +//// +//// token.approve(address(controller), 2100e18); +//// +//// return controller.createMetavest( +//// metavestController.metavestType.RestrictedTokenAward, +//// grantee, +//// allocation, +//// milestones, +//// 1e18, +//// address(paymentToken), +//// 1 days, +//// 0 +//// +//// ); +//// } +// +// +// function testGetMetaVestType() public { +// address vestingAllocation = createDummyVestingAllocation(); +//// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +//// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// +// assertEq(controller.getMetaVestType(vestingAllocation), 1); +//// assertEq(controller.getMetaVestType(tokenOptionAllocation), 2); +//// assertEq(controller.getMetaVestType(restrictedTokenAward), 3); // } // -// function testConfirmingMilestoneTokenOption() public { -// address vestingAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); -// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +//// function testWithdrawFromController() public { +//// uint256 amount = 100e18; +//// token.transfer(address(controller), amount); +//// +//// uint256 initialBalance = token.balanceOf(authority); +//// controller.withdrawFromController(address(token)); +//// uint256 finalBalance = token.balanceOf(authority); +//// +//// assertEq(finalBalance - initialBalance, amount); +//// assertEq(token.balanceOf(address(controller)), 0); +//// } +// +// function test_RevertIf_CreateMetavestWithZeroAddress() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(0), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); +// +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ZeroAddress.selector)); +// controller.createMetavest( +// metavestController.metavestType.Vesting, +// address(0), +// allocation, +// milestones, +// 0, +// address(0), +// 0, +// 0 +// +// ); +// } +// +// function test_RevertIf_CreateMetavestWithInsufficientApproval() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); +// +// // Not approving any tokens +// controller.createMetavest( +// metavestController.metavestType.Vesting, +// grantee, +// allocation, +// milestones, +// 0, +// address(0), +// 0, +// 0 +// +// ); +// } +// +// function testTerminateVestAndRecovers() public { +// address vestingAllocation = createDummyVestingAllocation(); +// uint256 snapshot = zkToken.balanceOf(authority); +// VestingAllocation(vestingAllocation).confirmMilestone(0); // vm.warp(block.timestamp + 50 seconds); +// controller.terminateMetavestVesting(vestingAllocation); // vm.startPrank(grantee); -// //exercise max available -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); +// assertEq(zkToken.balanceOf(authority), 0); // } - - function testUnlockMilestoneNotUnlocked() public { - address vestingAllocation = createDummyVestingAllocationNoUnlock(); - uint256 snapshot = zkToken.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 1050 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - -// function testTerminateTokenOptionAndRecover() public { -// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); +// +// function testTerminateVestAndRecoverSlowUnlock() public { +// address vestingAllocation = createDummyVestingAllocationSlowUnlock(); +// uint256 snapshot = zkToken.balanceOf(authority); +// VestingAllocation(vestingAllocation).confirmMilestone(0); // vm.warp(block.timestamp + 25 seconds); -// vm.prank(grantee); -// ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); -// vm.prank(grantee); -// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18); -// controller.terminateMetavestVesting(tokenOptionAllocation); +// controller.terminateMetavestVesting(vestingAllocation); // vm.startPrank(grantee); -// vm.warp(block.timestamp + 1 days + 25 seconds); -// assertEq(TokenOptionAllocation(tokenOptionAllocation).getAmountExercisable(), 0); -// TokenOptionAllocation(tokenOptionAllocation).withdraw(TokenOptionAllocation(tokenOptionAllocation).getAmountWithdrawable()); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.warp(block.timestamp + 25 seconds); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// assertEq(token.balanceOf(tokenOptionAllocation), 0); -// vm.warp(block.timestamp + 365 days); -// vm.prank(authority); -// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +// assertEq(zkToken.balanceOf(vestingAllocation), 0); // } - -// function testTerminateEarlyTokenOptionAndRecover() public { -// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); -// vm.warp(block.timestamp + 5 seconds); -// // vm.prank(grantee); -// /* ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); -// vm.prank(grantee); -// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18);*/ -// controller.terminateMetavestVesting(tokenOptionAllocation); -// vm.warp(block.timestamp + 365 days); -// vm.prank(authority); -// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +// +// function testTerminateRecoverAll() public { +// address vestingAllocation = createDummyVestingAllocationLarge(); +// uint256 snapshot = zkToken.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// controller.terminateMetavestVesting(vestingAllocation); +// vm.startPrank(grantee); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// assertEq(zkToken.balanceOf(authority), 0); // } - - -// function testTerminateRestrictedTokenAwardAndRecover() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// uint256 snapshot = token.balanceOf(authority); -// vm.warp(block.timestamp + 25 seconds); -// controller.terminateMetavestVesting(restrictedTokenAward); +// +// function testTerminateRecoverChunksBefore() public { +// address vestingAllocation = createDummyVestingAllocationLarge(); +// uint256 snapshot = zkToken.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// vm.startPrank(grantee); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// vm.warp(block.timestamp + 25 seconds); +// +// controller.terminateMetavestVesting(vestingAllocation); // vm.startPrank(grantee); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); -// vm.warp(block.timestamp + 20 days); -// paymentToken.approve(address(restrictedTokenAward), payamt); -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// assertEq(zkToken.balanceOf(authority), 0); +// } +// +//// function testConfirmingMilestoneRestrictedTokenAllocation() public { +//// address vestingAllocation = createDummyRestrictedTokenAward(); +//// uint256 snapshot = token.balanceOf(authority); +//// RestrictedTokenAward(vestingAllocation).confirmMilestone(0); +//// vm.warp(block.timestamp + 50 seconds); +//// vm.startPrank(grantee); +//// RestrictedTokenAward(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +//// vm.stopPrank(); +//// } +//// +//// function testConfirmingMilestoneTokenOption() public { +//// address vestingAllocation = createDummyTokenOptionAllocation(); +//// uint256 snapshot = token.balanceOf(authority); +//// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +//// vm.warp(block.timestamp + 50 seconds); +//// vm.startPrank(grantee); +//// //exercise max available +//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +//// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +//// vm.stopPrank(); +//// } // +// function testUnlockMilestoneNotUnlocked() public { +// address vestingAllocation = createDummyVestingAllocationNoUnlock(); +// uint256 snapshot = zkToken.balanceOf(authority); +// VestingAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); // vm.startPrank(grantee); -// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -// assertEq(token.balanceOf(restrictedTokenAward), 0); -// assertEq(paymentToken.balanceOf(restrictedTokenAward), 0); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.warp(block.timestamp + 1050 seconds); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); // } - -// function testChangeVestingAndUnlockingRate() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// uint256 snapshot = token.balanceOf(authority); -// vm.warp(block.timestamp + 25 seconds); // -// bytes4 msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); +//// function testTerminateTokenOptionAndRecover() public { +//// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +//// uint256 snapshot = token.balanceOf(authority); +//// vm.warp(block.timestamp + 25 seconds); +//// vm.prank(grantee); +//// ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); +//// vm.prank(grantee); +//// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18); +//// controller.terminateMetavestVesting(tokenOptionAllocation); +//// vm.startPrank(grantee); +//// vm.warp(block.timestamp + 1 days + 25 seconds); +//// assertEq(TokenOptionAllocation(tokenOptionAllocation).getAmountExercisable(), 0); +//// TokenOptionAllocation(tokenOptionAllocation).withdraw(TokenOptionAllocation(tokenOptionAllocation).getAmountWithdrawable()); +//// vm.stopPrank(); +//// assertEq(token.balanceOf(tokenOptionAllocation), 0); +//// vm.warp(block.timestamp + 365 days); +//// vm.prank(authority); +//// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +//// } // -// vm.prank(authority); -// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +//// function testTerminateEarlyTokenOptionAndRecover() public { +//// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +//// uint256 snapshot = token.balanceOf(authority); +//// vm.warp(block.timestamp + 5 seconds); +//// // vm.prank(grantee); +//// /* ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); +//// vm.prank(grantee); +//// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18);*/ +//// controller.terminateMetavestVesting(tokenOptionAllocation); +//// vm.warp(block.timestamp + 365 days); +//// vm.prank(authority); +//// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +//// } // -// vm.prank(grantee); -// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); // -// vm.prank(authority); -// controller.updateMetavestUnlockRate(restrictedTokenAward, 50e18); +//// function testTerminateRestrictedTokenAwardAndRecover() public { +//// address restrictedTokenAward = createDummyRestrictedTokenAward(); +//// uint256 snapshot = token.balanceOf(authority); +//// vm.warp(block.timestamp + 25 seconds); +//// controller.terminateMetavestVesting(restrictedTokenAward); +//// vm.startPrank(grantee); +//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +//// vm.stopPrank(); +//// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +//// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +//// vm.warp(block.timestamp + 20 days); +//// paymentToken.approve(address(restrictedTokenAward), payamt); +//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +//// +//// vm.startPrank(grantee); +//// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +//// assertEq(token.balanceOf(restrictedTokenAward), 0); +//// assertEq(paymentToken.balanceOf(restrictedTokenAward), 0); +//// } // -// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); +//// function testChangeVestingAndUnlockingRate() public { +//// address restrictedTokenAward = createDummyRestrictedTokenAward(); +//// uint256 snapshot = token.balanceOf(authority); +//// vm.warp(block.timestamp + 25 seconds); +//// +//// bytes4 msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +//// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestUnlockRate(restrictedTokenAward, 50e18); +//// +//// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +//// callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestVestingRate(restrictedTokenAward, 50e18); +//// +//// vm.startPrank(grantee); +//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +//// vm.stopPrank(); +//// +//// } +// +//// function testZeroReclaim() public { +//// address restrictedTokenAward = createDummyRestrictedTokenAward(); +//// vm.warp(block.timestamp + 15 seconds); +//// vm.startPrank(grantee); +//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +//// vm.stopPrank(); +//// //create call data to propose setting vesting to 0 +//// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +//// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestVestingRate(restrictedTokenAward, 0); +//// +//// vm.startPrank(authority); +//// controller.terminateMetavestVesting(restrictedTokenAward); +//// vm.warp(block.timestamp + 155 days); +//// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +//// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +//// paymentToken.approve(address(restrictedTokenAward), payamt); +//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +//// vm.stopPrank(); +//// vm.prank(grantee); +//// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +//// console.log(token.balanceOf(restrictedTokenAward)); +//// } +// +// function testZeroReclaimVesting() public { +// address vestingAllocation = createDummyVestingAllocation(); +// vm.warp(block.timestamp + 15 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// //create call data to propose setting vesting to 0 +// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 0); // // vm.prank(authority); -// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); // // vm.prank(grantee); -// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); // // vm.prank(authority); -// controller.updateMetavestVestingRate(restrictedTokenAward, 50e18); +// controller.updateMetavestVestingRate(vestingAllocation, 0); // -// vm.startPrank(grantee); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.startPrank(authority); +// controller.terminateMetavestVesting(vestingAllocation); // vm.stopPrank(); -// // } - -// function testZeroReclaim() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// vm.warp(block.timestamp + 15 seconds); +// +// function testSlightReduc() public { +// address vestingAllocation = createDummyVestingAllocation(); +// vm.warp(block.timestamp + 5 seconds); // vm.startPrank(grantee); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); // //create call data to propose setting vesting to 0 // bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); +// bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 80e18); // // vm.prank(authority); -// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); // // vm.prank(grantee); -// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); // // vm.prank(authority); -// controller.updateMetavestVestingRate(restrictedTokenAward, 0); -// +// controller.updateMetavestVestingRate(vestingAllocation, 80e18); +// vm.warp(block.timestamp + 5 seconds); // vm.startPrank(authority); -// controller.terminateMetavestVesting(restrictedTokenAward); -// vm.warp(block.timestamp + 155 days); -// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); -// paymentToken.approve(address(restrictedTokenAward), payamt); -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); -// vm.stopPrank(); -// vm.prank(grantee); -// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -// console.log(token.balanceOf(restrictedTokenAward)); +// controller.terminateMetavestVesting(vestingAllocation); +// vm.stopPrank(); +// vm.warp(block.timestamp + 155 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); // } - - function testZeroReclaimVesting() public { - address vestingAllocation = createDummyVestingAllocation(); - vm.warp(block.timestamp + 15 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(vestingAllocation, 0); - - vm.startPrank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.stopPrank(); - } - - function testSlightReduc() public { - address vestingAllocation = createDummyVestingAllocation(); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 80e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(vestingAllocation, 80e18); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.stopPrank(); - vm.warp(block.timestamp + 155 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - - function testLargeReduc() public { - address vestingAllocation = createDummyVestingAllocation(); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 10e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(vestingAllocation, 10e18); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.stopPrank(); - vm.warp(block.timestamp + 155 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - -// function testLargeReducOption() public { -// address restrictedTokenAward = createDummyTokenOptionAllocation(); +// +// function testLargeReduc() public { +// address vestingAllocation = createDummyVestingAllocation(); // vm.warp(block.timestamp + 5 seconds); // vm.startPrank(grantee); -// //approve amount to exercise by getting amount to exercise and price -// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); -// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); // //create call data to propose setting vesting to 0 // bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); +// bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 10e18); // // vm.prank(authority); -// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); // // vm.prank(grantee); -// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); // // vm.prank(authority); -// controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); +// controller.updateMetavestVestingRate(vestingAllocation, 10e18); // vm.warp(block.timestamp + 5 seconds); // vm.startPrank(authority); -// controller.terminateMetavestVesting(restrictedTokenAward); +// controller.terminateMetavestVesting(vestingAllocation); // vm.stopPrank(); // vm.warp(block.timestamp + 155 seconds); // vm.startPrank(grantee); -// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); -// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// console.log(token.balanceOf(restrictedTokenAward)); // } - - - -// function testReclaim() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// vm.warp(block.timestamp + 15 seconds); -// vm.startPrank(grantee); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -// vm.stopPrank(); // -// vm.startPrank(authority); -// controller.terminateMetavestVesting(restrictedTokenAward); -// vm.warp(block.timestamp + 155 days); -// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); -// paymentToken.approve(address(restrictedTokenAward), payamt); -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); -// vm.stopPrank(); -// vm.prank(grantee); -// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -// console.log(token.balanceOf(restrictedTokenAward)); +//// function testLargeReducOption() public { +//// address restrictedTokenAward = createDummyTokenOptionAllocation(); +//// vm.warp(block.timestamp + 5 seconds); +//// vm.startPrank(grantee); +//// //approve amount to exercise by getting amount to exercise and price +//// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); +//// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); +//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +//// vm.stopPrank(); +//// //create call data to propose setting vesting to 0 +//// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +//// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); +//// +//// vm.prank(authority); +//// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +//// +//// vm.prank(grantee); +//// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +//// +//// vm.prank(authority); +//// controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); +//// vm.warp(block.timestamp + 5 seconds); +//// vm.startPrank(authority); +//// controller.terminateMetavestVesting(restrictedTokenAward); +//// vm.stopPrank(); +//// vm.warp(block.timestamp + 155 seconds); +//// vm.startPrank(grantee); +//// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); +//// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); +//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +//// vm.stopPrank(); +//// console.log(token.balanceOf(restrictedTokenAward)); +//// } +// +// +// +//// function testReclaim() public { +//// address restrictedTokenAward = createDummyRestrictedTokenAward(); +//// vm.warp(block.timestamp + 15 seconds); +//// vm.startPrank(grantee); +//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +//// vm.stopPrank(); +//// +//// vm.startPrank(authority); +//// controller.terminateMetavestVesting(restrictedTokenAward); +//// vm.warp(block.timestamp + 155 days); +//// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +//// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +//// paymentToken.approve(address(restrictedTokenAward), payamt); +//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +//// vm.stopPrank(); +//// vm.prank(grantee); +//// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +//// console.log(token.balanceOf(restrictedTokenAward)); +//// } +// +// +// +//// function test_RevertIf_UpdateExercisePriceForVesting() public { +//// address vestingAllocation = createDummyVestingAllocation(); +//// controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); +//// } +// +//// function test_RevertIf_RepurchaseTokensAfterExpiry() public { +//// address restrictedTokenAward = createDummyRestrictedTokenAward(); +//// +//// // Fast forward time to after the short stop date +//// vm.warp(block.timestamp + 366 days); +//// +//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +//// } +// +//// function test_RevertIf_RepurchaseTokensInsufficientAllowance() public { +//// address restrictedTokenAward = createDummyRestrictedTokenAward(); +//// +//// // Not approving any tokens +//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +//// } +// +// function test_RevertIf_InitiateAuthorityUpdateNonAuthority() public { +// vm.prank(address(0x1234)); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); +// controller.initiateAuthorityUpdate(address(0x5678)); // } - - - -// function test_RevertIf_UpdateExercisePriceForVesting() public { -// address vestingAllocation = createDummyVestingAllocation(); -// controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); +// +// function test_RevertIf_AcceptAuthorityRoleNonPendingAuthority() public { +// controller.initiateAuthorityUpdate(address(0x5678)); +// +// vm.prank(address(0x1234)); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingAuthority.selector)); +// controller.acceptAuthorityRole(); +// } +// +// function test_RevertIf_InitiateDaoUpdateNonDao() public { +// vm.prank(address(0x1234)); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); +// controller.initiateDaoUpdate(address(0x5678)); // } - -// function test_RevertIf_RepurchaseTokensAfterExpiry() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); // -// // Fast forward time to after the short stop date -// vm.warp(block.timestamp + 366 days); +// function test_RevertIf_AcceptDaoRoleNonPendingDao() public { +// vm.prank(dao); +// controller.initiateDaoUpdate(address(0x5678)); // -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +// vm.prank(address(0x1234)); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingDao.selector)); +// controller.acceptDaoRole(); // } - -// function test_RevertIf_RepurchaseTokensInsufficientAllowance() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); // -// // Not approving any tokens -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +// function testUpdateFunctionCondition() public { +// bytes4 functionSig = bytes4(keccak256("testFunction()")); +// /* constructor( +// address[] memory _signers, +// uint256 _threshold, +// Logic _logic +// ) */ +// address[] memory signers = new address[](2); +// signers[0] = address(0x1); +// signers[1] = address(0x2); +// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); +// +// vm.prank(dao); +// controller.updateFunctionCondition(address(condition), functionSig); +// +// assertEq(controller.functionToConditions(functionSig, 0), address(condition)); +// } +// +// function test_RevertIf_UpdateFunctionConditionNonDao() public { +// bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); +// address condition = address(0x1234); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); +// controller.updateFunctionCondition(condition, functionSig); +// } +// +// +// function testRemoveFunctionCondition() public { +// bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); +// /* constructor( +// address[] memory _signers, +// uint256 _threshold, +// Logic _logic +// ) */ +// address[] memory signers = new address[](2); +// signers[0] = address(0x1); +// signers[1] = address(0x2); +// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); +// +// vm.prank(dao); +// controller.updateFunctionCondition(address(condition), functionSig); +// assert(controller.functionToConditions(functionSig, 0) == address(condition)); +// vm.prank(dao); +// controller.removeFunctionCondition(address(condition), functionSig); +// } +// +// function test_RevertIf_CheckFunctionCondition() public { +// bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); +// /* constructor( +// address[] memory _signers, +// uint256 _threshold, +// Logic _logic +// ) */ +// address[] memory signers = new address[](2); +// signers[0] = address(0x1); +// signers[1] = address(0x2); +// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); +// +// vm.prank(dao); +// controller.updateFunctionCondition(address(condition), functionSig); +// assert(controller.functionToConditions(functionSig, 0) == address(condition)); +// //create a dummy metavest +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ConditionNotSatisfied.selector, condition)); +// address vestingAllocation = createDummyVestingAllocation(); +// } +// +// function test_RevertIf_AddDuplicateCondition() public { +// bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); +// /* constructor( +// address[] memory _signers, +// uint256 _threshold, +// Logic _logic +// ) */ +// address[] memory signers = new address[](2); +// signers[0] = address(0x1); +// signers[1] = address(0x2); +// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); +// +// vm.prank(dao); +// controller.updateFunctionCondition(address(condition), functionSig); +// assert(controller.functionToConditions(functionSig, 0) == address(condition)); +// vm.prank(dao); +// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_DuplicateCondition.selector)); +// controller.updateFunctionCondition(address(condition), functionSig); // } - - function test_RevertIf_InitiateAuthorityUpdateNonAuthority() public { - vm.prank(address(0x1234)); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); - controller.initiateAuthorityUpdate(address(0x5678)); - } - - function test_RevertIf_AcceptAuthorityRoleNonPendingAuthority() public { - controller.initiateAuthorityUpdate(address(0x5678)); - - vm.prank(address(0x1234)); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingAuthority.selector)); - controller.acceptAuthorityRole(); - } - - function test_RevertIf_InitiateDaoUpdateNonDao() public { - vm.prank(address(0x1234)); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); - controller.initiateDaoUpdate(address(0x5678)); - } - - function test_RevertIf_AcceptDaoRoleNonPendingDao() public { - vm.prank(dao); - controller.initiateDaoUpdate(address(0x5678)); - - vm.prank(address(0x1234)); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingDao.selector)); - controller.acceptDaoRole(); - } - - function testUpdateFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("testFunction()")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - - assertEq(controller.functionToConditions(functionSig, 0), address(condition)); - } - - function test_RevertIf_UpdateFunctionConditionNonDao() public { - bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); - address condition = address(0x1234); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); - controller.updateFunctionCondition(condition, functionSig); - } - - - function testRemoveFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - assert(controller.functionToConditions(functionSig, 0) == address(condition)); - vm.prank(dao); - controller.removeFunctionCondition(address(condition), functionSig); - } - - function test_RevertIf_CheckFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - assert(controller.functionToConditions(functionSig, 0) == address(condition)); - //create a dummy metavest - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ConditionNotSatisfied.selector, condition)); - address vestingAllocation = createDummyVestingAllocation(); - } - - function test_RevertIf_AddDuplicateCondition() public { - bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - assert(controller.functionToConditions(functionSig, 0) == address(condition)); - vm.prank(dao); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_DuplicateCondition.selector)); - controller.updateFunctionCondition(address(condition), functionSig); - } } From bc572a6c6421173b802570e901680753ae7636ae Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 31 Jul 2025 16:50:00 -0700 Subject: [PATCH 10/68] wip: feat: grantee registration --- src/MetaVesTController.sol | 85 ++++++++++++++++++++++++++------- test/ZkGuardianCompensation.sol | 69 +++++++++++++------------- 2 files changed, 103 insertions(+), 51 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 31bea8a..58ac8b0 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -65,6 +65,16 @@ contract metavestController is SafeTransferLib { mapping(address => uint256) voterPower; } + struct GranteeMeta { + uint256 id; + metavestType _metavestType; + address grantee; + address recipient; + BaseAllocation.Allocation allocation; + BaseAllocation.Milestone[] milestones; + string agreementUri; + } + enum metavestType { Vesting, TokenOption, RestrictedTokenAward } /// @notice maps a function's signature to a Condition contract address @@ -81,6 +91,9 @@ contract metavestController is SafeTransferLib { mapping(bytes32 => bool) public setMajorityVoteActive; + /// @notice granteeId => granteeData + mapping(uint256 => GranteeMeta) public pendingGrantees; + /// /// EVENTS /// @@ -96,8 +109,9 @@ contract metavestController is SafeTransferLib { event MetaVesTController_SetRemoved(string indexed set); event MetaVesTController_AddressAddedToSet(string set, address indexed grantee); event MetaVesTController_AddressRemovedFromSet(string set, address indexed grantee); - event MetaVesTController_MetaVestCreated(address indexed metavest); + event MetaVesTController_MetaVestCreated(address indexed metavest, uint256 granteeId); event MetaVesTController_ZkCappedMinterUpdated(address zkCappedMinter); + event MetaVesTController_GranteeRegistered(address indexed grantee, address indexed recipient, uint256 granteeId, metavestType metavestType, BaseAllocation.Allocation allocation, BaseAllocation.Milestone[] milestones); /// /// ERRORS @@ -132,6 +146,7 @@ contract metavestController is SafeTransferLib { error MetaVestController_MetaVestNotInSet(); error MetaVesTController_SetAlreadyExists(); error MetaVesTController_StringTooLong(); + error MetaVesTController_TypeNotSupported(metavestType _type); /// /// FUNCTIONS @@ -230,37 +245,73 @@ contract metavestController is SafeTransferLib { emit MetaVesTController_ConditionUpdated(_condition, _functionSig); } - function createMetavest(metavestType _type, address _grantee, BaseAllocation.Allocation calldata _allocation, BaseAllocation.Milestone[] calldata _milestones, uint256 _exercisePrice, address _paymentToken, uint256 _shortStopDuration, uint256 _longStopDate) external conditionCheck returns (address) + function registerGrantee( + metavestType _metavestType, + address grantee, + address recipient, + BaseAllocation.Allocation calldata allocation, + BaseAllocation.Milestone[] calldata milestones, + string calldata agreementUri, + bytes calldata signature + ) external onlyAuthority returns (uint256) { + // TODO WIP + // TODO Verify signature + uint256 granteeId = metavestCounter++; + + // TODO test + BaseAllocation.Milestone[] memory milestones2 = new BaseAllocation.Milestone[](0); + + pendingGrantees[granteeId] = GranteeMeta({ + id: granteeId, + _metavestType: _metavestType, + grantee: grantee, + recipient: recipient, + allocation: allocation, + milestones: milestones2, + agreementUri: agreementUri + }); + emit MetaVesTController_GranteeRegistered(grantee, recipient, granteeId, _metavestType, allocation, milestones); + return granteeId; + } + + function createMetavest(uint256 granteeId) external conditionCheck returns (address) { + GranteeMeta storage granteeMeta = pendingGrantees[granteeId]; + require(granteeMeta.grantee != address(0), "Grantee not registered"); + + // TODO test + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); address newMetavest; - if(_type == metavestType.Vesting) + if(granteeMeta._metavestType == metavestType.Vesting) { - newMetavest = createVestingAllocation(_grantee, _allocation, _milestones); + // TODO WIP: must support recipient + newMetavest = createVestingAllocation(granteeMeta.grantee, granteeMeta.allocation, milestones); // TODO use test milestones for now } - else if(_type == metavestType.TokenOption) + else if(granteeMeta._metavestType == metavestType.TokenOption) { - // TODO WIP adopt ZkCappedMinter v2 - newMetavest = createTokenOptionAllocation(_grantee, _exercisePrice, _paymentToken, _shortStopDuration, _allocation, _milestones); + revert MetaVesTController_TypeNotSupported(granteeMeta._metavestType); } - else if(_type == metavestType.RestrictedTokenAward) + else if(granteeMeta._metavestType == metavestType.RestrictedTokenAward) { - // TODO WIP adopt ZkCappedMinter v2 - newMetavest = createRestrictedTokenAward(_grantee, _exercisePrice, _paymentToken, _shortStopDuration, _allocation, _milestones); + revert MetaVesTController_TypeNotSupported(granteeMeta._metavestType); } else { revert MetaVesTController_IncorrectMetaVesTType(); } - uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); - uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; // Grant MetaVesT minter privilege IZkCappedMinterV2(zkCappedMinter).grantRole( IZkCappedMinterV2(zkCappedMinter).MINTER_ROLE(), newMetavest ); BaseAllocation(newMetavest).setZkCappedMinterAddress(address(zkCappedMinter)); - emit MetaVesTController_MetaVestCreated(newMetavest); + + // TODO revision needed + // Remove from queue + delete pendingGrantees[granteeId]; + + emit MetaVesTController_MetaVestCreated(newMetavest, granteeId); return newMetavest; } @@ -269,13 +320,13 @@ contract metavestController is SafeTransferLib { address _grantee, address _paymentToken, uint256 _exercisePrice, - VestingAllocation.Allocation calldata _allocation + VestingAllocation.Allocation memory _allocation ) internal pure { if (_grantee == address(0) || _allocation.tokenContract == address(0) || _paymentToken == address(0) || _exercisePrice == 0) revert MetaVesTController_ZeroAddress(); } - function validateAllocation(VestingAllocation.Allocation calldata _allocation) internal pure { + function validateAllocation(VestingAllocation.Allocation memory _allocation) internal pure { if ( _allocation.vestingCliffCredit > _allocation.tokenStreamTotal || _allocation.unlockingCliffCredit > _allocation.tokenStreamTotal @@ -283,7 +334,7 @@ contract metavestController is SafeTransferLib { } function validateAndCalculateMilestones( - VestingAllocation.Milestone[] calldata _milestones + VestingAllocation.Milestone[] memory _milestones ) internal pure returns (uint256 _milestoneTotal) { if (_milestones.length != 0) { if (_milestones.length > ARRAY_LENGTH_LIMIT) revert MetaVesTController_LengthMismatch(); @@ -344,7 +395,7 @@ contract metavestController is SafeTransferLib { } - function createVestingAllocation(address _grantee, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal returns (address){ + function createVestingAllocation(address _grantee, VestingAllocation.Allocation memory _allocation, VestingAllocation.Milestone[] memory _milestones) internal returns (address){ //hard code values not to trigger the failure for the 2 parameters that don't matter for this type of allocation validateInputParameters(_grantee, address(this), 1, _allocation); validateAllocation(_allocation); diff --git a/test/ZkGuardianCompensation.sol b/test/ZkGuardianCompensation.sol index 283adff..ef37fe6 100644 --- a/test/ZkGuardianCompensation.sol +++ b/test/ZkGuardianCompensation.sol @@ -10,9 +10,15 @@ import "forge-std/Test.sol"; // TODO this does not use the actual ZkCappedMinterV2 yet. Still v1 contract ZkGuardianCompensationTest is Test { - address zkTokenAdmin; - IZkTokenV1 zkToken; - IZkCappedMinterV2Factory zkCappedMinterFactory; + // zkSync Era Sepolia @ 5576300 + address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; + IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); + IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); +// // zkSync Era mainnet +// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; +// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); +// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); + IZkCappedMinterV2 zkCappedMinter; address deployer = address(0x2); @@ -33,15 +39,6 @@ contract ZkGuardianCompensationTest is Test { uint48 cappedMinterExpirationTime = uint48(block.timestamp + 30 days + 365 days * 2); function setUp() public { - // zkSync Era Sepolia @ 5576300 - zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; - zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); - zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); -// // zkSync Era mainnet -// zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; -// zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); -// zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); - vm.startPrank(deployer); // Deploy MetaVesT controller @@ -75,17 +72,18 @@ contract ZkGuardianCompensationTest is Test { controller.setZkCappedMinter(address(zkCappedMinter)); } - // Test by forge test --zksync --fork-url https://zksync-sepolia.g.alchemy.com/v2/ --mc MetaVesTZkCappedMinterV2Test + // Test by forge test --zksync --via-ir function test_GuardianCompensationYear1_2() public { - // TODO guardians to sign agreements + // Assume ZK Capped Minter and its MetaVesTController counterpart are already deployed - // Create MetaVesT for Alice and Bob + // Guardians to sign agreements and register on MetaVesTController BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest( + vm.startPrank(guardianSafe); + controller.registerGrantee( metavestController.metavestType.Vesting, alice, + alice, BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000e18, @@ -97,16 +95,13 @@ contract ZkGuardianCompensationTest is Test { unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter }), milestones, - 0, - address(0), - 0, - 0 - )); - assertEq(zkToken.balanceOf(address(vestingAllocationAlice)), 0, "Alice's vesting contract should not have any token (it mints on-demand)"); - - VestingAllocation vestingAllocationBob = VestingAllocation(controller.createMetavest( + "ipfs.io/ipfs/[cid]", + hex"1234" // TODO WIP: signature of all arguments above + ); + controller.registerGrantee( metavestController.metavestType.Vesting, bob, + bob, BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 2000e18, @@ -118,26 +113,32 @@ contract ZkGuardianCompensationTest is Test { unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter }), milestones, - 0, - address(0), - 0, - 0 - )); - assertEq(zkToken.balanceOf(address(vestingAllocationBob)), 0, "Vesting contract should not have any token (it mints on-demand)"); + "ipfs.io/ipfs/[cid]", + hex"1234" // TODO WIP: signature of all arguments above + ); + vm.stopPrank(); - // Simulate TPP approval and ZK Token admin to grant our ZkCappedMinter access + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions bytes32 minterRole = zkToken.MINTER_ROLE(); vm.prank(zkTokenAdmin); zkToken.grantRole(minterRole, address(zkCappedMinter)); - // Wait until capped minter start time and simulate grantee withdrawing cliff amounts + // Anyone can create MetaVesT for Alice and Bob (per agreements) to start vesting + + VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(0)); + assertEq(zkToken.balanceOf(address(vestingAllocationAlice)), 0, "Alice's vesting contract should not have any token (it mints on-demand)"); + + VestingAllocation vestingAllocationBob = VestingAllocation(controller.createMetavest(1)); + assertEq(zkToken.balanceOf(address(vestingAllocationBob)), 0, "Vesting contract should not have any token (it mints on-demand)"); + + // Alice and Bob should be able to start withdrawal after capped minter and MetaVesT start skip(30 days); granteeWithdrawAndAsserts(vestingAllocationAlice, 100e18, "Alice cliff"); granteeWithdrawAndAsserts(vestingAllocationBob, 200e18, "Bob cliff"); - // Wait until fully vested and unlocked and simulate full withdrawal + // Alice and Bob should be able to withdrawal all remaining tokens after sufficient time passed skip(90 seconds); granteeWithdrawAndAsserts(vestingAllocationAlice, 900e18, "Alice full"); From b02dc6bf3bbca6aa592e452d16f012b1b3355e72 Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 1 Aug 2025 13:19:29 -0700 Subject: [PATCH 11/68] feat: agreement signature --- src/MetaVesTController.sol | 244 ++++++++++++++++++++++++++------ test/ZkGuardianCompensation.sol | 118 ++++++++++++--- test/lib/MetaVesTUtils.sol | 83 +++++++++++ 3 files changed, 385 insertions(+), 60 deletions(-) create mode 100644 test/lib/MetaVesTUtils.sol diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 58ac8b0..9a4026a 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -8,7 +8,7 @@ pragma solidity 0.8.24; -//import "./MetaVesT.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "./BaseAllocation.sol"; import "./RestrictedTokenAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; @@ -27,8 +27,19 @@ import "./lib/EnumberableSet.sol"; * by an applicable affected grantee or a majority-in-governing power of similar token grantees **/ contract metavestController is SafeTransferLib { + using ECDSA for bytes32; using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.Bytes32Set; + + // Domain information + string public constant name = "metavestController"; + string public version; + bytes32 public DOMAIN_SEPARATOR; + // Type hash for SignedAgreementData + bytes32 public SIGNED_AGREEMENT_DATA_TYPEHASH; + bytes32 public ALLOCATION_TYPEHASH; + bytes32 public MILESTONE_TYPEHASH; + /// @dev opinionated time limit for a MetaVesT amendment, one calendar week in seconds uint256 internal constant AMENDMENT_TIME_LIMIT = 604800; uint256 internal constant ARRAY_LENGTH_LIMIT = 20; @@ -65,14 +76,19 @@ contract metavestController is SafeTransferLib { mapping(address => uint256) voterPower; } - struct GranteeMeta { - uint256 id; + struct AgreementData { + SignedAgreementData signedData; + bool pending; // Pending MeteVesT deployment + } + + struct SignedAgreementData { + bytes32 id; + string agreementUri; metavestType _metavestType; address grantee; address recipient; BaseAllocation.Allocation allocation; BaseAllocation.Milestone[] milestones; - string agreementUri; } enum metavestType { Vesting, TokenOption, RestrictedTokenAward } @@ -92,7 +108,7 @@ contract metavestController is SafeTransferLib { mapping(bytes32 => bool) public setMajorityVoteActive; /// @notice granteeId => granteeData - mapping(uint256 => GranteeMeta) public pendingGrantees; + mapping(bytes32 => AgreementData) public agreements; /// /// EVENTS @@ -109,9 +125,9 @@ contract metavestController is SafeTransferLib { event MetaVesTController_SetRemoved(string indexed set); event MetaVesTController_AddressAddedToSet(string set, address indexed grantee); event MetaVesTController_AddressRemovedFromSet(string set, address indexed grantee); - event MetaVesTController_MetaVestCreated(address indexed metavest, uint256 granteeId); + event MetaVesTController_MetaVestCreated(address indexed metavest, bytes32 contractId); event MetaVesTController_ZkCappedMinterUpdated(address zkCappedMinter); - event MetaVesTController_GranteeRegistered(address indexed grantee, address indexed recipient, uint256 granteeId, metavestType metavestType, BaseAllocation.Allocation allocation, BaseAllocation.Milestone[] milestones); + event MetaVesTController_ContractCreated(bytes32 indexed contractId, address indexed grantee, address indexed recipient, metavestType metavestType, BaseAllocation.Allocation allocation, BaseAllocation.Milestone[] milestones); /// /// ERRORS @@ -147,6 +163,8 @@ contract metavestController is SafeTransferLib { error MetaVesTController_SetAlreadyExists(); error MetaVesTController_StringTooLong(); error MetaVesTController_TypeNotSupported(metavestType _type); + error MetaVesTController_AgreementAlreadyProcessed(); + error MetaVesTController_SignatureVerificationFailed(); /// /// FUNCTIONS @@ -203,6 +221,31 @@ contract metavestController is SafeTransferLib { tokenOptionFactory = _tokenOptionFactory; restrictedTokenFactory = _restrictedTokenFactory; dao = _dao; + + version = "1"; + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), + keccak256(bytes(name)), + keccak256(bytes(version)), + block.chainid, + address(this) + ) + ); + + SIGNED_AGREEMENT_DATA_TYPEHASH = keccak256( + "SignedAgreementData(bytes32 id,string agreementUri,metavestType _metavestType,address grantee,address recipient,Allocation allocation,Milestone[] milestones)" + ); + + ALLOCATION_TYPEHASH = keccak256( + "Allocation(uint256 tokenStreamTotal,uint128 vestingCliffCredit,uint128 unlockingCliffCredit,uint160 vestingRate,uint48 vestingStartTime,uint160 unlockRate,uint48 unlockStartTime,address tokenContract)" + ); + + MILESTONE_TYPEHASH = keccak256( + "Milestone(uint256 milestoneAward,bool unlockOnCompletion,bool complete,address[] conditionContracts)" + ); } /// @notice for a grantee to consent to an update to one of their metavestDetails by 'authority' corresponding to the applicable function in this controller @@ -245,7 +288,8 @@ contract metavestController is SafeTransferLib { emit MetaVesTController_ConditionUpdated(_condition, _functionSig); } - function registerGrantee( + function createSignedContract( + uint256 salt, metavestType _metavestType, address grantee, address recipient, @@ -253,48 +297,66 @@ contract metavestController is SafeTransferLib { BaseAllocation.Milestone[] calldata milestones, string calldata agreementUri, bytes calldata signature - ) external onlyAuthority returns (uint256) { - // TODO WIP - // TODO Verify signature - uint256 granteeId = metavestCounter++; - - // TODO test - BaseAllocation.Milestone[] memory milestones2 = new BaseAllocation.Milestone[](0); - - pendingGrantees[granteeId] = GranteeMeta({ - id: granteeId, - _metavestType: _metavestType, - grantee: grantee, - recipient: recipient, - allocation: allocation, - milestones: milestones2, - agreementUri: agreementUri + ) external onlyAuthority returns (bytes32) { + bytes32 contractId = computeContractId(salt, agreementUri, grantee, recipient, allocation, milestones); + + // Verify signature + if (!_verifySignature( + grantee, + SignedAgreementData({ + id: contractId, + agreementUri: agreementUri, + _metavestType: _metavestType, + grantee: grantee, + recipient: recipient, + allocation: allocation, + milestones: milestones + }), + signature + )) { + revert MetaVesTController_SignatureVerificationFailed(); + } + + agreements[contractId] = AgreementData({ + signedData: SignedAgreementData({ + id: contractId, + agreementUri: agreementUri, + _metavestType: _metavestType, + grantee: grantee, + recipient: recipient, + allocation: allocation, + milestones: milestones + }), + pending: true }); - emit MetaVesTController_GranteeRegistered(grantee, recipient, granteeId, _metavestType, allocation, milestones); - return granteeId; + emit MetaVesTController_ContractCreated(contractId, grantee, recipient, _metavestType, allocation, milestones); + return contractId; } - function createMetavest(uint256 granteeId) external conditionCheck returns (address) + function createMetavest(bytes32 contractId) external conditionCheck returns (address) { - GranteeMeta storage granteeMeta = pendingGrantees[granteeId]; - require(granteeMeta.grantee != address(0), "Grantee not registered"); + AgreementData storage agreement = agreements[contractId]; + + if (!agreement.pending) { + revert MetaVesTController_AgreementAlreadyProcessed(); + } + agreement.pending = false; - // TODO test - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - address newMetavest; - if(granteeMeta._metavestType == metavestType.Vesting) + if(agreement.signedData._metavestType == metavestType.Vesting) { // TODO WIP: must support recipient - newMetavest = createVestingAllocation(granteeMeta.grantee, granteeMeta.allocation, milestones); // TODO use test milestones for now + newMetavest = createVestingAllocation(agreement.signedData.grantee, agreement.signedData.allocation, agreement.signedData.milestones); } - else if(granteeMeta._metavestType == metavestType.TokenOption) + else if(agreement.signedData._metavestType == metavestType.TokenOption) { - revert MetaVesTController_TypeNotSupported(granteeMeta._metavestType); + // TODO will be supported in the next stage + revert MetaVesTController_TypeNotSupported(agreement.signedData._metavestType); } - else if(granteeMeta._metavestType == metavestType.RestrictedTokenAward) + else if(agreement.signedData._metavestType == metavestType.RestrictedTokenAward) { - revert MetaVesTController_TypeNotSupported(granteeMeta._metavestType); + // TODO will be supported in the next stage + revert MetaVesTController_TypeNotSupported(agreement.signedData._metavestType); } else { @@ -307,11 +369,7 @@ contract metavestController is SafeTransferLib { ); BaseAllocation(newMetavest).setZkCappedMinterAddress(address(zkCappedMinter)); - // TODO revision needed - // Remove from queue - delete pendingGrantees[granteeId]; - - emit MetaVesTController_MetaVestCreated(newMetavest, granteeId); + emit MetaVesTController_MetaVestCreated(newMetavest, contractId); return newMetavest; } @@ -818,4 +876,104 @@ contract metavestController is SafeTransferLib { zkCappedMinter = _zkCappedMinter; emit MetaVesTController_ZkCappedMinterUpdated(zkCappedMinter); } + + function computeContractId( + uint256 salt, + string memory agreementUri, + address grantee, + address recipient, + BaseAllocation.Allocation memory allocation, + BaseAllocation.Milestone[] memory milestones + ) public pure returns (bytes32) { + return keccak256(abi.encode(salt, agreementUri, grantee, recipient, allocation, milestones)); + } + + function _verifySignature( + address signer, + SignedAgreementData memory data, + bytes memory signature + ) internal view returns (bool) { + // Hash the data (AgreementData) according to EIP-712 + bytes32 digest = _hashTypedDataV4(data); + + // Recover the signer address + address recoveredSigner = digest.recover(signature); + + // Check direct signature + if (recoveredSigner == signer) { + return true; + } + + // TODO WIP: delegation signature + // Check delegation signature +// Delegation storage delegation = delegations[signer]; +// if (delegation.delegate == recoveredSigner && +// (delegation.expiry == 0 || delegation.expiry > block.timestamp)) { +// return true; +// } + + return false; + } + + function _hashTypedDataV4(metavestController.SignedAgreementData memory data) internal view returns(bytes32) { + return keccak256(abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256(abi.encode( + SIGNED_AGREEMENT_DATA_TYPEHASH, + data.id, + keccak256(bytes(data.agreementUri)), + data._metavestType, + data.grantee, + data.recipient, + _hashAllocaiton(data.allocation), + _hashMilestones(data.milestones) + )) + )); + } + + function _hashSignedAgreementData(metavestController.SignedAgreementData memory data) internal view returns (bytes32) { + return keccak256(abi.encode( + SIGNED_AGREEMENT_DATA_TYPEHASH, + data.id, + keccak256(bytes(data.agreementUri)), + data._metavestType, + data.grantee, + data.recipient, + _hashAllocaiton(data.allocation), + _hashMilestones(data.milestones) + )); + } + + function _hashAllocaiton(BaseAllocation.Allocation memory allocation) internal view returns (bytes32) { + return keccak256(abi.encode( + ALLOCATION_TYPEHASH, + allocation.tokenContract, + allocation.tokenStreamTotal, + allocation.vestingCliffCredit, + allocation.unlockingCliffCredit, + allocation.vestingRate, + allocation.vestingStartTime, + allocation.unlockRate, + allocation.unlockStartTime + )); + } + + function _hashMilestones(BaseAllocation.Milestone[] memory milestones) internal view returns (bytes32) { + bytes32[] memory hashes = new bytes32[](milestones.length); + for (uint256 i = 0; i < milestones.length; i++) { + hashes[i] = _hashMilestone(milestones[i]); + } + return keccak256(abi.encodePacked(hashes)); + } + + function _hashMilestone(BaseAllocation.Milestone memory milestone) internal view returns (bytes32) { + return keccak256(abi.encode( + MILESTONE_TYPEHASH, + milestone.milestoneAward, + milestone.unlockOnCompletion, + milestone.complete, + keccak256(abi.encodePacked(milestone.conditionContracts)) + )); + } } diff --git a/test/ZkGuardianCompensation.sol b/test/ZkGuardianCompensation.sol index ef37fe6..b1a73ff 100644 --- a/test/ZkGuardianCompensation.sol +++ b/test/ZkGuardianCompensation.sol @@ -6,6 +6,7 @@ import "../src/RestrictedTokenFactory.sol"; import "../src/TokenOptionFactory.sol"; import "../src/VestingAllocationFactory.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "./lib/MetaVesTUtils.sol"; import "forge-std/Test.sol"; // TODO this does not use the actual ZkCappedMinterV2 yet. Still v1 @@ -23,8 +24,11 @@ contract ZkGuardianCompensationTest is Test { address deployer = address(0x2); address guardianSafe = address(0x3); - address alice = address(0x4); - address bob = address(0x5); + + uint256 alicePrivateKey = 1; + address alice = vm.addr(alicePrivateKey); + uint256 bobPrivateKey = 2; + address bob = vm.addr(bobPrivateKey); VestingAllocationFactory vestingAllocationFactory; TokenOptionFactory tokenOptionFactory; @@ -78,12 +82,12 @@ contract ZkGuardianCompensationTest is Test { // Guardians to sign agreements and register on MetaVesTController - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); vm.startPrank(guardianSafe); - controller.registerGrantee( - metavestController.metavestType.Vesting, - alice, + + bytes32 contractIdAlice = signAndCreateContract( alice, + alicePrivateKey, + "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000e18, @@ -94,14 +98,13 @@ contract ZkGuardianCompensationTest is Test { unlockRate: 10e18, unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter }), - milestones, - "ipfs.io/ipfs/[cid]", - hex"1234" // TODO WIP: signature of all arguments above + new BaseAllocation.Milestone[](0) ); - controller.registerGrantee( - metavestController.metavestType.Vesting, - bob, + + bytes32 contractIdBob = signAndCreateContract( bob, + bobPrivateKey, + "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 2000e18, @@ -112,10 +115,9 @@ contract ZkGuardianCompensationTest is Test { unlockRate: 20e18, unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter }), - milestones, - "ipfs.io/ipfs/[cid]", - hex"1234" // TODO WIP: signature of all arguments above + new BaseAllocation.Milestone[](0) ); + vm.stopPrank(); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions @@ -126,10 +128,10 @@ contract ZkGuardianCompensationTest is Test { // Anyone can create MetaVesT for Alice and Bob (per agreements) to start vesting - VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(0)); + VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); assertEq(zkToken.balanceOf(address(vestingAllocationAlice)), 0, "Alice's vesting contract should not have any token (it mints on-demand)"); - VestingAllocation vestingAllocationBob = VestingAllocation(controller.createMetavest(1)); + VestingAllocation vestingAllocationBob = VestingAllocation(controller.createMetavest(contractIdBob)); assertEq(zkToken.balanceOf(address(vestingAllocationBob)), 0, "Vesting contract should not have any token (it mints on-demand)"); // Alice and Bob should be able to start withdrawal after capped minter and MetaVesT start @@ -145,6 +147,28 @@ contract ZkGuardianCompensationTest is Test { granteeWithdrawAndAsserts(vestingAllocationBob, 1800e18, "Bob full"); } + function test_RevertIf_IncorrectAgreementSignature() public { + // Register Alice with someone else's signature should fail + vm.startPrank(guardianSafe); + signAndCreateContract( + alice, + bobPrivateKey, // Use someone else to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 10e18, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert + ); + } + function granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) public { address grantee = vestingAllocation.grantee(); uint256 balanceBefore = zkToken.balanceOf(grantee); @@ -154,4 +178,64 @@ contract ZkGuardianCompensationTest is Test { assertEq(zkToken.balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); } + + function signAndCreateContract( + address grantee, + uint256 granteePrivateKey, + string memory agreementUri, + BaseAllocation.Allocation memory allocation, + BaseAllocation.Milestone[] memory milestones + ) public returns(bytes32) { + return signAndCreateContract( + grantee, granteePrivateKey, agreementUri, allocation, milestones, + "" // Not expecting revert + ); + } + + function signAndCreateContract( + address grantee, + uint256 granteePrivateKey, + string memory agreementUri, + BaseAllocation.Allocation memory allocation, + BaseAllocation.Milestone[] memory milestones, + bytes memory expectRevertData + ) public returns(bytes32) { + uint256 contractSalt = block.timestamp; + bytes32 expectedContractId = controller.computeContractId(contractSalt, agreementUri, grantee, grantee, allocation, milestones); + bytes memory signature = MetaVesTUtils.signAgreementTypedData( + vm, + controller, + metavestController.SignedAgreementData({ + id: expectedContractId, + agreementUri: agreementUri, + _metavestType: metavestController.metavestType.Vesting, + grantee: grantee, + recipient: grantee, + allocation: allocation, + milestones: milestones + }), + granteePrivateKey + ); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + bytes32 contractId = controller.createSignedContract( + contractSalt, + metavestController.metavestType.Vesting, + grantee, + grantee, + allocation, + milestones, + agreementUri, + signature + ); + + if (expectRevertData.length == 0) { + assertEq(contractId, expectedContractId, "Unexpected contract ID"); + return contractId; + } else { + return 0; + } + } } diff --git a/test/lib/MetaVesTUtils.sol b/test/lib/MetaVesTUtils.sol new file mode 100644 index 0000000..247c8d5 --- /dev/null +++ b/test/lib/MetaVesTUtils.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.24; + +import "../../src/MetaVesTController.sol"; +import {Vm} from "forge-std/Test.sol"; + +library MetaVesTUtils { + function signAgreementTypedData( + Vm vm, + metavestController controller, + metavestController.SignedAgreementData memory data, + uint256 privKey + ) internal view returns (bytes memory signature) { + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + controller.DOMAIN_SEPARATOR(), + _hashSignedAgreementData(controller, data) + ) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, digest); + signature = abi.encodePacked(r, s, v); + return signature; + } + + function _hashSignedAgreementData( + metavestController controller, + metavestController.SignedAgreementData memory data + ) internal view returns (bytes32) { + return keccak256(abi.encode( + controller.SIGNED_AGREEMENT_DATA_TYPEHASH(), + data.id, + keccak256(bytes(data.agreementUri)), + data._metavestType, + data.grantee, + data.recipient, + _hashAllocaiton(controller, data.allocation), + _hashMilestones(controller, data.milestones) + )); + } + + function _hashAllocaiton( + metavestController controller, + BaseAllocation.Allocation memory allocation + ) internal view returns (bytes32) { + return keccak256(abi.encode( + controller.ALLOCATION_TYPEHASH(), + allocation.tokenContract, + allocation.tokenStreamTotal, + allocation.vestingCliffCredit, + allocation.unlockingCliffCredit, + allocation.vestingRate, + allocation.vestingStartTime, + allocation.unlockRate, + allocation.unlockStartTime + )); + } + + function _hashMilestones( + metavestController controller, + BaseAllocation.Milestone[] memory milestones + ) internal view returns (bytes32) { + bytes32[] memory hashes = new bytes32[](milestones.length); + for (uint256 i = 0; i < milestones.length; i++) { + hashes[i] = _hashMilestone(controller, milestones[i]); + } + return keccak256(abi.encodePacked(hashes)); + } + + function _hashMilestone( + metavestController controller, + BaseAllocation.Milestone memory milestone + ) internal view returns (bytes32) { + return keccak256(abi.encode( + controller.MILESTONE_TYPEHASH(), + milestone.milestoneAward, + milestone.unlockOnCompletion, + milestone.complete, + keccak256(abi.encodePacked(milestone.conditionContracts)) + )); + } +} From 5e98f8b3e41a64d7eb19c1acfcae3488007a6ff9 Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 1 Aug 2025 15:27:18 -0700 Subject: [PATCH 12/68] feat: signing delegation --- src/MetaVesTController.sol | 76 +++++++++++++++++++-- test/ZkGuardianCompensation.sol | 113 ++++++++++++++++++++++++++------ 2 files changed, 164 insertions(+), 25 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 9a4026a..4680b49 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -91,6 +91,11 @@ contract metavestController is SafeTransferLib { BaseAllocation.Milestone[] milestones; } + struct Delegation { + address delegate; + uint256 expiry; + } + enum metavestType { Vesting, TokenOption, RestrictedTokenAward } /// @notice maps a function's signature to a Condition contract address @@ -110,6 +115,8 @@ contract metavestController is SafeTransferLib { /// @notice granteeId => granteeData mapping(bytes32 => AgreementData) public agreements; + mapping(address => Delegation) public delegations; + /// /// EVENTS /// @@ -128,6 +135,8 @@ contract metavestController is SafeTransferLib { event MetaVesTController_MetaVestCreated(address indexed metavest, bytes32 contractId); event MetaVesTController_ZkCappedMinterUpdated(address zkCappedMinter); event MetaVesTController_ContractCreated(bytes32 indexed contractId, address indexed grantee, address indexed recipient, metavestType metavestType, BaseAllocation.Allocation allocation, BaseAllocation.Milestone[] milestones); + event MetaVesTController_DelegationSet(address indexed delegator, address indexed delegate, uint256 expiry); + event MetaVesTController_DelegationRevoked(address indexed delegator, address indexed delegate); /// /// ERRORS @@ -888,6 +897,62 @@ contract metavestController is SafeTransferLib { return keccak256(abi.encode(salt, agreementUri, grantee, recipient, allocation, milestones)); } + function getAgreement(bytes32 contractId) public view returns (AgreementData memory) { + return agreements[contractId]; + } + + function setDelegation(address delegate, uint256 expiry) external { + if (delegate == address(0)) revert("Cannot delegate to zero address"); + if (delegate == msg.sender) revert("Cannot delegate to self"); + if (expiry != 0 && expiry <= block.timestamp) revert("Expiry must be in the future"); + + delegations[msg.sender] = Delegation({delegate: delegate, expiry: expiry}); + emit MetaVesTController_DelegationSet(msg.sender, delegate, expiry); + } + + /** + * @dev Revoke delegation for the caller + */ + function revokeDelegation() external { + address delegate = delegations[msg.sender].delegate; + delete delegations[msg.sender]; + emit MetaVesTController_DelegationRevoked(msg.sender, delegate); + } + + /** + * @dev Get delegation info for a given address + * @param delegator The address to check for delegation + * @return delegate The delegate address + * @return expiry The expiry timestamp (0 if no expiry) + */ + function getDelegation(address delegator) external view returns (address delegate, uint256 expiry) { + Delegation storage delegation = delegations[delegator]; + return (delegation.delegate, delegation.expiry); + } + + /** + * @dev Check if a delegation is valid (not expired) + * @param delegator The delegator address + * @return True if delegation exists and is not expired + */ + function isValidDelegation(address delegator) external view returns (bool) { + Delegation storage delegation = delegations[delegator]; + return delegation.delegate != address(0) && + (delegation.expiry == 0 || delegation.expiry > block.timestamp); + } + + /** + * @dev Check if an address is a valid delegate for a given delegator + * @param delegator The delegator address + * @param delegate The delegate address to check + * @return True if the delegate is valid and not expired + */ + function isValidDelegate(address delegator, address delegate) external view returns (bool) { + Delegation storage delegation = delegations[delegator]; + return delegation.delegate == delegate && + (delegation.expiry == 0 || delegation.expiry > block.timestamp); + } + function _verifySignature( address signer, SignedAgreementData memory data, @@ -904,13 +969,12 @@ contract metavestController is SafeTransferLib { return true; } - // TODO WIP: delegation signature // Check delegation signature -// Delegation storage delegation = delegations[signer]; -// if (delegation.delegate == recoveredSigner && -// (delegation.expiry == 0 || delegation.expiry > block.timestamp)) { -// return true; -// } + Delegation storage delegation = delegations[signer]; + if (delegation.delegate == recoveredSigner && + (delegation.expiry == 0 || delegation.expiry > block.timestamp)) { + return true; + } return false; } diff --git a/test/ZkGuardianCompensation.sol b/test/ZkGuardianCompensation.sol index b1a73ff..ce1eea8 100644 --- a/test/ZkGuardianCompensation.sol +++ b/test/ZkGuardianCompensation.sol @@ -82,9 +82,8 @@ contract ZkGuardianCompensationTest is Test { // Guardians to sign agreements and register on MetaVesTController - vm.startPrank(guardianSafe); - - bytes32 contractIdAlice = signAndCreateContract( + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, alice, alicePrivateKey, "ipfs.io/ipfs/[cid]", @@ -101,7 +100,8 @@ contract ZkGuardianCompensationTest is Test { new BaseAllocation.Milestone[](0) ); - bytes32 contractIdBob = signAndCreateContract( + bytes32 contractIdBob = _signAndCreateContract( + guardianSafe, bob, bobPrivateKey, "ipfs.io/ipfs/[cid]", @@ -118,8 +118,6 @@ contract ZkGuardianCompensationTest is Test { new BaseAllocation.Milestone[](0) ); - vm.stopPrank(); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions bytes32 minterRole = zkToken.MINTER_ROLE(); @@ -137,20 +135,42 @@ contract ZkGuardianCompensationTest is Test { // Alice and Bob should be able to start withdrawal after capped minter and MetaVesT start skip(30 days); - granteeWithdrawAndAsserts(vestingAllocationAlice, 100e18, "Alice cliff"); - granteeWithdrawAndAsserts(vestingAllocationBob, 200e18, "Bob cliff"); + _granteeWithdrawAndAsserts(vestingAllocationAlice, 100e18, "Alice cliff"); + _granteeWithdrawAndAsserts(vestingAllocationBob, 200e18, "Bob cliff"); // Alice and Bob should be able to withdrawal all remaining tokens after sufficient time passed skip(90 seconds); - granteeWithdrawAndAsserts(vestingAllocationAlice, 900e18, "Alice full"); - granteeWithdrawAndAsserts(vestingAllocationBob, 1800e18, "Bob full"); + _granteeWithdrawAndAsserts(vestingAllocationAlice, 900e18, "Alice full"); + _granteeWithdrawAndAsserts(vestingAllocationBob, 1800e18, "Bob full"); + } + + function test_RevertIf_NotAuthority() public { + // Non Guardian SAFE should not be able to accept agreement and create contract + _signAndCreateContract( + deployer, // Not authority + alice, + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 10e18, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert + ); } function test_RevertIf_IncorrectAgreementSignature() public { // Register Alice with someone else's signature should fail - vm.startPrank(guardianSafe); - signAndCreateContract( + _signAndCreateContract( + guardianSafe, alice, bobPrivateKey, // Use someone else to sign "ipfs.io/ipfs/[cid]", @@ -169,7 +189,59 @@ contract ZkGuardianCompensationTest is Test { ); } - function granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) public { + function test_DelegateSignature() public { + // Alice to delegate to Bob + vm.prank(alice); + controller.setDelegation(bob, block.timestamp + 60); + assertTrue(controller.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); + + // Bob should be able to sign for Alice now + bytes32 contractId = _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use Bob to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 10e18, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0) + ); + metavestController.AgreementData memory agreement = controller.getAgreement(contractId); + assertEq(agreement.signedData.grantee, alice, "Alice should be the grantee"); + + // Wait until expiry + skip(61); + + // Bob should no longer be able to sign for Alice + assertFalse(controller.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); + _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use Bob to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 10e18, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert + ); + } + + function _granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) internal { address grantee = vestingAllocation.grantee(); uint256 balanceBefore = zkToken.balanceOf(grantee); assertEq(vestingAllocation.getAmountWithdrawable(), amount, string(abi.encodePacked(assertName, ": unexpected withdrawable amount after cliff"))); @@ -179,27 +251,29 @@ contract ZkGuardianCompensationTest is Test { assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); } - function signAndCreateContract( + function _signAndCreateContract( + address authority, address grantee, uint256 granteePrivateKey, string memory agreementUri, BaseAllocation.Allocation memory allocation, BaseAllocation.Milestone[] memory milestones - ) public returns(bytes32) { - return signAndCreateContract( - grantee, granteePrivateKey, agreementUri, allocation, milestones, + ) internal returns(bytes32) { + return _signAndCreateContract( + authority, grantee, granteePrivateKey, agreementUri, allocation, milestones, "" // Not expecting revert ); } - function signAndCreateContract( + function _signAndCreateContract( + address authority, address grantee, uint256 granteePrivateKey, string memory agreementUri, BaseAllocation.Allocation memory allocation, BaseAllocation.Milestone[] memory milestones, bytes memory expectRevertData - ) public returns(bytes32) { + ) internal returns(bytes32) { uint256 contractSalt = block.timestamp; bytes32 expectedContractId = controller.computeContractId(contractSalt, agreementUri, grantee, grantee, allocation, milestones); bytes memory signature = MetaVesTUtils.signAgreementTypedData( @@ -220,6 +294,7 @@ contract ZkGuardianCompensationTest is Test { if (expectRevertData.length > 0) { vm.expectRevert(expectRevertData); } + vm.prank(authority); bytes32 contractId = controller.createSignedContract( contractSalt, metavestController.metavestType.Vesting, From 9212f46c2407b9eeda60a5f9951dce74e2ffd811 Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 1 Aug 2025 19:27:38 -0700 Subject: [PATCH 13/68] feat: temporarily remove WIP metavest types. Add more realistic test cases --- src/MetaVesTController.sol | 182 ++++++++------- src/MetaVesTFactory.sol | 50 ---- .../zk-governance/IZkCappedMinterV2.sol | 2 + test/MetaVesTFactory.t.sol | 125 ---------- ...ation.sol => ZkGuardianCompensation.t.sol} | 221 ++++++++++++------ 5 files changed, 249 insertions(+), 331 deletions(-) delete mode 100644 src/MetaVesTFactory.sol delete mode 100644 test/MetaVesTFactory.t.sol rename test/{ZkGuardianCompensation.sol => ZkGuardianCompensation.t.sol} (64%) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 4680b49..5a762ef 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -51,8 +51,8 @@ contract metavestController is SafeTransferLib { address public authority; address public dao; address public vestingFactory; - address public tokenOptionFactory; - address public restrictedTokenFactory; +// address public tokenOptionFactory; +// address public restrictedTokenFactory; address public zkCappedMinter; address public ZkTokenAddress; address internal _pendingAuthority; @@ -223,12 +223,18 @@ contract metavestController is SafeTransferLib { /// @param _authority address of the authority who can call the functions in this contract and update each MetaVesT in '_metavest', such as a BORG /// @param _dao DAO governance contract address which exercises control over ability of 'authority' to call certain functions via imposing /// conditions through 'updateFunctionCondition'. - constructor(address _authority, address _dao, address _vestingFactory, address _tokenOptionFactory, address _restrictedTokenFactory) { + constructor( + address _authority, + address _dao, + address _vestingFactory +// address _tokenOptionFactory, +// address _restrictedTokenFactory + ) { if (_authority == address(0)) revert MetaVesTController_ZeroAddress(); authority = _authority; vestingFactory = _vestingFactory; - tokenOptionFactory = _tokenOptionFactory; - restrictedTokenFactory = _restrictedTokenFactory; +// tokenOptionFactory = _tokenOptionFactory; +// restrictedTokenFactory = _restrictedTokenFactory; dao = _dao; version = "1"; @@ -421,45 +427,45 @@ contract metavestController is SafeTransferLib { ) revert MetaVesTController_AmountNotApprovedForTransferFrom(); } - function createAndInitializeTokenOptionAllocation( - address _grantee, - address _paymentToken, - uint256 _exercisePrice, - uint256 _shortStopDuration, - VestingAllocation.Allocation calldata _allocation, - VestingAllocation.Milestone[] calldata _milestones - ) internal returns (address) { - return IAllocationFactory(tokenOptionFactory).createAllocation( - IAllocationFactory.AllocationType.TokenOption, - _grantee, - address(this), - _allocation, - _milestones, - _paymentToken, - _exercisePrice, - _shortStopDuration - ); - } - - function createAndInitializeRestrictedTokenAward( - address _grantee, - address _paymentToken, - uint256 _repurchasePrice, - uint256 _shortStopDuration, - VestingAllocation.Allocation calldata _allocation, - VestingAllocation.Milestone[] calldata _milestones - ) internal returns (address) { - return IAllocationFactory(restrictedTokenFactory).createAllocation( - IAllocationFactory.AllocationType.RestrictedToken, - _grantee, - address(this), - _allocation, - _milestones, - _paymentToken, - _repurchasePrice, - _shortStopDuration - ); - } +// function createAndInitializeTokenOptionAllocation( +// address _grantee, +// address _paymentToken, +// uint256 _exercisePrice, +// uint256 _shortStopDuration, +// VestingAllocation.Allocation calldata _allocation, +// VestingAllocation.Milestone[] calldata _milestones +// ) internal returns (address) { +// return IAllocationFactory(tokenOptionFactory).createAllocation( +// IAllocationFactory.AllocationType.TokenOption, +// _grantee, +// address(this), +// _allocation, +// _milestones, +// _paymentToken, +// _exercisePrice, +// _shortStopDuration +// ); +// } +// +// function createAndInitializeRestrictedTokenAward( +// address _grantee, +// address _paymentToken, +// uint256 _repurchasePrice, +// uint256 _shortStopDuration, +// VestingAllocation.Allocation calldata _allocation, +// VestingAllocation.Milestone[] calldata _milestones +// ) internal returns (address) { +// return IAllocationFactory(restrictedTokenFactory).createAllocation( +// IAllocationFactory.AllocationType.RestrictedToken, +// _grantee, +// address(this), +// _allocation, +// _milestones, +// _paymentToken, +// _repurchasePrice, +// _shortStopDuration +// ); +// } function createVestingAllocation(address _grantee, VestingAllocation.Allocation memory _allocation, VestingAllocation.Milestone[] memory _milestones) internal returns (address){ @@ -487,50 +493,50 @@ contract metavestController is SafeTransferLib { return vestingAllocation; } - function createTokenOptionAllocation(address _grantee, uint256 _exercisePrice, address _paymentToken, uint256 _shortStopDuration, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal conditionCheck returns (address) { - - validateInputParameters(_grantee, _paymentToken, _exercisePrice, _allocation); - validateAllocation(_allocation); - uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); - - uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; - if (_total == 0) revert MetaVesTController_ZeroAmount(); - //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); - - address tokenOptionAllocation = createAndInitializeTokenOptionAllocation( - _grantee, - _paymentToken, - _exercisePrice, - _shortStopDuration, - _allocation, - _milestones - ); - - //safeTransferFrom(_allocation.tokenContract, authority, tokenOptionAllocation, _total); - return tokenOptionAllocation; - } - - function createRestrictedTokenAward(address _grantee, uint256 _repurchasePrice, address _paymentToken, uint256 _shortStopDuration, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal conditionCheck returns (address){ - validateInputParameters(_grantee, _paymentToken, _repurchasePrice, _allocation); - validateAllocation(_allocation); - uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); - - uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; - if (_total == 0) revert MetaVesTController_ZeroAmount(); - //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); - - address restrictedTokenAward = createAndInitializeRestrictedTokenAward( - _grantee, - _paymentToken, - _repurchasePrice, - _shortStopDuration, - _allocation, - _milestones - ); - - //safeTransferFrom(_allocation.tokenContract, authority, restrictedTokenAward, _total); - return restrictedTokenAward; - } +// function createTokenOptionAllocation(address _grantee, uint256 _exercisePrice, address _paymentToken, uint256 _shortStopDuration, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal conditionCheck returns (address) { +// +// validateInputParameters(_grantee, _paymentToken, _exercisePrice, _allocation); +// validateAllocation(_allocation); +// uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); +// +// uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; +// if (_total == 0) revert MetaVesTController_ZeroAmount(); +// //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); +// +// address tokenOptionAllocation = createAndInitializeTokenOptionAllocation( +// _grantee, +// _paymentToken, +// _exercisePrice, +// _shortStopDuration, +// _allocation, +// _milestones +// ); +// +// //safeTransferFrom(_allocation.tokenContract, authority, tokenOptionAllocation, _total); +// return tokenOptionAllocation; +// } +// +// function createRestrictedTokenAward(address _grantee, uint256 _repurchasePrice, address _paymentToken, uint256 _shortStopDuration, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal conditionCheck returns (address){ +// validateInputParameters(_grantee, _paymentToken, _repurchasePrice, _allocation); +// validateAllocation(_allocation); +// uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); +// +// uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; +// if (_total == 0) revert MetaVesTController_ZeroAmount(); +// //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); +// +// address restrictedTokenAward = createAndInitializeRestrictedTokenAward( +// _grantee, +// _paymentToken, +// _repurchasePrice, +// _shortStopDuration, +// _allocation, +// _milestones +// ); +// +// //safeTransferFrom(_allocation.tokenContract, authority, restrictedTokenAward, _total); +// return restrictedTokenAward; +// } function getMetaVestType(address _grant) public view returns (uint256) { return BaseAllocation(_grant).getVestingType(); diff --git a/src/MetaVesTFactory.sol b/src/MetaVesTFactory.sol deleted file mode 100644 index dfbacaf..0000000 --- a/src/MetaVesTFactory.sol +++ /dev/null @@ -1,50 +0,0 @@ -//SPDX-License-Identifier: AGPL-3.0-only - -pragma solidity 0.8.24; - -/* -************************************ - MetaVesTFactory - ************************************ - */ - -import "./MetaVesTController.sol"; -interface IMetaVesTController { - function metavest() external view returns (address); -} - -/** - * @title MetaVesT Factory - * - * @notice Deploy a new instance of MetaVesTController, which in turn deploys a new MetaVesT it controls - * - **/ -contract MetaVesTFactory { - event MetaVesT_Deployment( - address newMetaVesT, - address authority, - address controller, - address dao, - address vestingAllocationFactory, - address tokenOptionFactory, - address restrictedTokenFactory - ); - - error MetaVesTFactory_ZeroAddress(); - - constructor() { } - - /// @notice constructs a MetaVesT framework specifying authority address, DAO staking/voting contract address - /// each individual grantee's MetaVesT will be initiated in the newly deployed MetaVesT contract, and deployed MetaVesTs are amendable by 'authority' via the controller contract - /// @dev conditionals are contained in the deployed MetaVesT, which is deployed in the MetaVesTController's constructor(); the MetaVesT within the MetaVesTController is immutable, but the 'authority' which has access control within the controller may replace itself - /// @param _authority: address which initiates and may update each MetaVesT, such as a BORG or DAO - /// @param _dao: contract address which token may be staked and used for voting, typically a DAO pool, governor, staking address. Submit address(0) for no such functionality. - function deployMetavestAndController(address _authority, address _dao, address _vestingAllocationFactory, address _tokenOptionFactory, address _restrictedTokenFactory ) external returns(address) { - if(_vestingAllocationFactory == address(0) || _tokenOptionFactory == address(0) || _restrictedTokenFactory == address(0)) - revert MetaVesTFactory_ZeroAddress(); - metavestController _controller = new metavestController(_authority, _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory); - emit MetaVesT_Deployment(address(0), _authority, address(_controller), _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory); - return address(_controller); - } - -} diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2.sol b/src/interfaces/zk-governance/IZkCappedMinterV2.sol index c971502..6113aa4 100644 --- a/src/interfaces/zk-governance/IZkCappedMinterV2.sol +++ b/src/interfaces/zk-governance/IZkCappedMinterV2.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.24; interface IZkCappedMinterV2 { + error ZkCappedMinterV2__CapExceeded(address minter, uint256 amount); + function MINTER_ROLE() external returns (bytes32); function START_TIME() external returns (uint48); diff --git a/test/MetaVesTFactory.t.sol b/test/MetaVesTFactory.t.sol deleted file mode 100644 index 0138255..0000000 --- a/test/MetaVesTFactory.t.sol +++ /dev/null @@ -1,125 +0,0 @@ -//SPDX-License-Identifier: AGPL-3.0-only - -pragma solidity ^0.8.18; - -import "../src/MetaVesTController.sol"; -import "../src/MetaVesTFactory.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/VestingAllocationFactory.sol"; -import "../src/interfaces/zk-governance/IZkTokenV1.sol"; -import "forge-std/Test.sol"; - -/// @dev foundry framework testing of MetaVesTFactory.sol -/// forge t --via-ir - -/// @notice test contract for MetaVesTFactory using Foundry -contract MetaVesTFactoryTest is Test { -// // zkSync Era Sepolia @ 5576300 -// address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; -// IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); -// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); -//// // zkSync Era mainnet -//// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; -//// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); -//// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); -// -// IZkCappedMinterV2 zkCappedMinter; -// -// MetaVesTFactory internal factory; -// metavestController controller; -// address factoryAddr; -// address dai_addr = 0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6; -// VestingAllocationFactory _factory;// = new VestingAllocationFactory(); -// RestrictedTokenFactory _factory2;// = new RestrictedTokenFactory(); -// TokenOptionFactory _factory3;// = new TokenOptionFactory(); -// -// bytes32 salt = keccak256("MetaVesTFactoryTest"); -// -// event MetaVesT_Deployment( -// address newMetaVesT, -// address authority, -// address controller, -// address dao, -// address paymentToken -// ); -// -// function setUp() public { -// _factory = new VestingAllocationFactory(); -// _factory2 = new RestrictedTokenFactory(); -// _factory3 = new TokenOptionFactory(); -// factory = new MetaVesTFactory(); -// factoryAddr = address(factory); -// address _authority = address(0xa); -// -// address _dao = address(0xB); -// address _paymentToken = address(0xC); -// -// controller = metavestController(factory.deployMetavestAndController(_authority, _dao, address(_factory), address(_factory2), address(_factory3))); -// -// // Deploy ZK Capped Minter v2 -// zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( -// address(zkToken), -// address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT -// 1000e18, -// uint48(block.timestamp), // start now -// uint48(block.timestamp + 365 days * 2), -// uint256(salt) -// )); -// -// vm.prank(_authority); -// controller.setZkCappedMinter(address(zkCappedMinter)); -// } -// -// function testDeployMetavestAndController() public { -// address _authority = address(0xa); -// address _dao = address(0xB); -// address _paymentToken = address(0xC); -// address grantee = address(0xD); -// RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); -// -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 100e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); -// -// //token.approve(address(controller), 1100e18); -// -// VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( -// metavestController.metavestType.Vesting, -// grantee, -// allocation, -// milestones, -// 0, -// address(0), -// 0, -// 0 -// -// )); -// -// assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); -// assertEq(vestingAllocation.getAmountWithdrawable(), 100e18, "Should be able to withdraw cliff amount"); -// } -// -// function test_RevertIf_ControllerZeroAddress() public { -// address _authority = address(0); -// address _dao = address(0); -// address _paymentToken = address(0); -// vm.expectRevert(abi.encodeWithSelector(MetaVesTFactory.MetaVesTFactory_ZeroAddress.selector)); -// factory.deployMetavestAndController(_authority, _dao, address(0), address(0), address(0)); -// } -} diff --git a/test/ZkGuardianCompensation.sol b/test/ZkGuardianCompensation.t.sol similarity index 64% rename from test/ZkGuardianCompensation.sol rename to test/ZkGuardianCompensation.t.sol index ce1eea8..bf96745 100644 --- a/test/ZkGuardianCompensation.sol +++ b/test/ZkGuardianCompensation.t.sol @@ -2,23 +2,22 @@ pragma solidity ^0.8.20; import "../src/MetaVesTController.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "../src/TokenOptionFactory.sol"; import "../src/VestingAllocationFactory.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; import "./lib/MetaVesTUtils.sol"; import "forge-std/Test.sol"; -// TODO this does not use the actual ZkCappedMinterV2 yet. Still v1 +// Test by forge test --zksync --via-ir contract ZkGuardianCompensationTest is Test { - // zkSync Era Sepolia @ 5576300 - address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; - IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); - IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); -// // zkSync Era mainnet -// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; -// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); -// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); +// // zkSync Era Sepolia @ 5576300 +// address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; +// IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); +// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); + // zkSync Era mainnet @ 63631890 + address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; + IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); + IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); IZkCappedMinterV2 zkCappedMinter; @@ -29,34 +28,33 @@ contract ZkGuardianCompensationTest is Test { address alice = vm.addr(alicePrivateKey); uint256 bobPrivateKey = 2; address bob = vm.addr(bobPrivateKey); + uint256 chadPrivateKey = 3; + address chad = vm.addr(chadPrivateKey); VestingAllocationFactory vestingAllocationFactory; - TokenOptionFactory tokenOptionFactory; - RestrictedTokenFactory restrictedTokenFactory; metavestController controller; // Parameters bytes32 salt = keccak256("MetaLexZkSyncTest"); - uint256 cap = 1e6 ether; - uint48 cappedMinterStartTime = uint48(block.timestamp + 30 days); - uint48 cappedMinterExpirationTime = uint48(block.timestamp + 30 days + 365 days * 2); + uint256 cap = 1e6 ether; // 1M ZK + uint48 cappedMinterStartTime = 1756684800; // 2025/9/1 UTC + uint48 cappedMinterExpirationTime = cappedMinterStartTime + 365 days * 2; // Expect to vest over an year with a margin of an extra year for withdrawal function setUp() public { + // Assume deployment 7 days before the vesting starts + vm.warp(cappedMinterStartTime - 7 days); + vm.startPrank(deployer); // Deploy MetaVesT controller vestingAllocationFactory = new VestingAllocationFactory(); - tokenOptionFactory = new TokenOptionFactory(); - restrictedTokenFactory = new RestrictedTokenFactory(); controller = new metavestController{salt: salt}( guardianSafe, guardianSafe, - address(vestingAllocationFactory), - address(tokenOptionFactory), - address(restrictedTokenFactory) + address(vestingAllocationFactory) ); // Deploy ZK Capped Minter v2 @@ -76,73 +74,111 @@ contract ZkGuardianCompensationTest is Test { controller.setZkCappedMinter(address(zkCappedMinter)); } - // Test by forge test --zksync --via-ir function test_GuardianCompensationYear1_2() public { // Assume ZK Capped Minter and its MetaVesTController counterpart are already deployed - // Guardians to sign agreements and register on MetaVesTController + (bytes32 contractIdAlice, bytes32 contractIdBob) = _guardiansSignAndTppPass(); - bytes32 contractIdAlice = _signAndCreateContract( + // Anyone can create MetaVesT for Alice and Bob (per agreements) to start vesting + + VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); + assertEq(zkToken.balanceOf(address(vestingAllocationAlice)), 0, "Alice's vesting contract should not have any token (it mints on-demand)"); + + VestingAllocation vestingAllocationBob = VestingAllocation(controller.createMetavest(contractIdBob)); + assertEq(zkToken.balanceOf(address(vestingAllocationBob)), 0, "Vesting contract should not have any token (it mints on-demand)"); + + // Grantees should not be able to withdraw yet + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_MoreThanAvailable.selector)); + vestingAllocationAlice.withdraw(1 ether); + vm.prank(bob); + vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_MoreThanAvailable.selector)); + vestingAllocationBob.withdraw(1 ether); + + // Grantees should be able to start withdrawal after capped minter and MetaVesT start + vm.warp(cappedMinterStartTime); + + _granteeWithdrawAndAsserts(vestingAllocationAlice, 50e3 ether, "Alice cliff"); + _granteeWithdrawAndAsserts(vestingAllocationBob, 40e3 ether, "Bob cliff"); + + // Grantees should be able to withdraw all remaining tokens after sufficient time passed + skip(365 days + 1); + + _granteeWithdrawAndAsserts(vestingAllocationAlice, 50e3 ether, "Alice full"); + _granteeWithdrawAndAsserts(vestingAllocationBob, 30e3 ether, "Bob partial"); + + // Grantees should be able to withdraw within an year after vesting ends + skip(364 days); + + _granteeWithdrawAndAsserts(vestingAllocationBob, 10e3 ether, "Bob full"); + } + + function test_AdminToolingCompensation() public { + (bytes32 contractIdAlice, bytes32 contractIdBob) = _guardiansSignAndTppPass(); + + // Vesting starts and a month has passed + vm.warp(cappedMinterStartTime + 30 days); + + // Alice creates vesting contract and start withdrawal + VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); + _granteeWithdrawAndAsserts(vestingAllocationAlice, uint256(50e3 ether) + uint160(50e3 ether) / 365 days * 30 days, "Alice cliff + first month"); + + // Second month + skip(30 days); + + // Add new grantee for admin/tooling compensation + bytes32 contractIdChad = _signAndCreateContract( guardianSafe, - alice, - alicePrivateKey, + chad, + chadPrivateKey, "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 10e18, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + // 10k ZK total in one cliff + tokenStreamTotal: 10e3 ether, + vestingCliffCredit: 10e3 ether, + unlockingCliffCredit: 10e3 ether, + vestingRate: 0, + vestingStartTime: 0, + unlockRate: 0, + unlockStartTime: 0 }), new BaseAllocation.Milestone[](0) ); + VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); + _granteeWithdrawAndAsserts(vestingAllocationChad, 10e3 ether, "Chad cliff"); + } - bytes32 contractIdBob = _signAndCreateContract( + function test_RevertIf_ExceedCap() public { + _guardiansSignAndTppPass(); + + // Add a large grant that exceeds the cap + bytes32 contractIdChad = _signAndCreateContract( guardianSafe, - bob, - bobPrivateKey, + chad, + chadPrivateKey, "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), - tokenStreamTotal: 2000e18, - vestingCliffCredit: 200e18, - unlockingCliffCredit: 200e18, - vestingRate: 20e18, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 20e18, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + // 1.01M ZK total in one cliff + tokenStreamTotal: 1.01e6 ether, + vestingCliffCredit: 1.01e6 ether, + unlockingCliffCredit: 1.01e6 ether, + vestingRate: 0, + vestingStartTime: 0, + unlockRate: 0, + unlockStartTime: 0 }), new BaseAllocation.Milestone[](0) ); + VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); + // Wait until minter starts + vm.warp(cappedMinterStartTime); - // Anyone can create MetaVesT for Alice and Bob (per agreements) to start vesting - - VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); - assertEq(zkToken.balanceOf(address(vestingAllocationAlice)), 0, "Alice's vesting contract should not have any token (it mints on-demand)"); - - VestingAllocation vestingAllocationBob = VestingAllocation(controller.createMetavest(contractIdBob)); - assertEq(zkToken.balanceOf(address(vestingAllocationBob)), 0, "Vesting contract should not have any token (it mints on-demand)"); - - // Alice and Bob should be able to start withdrawal after capped minter and MetaVesT start - skip(30 days); - - _granteeWithdrawAndAsserts(vestingAllocationAlice, 100e18, "Alice cliff"); - _granteeWithdrawAndAsserts(vestingAllocationBob, 200e18, "Bob cliff"); - - // Alice and Bob should be able to withdrawal all remaining tokens after sufficient time passed - skip(90 seconds); - - _granteeWithdrawAndAsserts(vestingAllocationAlice, 900e18, "Alice full"); - _granteeWithdrawAndAsserts(vestingAllocationBob, 1800e18, "Bob full"); + vm.prank(chad); + vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(vestingAllocationChad), 1.01e6 ether)); + vestingAllocationChad.withdraw(1.01e6 ether); } function test_RevertIf_NotAuthority() public { @@ -241,10 +277,59 @@ contract ZkGuardianCompensationTest is Test { ); } + function _guardiansSignAndTppPass() internal returns(bytes32, bytes32) { + // Guardians to sign agreements and register on MetaVesTController + + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 100e3 ether, + vestingCliffCredit: 50e3 ether, + unlockingCliffCredit: 50e3 ether, + vestingRate: uint160(50e3 ether) / 365 days, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: uint160(50e3 ether) / 365 days, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0) + ); + + bytes32 contractIdBob = _signAndCreateContract( + guardianSafe, + bob, + bobPrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 80k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 80e3 ether, + vestingCliffCredit: 40e3 ether, + unlockingCliffCredit: 40e3 ether, + vestingRate: uint160(40e3 ether) / 365 days, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: uint160(40e3 ether) / 365 days, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0) + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + return (contractIdAlice, contractIdBob); + } + function _granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) internal { address grantee = vestingAllocation.grantee(); uint256 balanceBefore = zkToken.balanceOf(grantee); - assertEq(vestingAllocation.getAmountWithdrawable(), amount, string(abi.encodePacked(assertName, ": unexpected withdrawable amount after cliff"))); vm.prank(grantee); vestingAllocation.withdraw(amount); assertEq(zkToken.balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); From 338e4554a328c8e6b739ef830370fc505061d34d Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 1 Aug 2025 19:58:23 -0700 Subject: [PATCH 14/68] test: Move MetaVesTController unit tests to dedicated file --- test/MetaVesTController.t.sol | 205 ++++++++++++++++++++++++ test/ZkGuardianCompensation.t.sol | 135 +--------------- test/lib/MetaVesTControllerTestBase.sol | 107 +++++++++++++ 3 files changed, 315 insertions(+), 132 deletions(-) create mode 100644 test/MetaVesTController.t.sol create mode 100644 test/lib/MetaVesTControllerTestBase.sol diff --git a/test/MetaVesTController.t.sol b/test/MetaVesTController.t.sol new file mode 100644 index 0000000..c2be705 --- /dev/null +++ b/test/MetaVesTController.t.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import "../src/MetaVesTController.sol"; +import "../src/VestingAllocationFactory.sol"; +import "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "./lib/MetaVesTUtils.sol"; +import "./lib/MetaVesTControllerTestBase.sol"; + +// Test by forge test --zksync --via-ir +contract MetaVesTControllerTest is MetaVesTControllerTestBase { + // Parameters + bytes32 salt = keccak256("MetaVesTControllerTest"); + uint256 cap = 100 ether; + uint48 cappedMinterStartTime = uint48(block.timestamp + 60); // Minter start 60 seconds later + uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 120); // Minter expired 120 seconds after start + + function setUp() public { + vm.startPrank(deployer); + + // Deploy MetaVesT controller + + vestingAllocationFactory = new VestingAllocationFactory(); + + controller = new metavestController{salt: salt}( + guardianSafe, + guardianSafe, + address(vestingAllocationFactory) + ); + + // Deploy ZK Capped Minter v2 + + zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + cap, + cappedMinterStartTime, + cappedMinterExpirationTime, + uint256(salt) + )); + + vm.stopPrank(); + + vm.prank(guardianSafe); + controller.setZkCappedMinter(address(zkCappedMinter)); + } + + function test_RevertIf_ExceedCap() public { + // Add a large grant that exceeds the cap + bytes32 contractIdChad = _signAndCreateContract( + guardianSafe, + chad, + chadPrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 101 ZK total in one cliff + tokenStreamTotal: 101 ether, + vestingCliffCredit: 101 ether, + unlockingCliffCredit: 101 ether, + vestingRate: 0, + vestingStartTime: 0, + unlockRate: 0, + unlockStartTime: 0 + }), + new BaseAllocation.Milestone[](0) + ); + VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); + + // Wait until minter starts + skip(60); + + vm.prank(chad); + vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(vestingAllocationChad), 101 ether)); + vestingAllocationChad.withdraw(101 ether); + } + + function test_RevertIf_NotAuthority() public { + // Non Guardian SAFE should not be able to accept agreement and create contract + _signAndCreateContract( + deployer, // Not authority + alice, + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert + ); + } + + function test_RevertIf_IncorrectAgreementSignature() public { + // Register Alice with someone else's signature should fail + _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use someone else to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert + ); + } + + function test_DelegateSignature() public { + // Alice to delegate to Bob + vm.prank(alice); + controller.setDelegation(bob, block.timestamp + 60); + assertTrue(controller.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); + + // Bob should be able to sign for Alice now + bytes32 contractId = _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use Bob to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0) + ); + metavestController.AgreementData memory agreement = controller.getAgreement(contractId); + assertEq(agreement.signedData.grantee, alice, "Alice should be the grantee"); + + // Wait until expiry + skip(61); + + // Bob should no longer be able to sign for Alice + assertFalse(controller.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); + _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use Bob to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert + ); + } + +// function _guardiansSignAndTppPass() internal returns(bytes32) { +// // Guardians to sign agreements and register on MetaVesTController +// +// bytes32 contractIdAlice = _signAndCreateContract( +// guardianSafe, +// alice, +// alicePrivateKey, +// "ipfs.io/ipfs/[cid]", +// BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year +// tokenStreamTotal: 60 ether, +// vestingCliffCredit: 30 ether, +// unlockingCliffCredit: 30 ether, +// vestingRate: 1 ether, +// vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter +// unlockRate: 1 ether, +// unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter +// }), +// new BaseAllocation.Milestone[](0) +// ); +// +// // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions +// +// bytes32 minterRole = zkToken.MINTER_ROLE(); +// vm.prank(zkTokenAdmin); +// zkToken.grantRole(minterRole, address(zkCappedMinter)); +// +// return contractIdAlice; +// } +} diff --git a/test/ZkGuardianCompensation.t.sol b/test/ZkGuardianCompensation.t.sol index bf96745..3b4f21a 100644 --- a/test/ZkGuardianCompensation.t.sol +++ b/test/ZkGuardianCompensation.t.sol @@ -3,38 +3,13 @@ pragma solidity ^0.8.20; import "../src/MetaVesTController.sol"; import "../src/VestingAllocationFactory.sol"; -import "../src/interfaces/zk-governance/IZkTokenV1.sol"; import "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; import "./lib/MetaVesTUtils.sol"; -import "forge-std/Test.sol"; +import "./lib/MetaVesTControllerTestBase.sol"; // Test by forge test --zksync --via-ir -contract ZkGuardianCompensationTest is Test { -// // zkSync Era Sepolia @ 5576300 -// address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; -// IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); -// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); - // zkSync Era mainnet @ 63631890 - address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; - IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); - IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); - - IZkCappedMinterV2 zkCappedMinter; - - address deployer = address(0x2); - address guardianSafe = address(0x3); - - uint256 alicePrivateKey = 1; - address alice = vm.addr(alicePrivateKey); - uint256 bobPrivateKey = 2; - address bob = vm.addr(bobPrivateKey); - uint256 chadPrivateKey = 3; - address chad = vm.addr(chadPrivateKey); - - VestingAllocationFactory vestingAllocationFactory; - - metavestController controller; - +contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { // Parameters bytes32 salt = keccak256("MetaLexZkSyncTest"); uint256 cap = 1e6 ether; // 1M ZK @@ -149,38 +124,6 @@ contract ZkGuardianCompensationTest is Test { _granteeWithdrawAndAsserts(vestingAllocationChad, 10e3 ether, "Chad cliff"); } - function test_RevertIf_ExceedCap() public { - _guardiansSignAndTppPass(); - - // Add a large grant that exceeds the cap - bytes32 contractIdChad = _signAndCreateContract( - guardianSafe, - chad, - chadPrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 1.01M ZK total in one cliff - tokenStreamTotal: 1.01e6 ether, - vestingCliffCredit: 1.01e6 ether, - unlockingCliffCredit: 1.01e6 ether, - vestingRate: 0, - vestingStartTime: 0, - unlockRate: 0, - unlockStartTime: 0 - }), - new BaseAllocation.Milestone[](0) - ); - VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); - - // Wait until minter starts - vm.warp(cappedMinterStartTime); - - vm.prank(chad); - vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(vestingAllocationChad), 1.01e6 ether)); - vestingAllocationChad.withdraw(1.01e6 ether); - } - function test_RevertIf_NotAuthority() public { // Non Guardian SAFE should not be able to accept agreement and create contract _signAndCreateContract( @@ -326,76 +269,4 @@ contract ZkGuardianCompensationTest is Test { return (contractIdAlice, contractIdBob); } - - function _granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) internal { - address grantee = vestingAllocation.grantee(); - uint256 balanceBefore = zkToken.balanceOf(grantee); - vm.prank(grantee); - vestingAllocation.withdraw(amount); - assertEq(zkToken.balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); - assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); - } - - function _signAndCreateContract( - address authority, - address grantee, - uint256 granteePrivateKey, - string memory agreementUri, - BaseAllocation.Allocation memory allocation, - BaseAllocation.Milestone[] memory milestones - ) internal returns(bytes32) { - return _signAndCreateContract( - authority, grantee, granteePrivateKey, agreementUri, allocation, milestones, - "" // Not expecting revert - ); - } - - function _signAndCreateContract( - address authority, - address grantee, - uint256 granteePrivateKey, - string memory agreementUri, - BaseAllocation.Allocation memory allocation, - BaseAllocation.Milestone[] memory milestones, - bytes memory expectRevertData - ) internal returns(bytes32) { - uint256 contractSalt = block.timestamp; - bytes32 expectedContractId = controller.computeContractId(contractSalt, agreementUri, grantee, grantee, allocation, milestones); - bytes memory signature = MetaVesTUtils.signAgreementTypedData( - vm, - controller, - metavestController.SignedAgreementData({ - id: expectedContractId, - agreementUri: agreementUri, - _metavestType: metavestController.metavestType.Vesting, - grantee: grantee, - recipient: grantee, - allocation: allocation, - milestones: milestones - }), - granteePrivateKey - ); - - if (expectRevertData.length > 0) { - vm.expectRevert(expectRevertData); - } - vm.prank(authority); - bytes32 contractId = controller.createSignedContract( - contractSalt, - metavestController.metavestType.Vesting, - grantee, - grantee, - allocation, - milestones, - agreementUri, - signature - ); - - if (expectRevertData.length == 0) { - assertEq(contractId, expectedContractId, "Unexpected contract ID"); - return contractId; - } else { - return 0; - } - } } diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol new file mode 100644 index 0000000..d3279c3 --- /dev/null +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.24; + +import "forge-std/Test.sol"; +import "../../src/MetaVesTController.sol"; +import "../../src/VestingAllocationFactory.sol"; +import "../../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "./MetaVesTUtils.sol"; + +contract MetaVesTControllerTestBase is Test { +// // zkSync Era Sepolia @ 5576300 +// address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; +// IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); +// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); + // zkSync Era mainnet @ 63631890 + address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; + IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); + IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); + + IZkCappedMinterV2 zkCappedMinter; + + address deployer = address(0x2); + address guardianSafe = address(0x3); + + uint256 alicePrivateKey = 1; + address alice = vm.addr(alicePrivateKey); + uint256 bobPrivateKey = 2; + address bob = vm.addr(bobPrivateKey); + uint256 chadPrivateKey = 3; + address chad = vm.addr(chadPrivateKey); + + VestingAllocationFactory vestingAllocationFactory; + + metavestController controller; + + function _granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) internal { + address grantee = vestingAllocation.grantee(); + uint256 balanceBefore = zkToken.balanceOf(grantee); + vm.prank(grantee); + vestingAllocation.withdraw(amount); + assertEq(zkToken.balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); + assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); + } + + function _signAndCreateContract( + address authority, + address grantee, + uint256 granteePrivateKey, + string memory agreementUri, + BaseAllocation.Allocation memory allocation, + BaseAllocation.Milestone[] memory milestones + ) internal returns(bytes32) { + return _signAndCreateContract( + authority, grantee, granteePrivateKey, agreementUri, allocation, milestones, + "" // Not expecting revert + ); + } + + function _signAndCreateContract( + address authority, + address grantee, + uint256 granteePrivateKey, + string memory agreementUri, + BaseAllocation.Allocation memory allocation, + BaseAllocation.Milestone[] memory milestones, + bytes memory expectRevertData + ) internal returns(bytes32) { + uint256 contractSalt = block.timestamp; + bytes32 expectedContractId = controller.computeContractId(contractSalt, agreementUri, grantee, grantee, allocation, milestones); + bytes memory signature = MetaVesTUtils.signAgreementTypedData( + vm, + controller, + metavestController.SignedAgreementData({ + id: expectedContractId, + agreementUri: agreementUri, + _metavestType: metavestController.metavestType.Vesting, + grantee: grantee, + recipient: grantee, + allocation: allocation, + milestones: milestones + }), + granteePrivateKey + ); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + vm.prank(authority); + bytes32 contractId = controller.createSignedContract( + contractSalt, + metavestController.metavestType.Vesting, + grantee, + grantee, + allocation, + milestones, + agreementUri, + signature + ); + + if (expectRevertData.length == 0) { + assertEq(contractId, expectedContractId, "Unexpected contract ID"); + return contractId; + } else { + return 0; + } + } +} From 8a49284c44f08ff0c1a9487f5952b4f19e61ba2f Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 1 Aug 2025 22:22:46 -0700 Subject: [PATCH 15/68] test: combine all MetaVesTController unit tests --- test/MetaVesTController.t.sol | 205 -- test/controller.t.sol | 3435 ++++++++++++--------------------- 2 files changed, 1233 insertions(+), 2407 deletions(-) delete mode 100644 test/MetaVesTController.t.sol diff --git a/test/MetaVesTController.t.sol b/test/MetaVesTController.t.sol deleted file mode 100644 index c2be705..0000000 --- a/test/MetaVesTController.t.sol +++ /dev/null @@ -1,205 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.20; - -import "../src/MetaVesTController.sol"; -import "../src/VestingAllocationFactory.sol"; -import "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; -import "../src/interfaces/zk-governance/IZkTokenV1.sol"; -import "./lib/MetaVesTUtils.sol"; -import "./lib/MetaVesTControllerTestBase.sol"; - -// Test by forge test --zksync --via-ir -contract MetaVesTControllerTest is MetaVesTControllerTestBase { - // Parameters - bytes32 salt = keccak256("MetaVesTControllerTest"); - uint256 cap = 100 ether; - uint48 cappedMinterStartTime = uint48(block.timestamp + 60); // Minter start 60 seconds later - uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 120); // Minter expired 120 seconds after start - - function setUp() public { - vm.startPrank(deployer); - - // Deploy MetaVesT controller - - vestingAllocationFactory = new VestingAllocationFactory(); - - controller = new metavestController{salt: salt}( - guardianSafe, - guardianSafe, - address(vestingAllocationFactory) - ); - - // Deploy ZK Capped Minter v2 - - zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( - address(zkToken), - address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT - cap, - cappedMinterStartTime, - cappedMinterExpirationTime, - uint256(salt) - )); - - vm.stopPrank(); - - vm.prank(guardianSafe); - controller.setZkCappedMinter(address(zkCappedMinter)); - } - - function test_RevertIf_ExceedCap() public { - // Add a large grant that exceeds the cap - bytes32 contractIdChad = _signAndCreateContract( - guardianSafe, - chad, - chadPrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 101 ZK total in one cliff - tokenStreamTotal: 101 ether, - vestingCliffCredit: 101 ether, - unlockingCliffCredit: 101 ether, - vestingRate: 0, - vestingStartTime: 0, - unlockRate: 0, - unlockStartTime: 0 - }), - new BaseAllocation.Milestone[](0) - ); - VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); - - // Wait until minter starts - skip(60); - - vm.prank(chad); - vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(vestingAllocationChad), 101 ether)); - vestingAllocationChad.withdraw(101 ether); - } - - function test_RevertIf_NotAuthority() public { - // Non Guardian SAFE should not be able to accept agreement and create contract - _signAndCreateContract( - deployer, // Not authority - alice, - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 100 ether, - vestingCliffCredit: 10 ether, - unlockingCliffCredit: 10 ether, - vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert - ); - } - - function test_RevertIf_IncorrectAgreementSignature() public { - // Register Alice with someone else's signature should fail - _signAndCreateContract( - guardianSafe, - alice, - bobPrivateKey, // Use someone else to sign - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 100 ether, - vestingCliffCredit: 10 ether, - unlockingCliffCredit: 10 ether, - vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert - ); - } - - function test_DelegateSignature() public { - // Alice to delegate to Bob - vm.prank(alice); - controller.setDelegation(bob, block.timestamp + 60); - assertTrue(controller.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); - - // Bob should be able to sign for Alice now - bytes32 contractId = _signAndCreateContract( - guardianSafe, - alice, - bobPrivateKey, // Use Bob to sign - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 100 ether, - vestingCliffCredit: 10 ether, - unlockingCliffCredit: 10 ether, - vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0) - ); - metavestController.AgreementData memory agreement = controller.getAgreement(contractId); - assertEq(agreement.signedData.grantee, alice, "Alice should be the grantee"); - - // Wait until expiry - skip(61); - - // Bob should no longer be able to sign for Alice - assertFalse(controller.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); - _signAndCreateContract( - guardianSafe, - alice, - bobPrivateKey, // Use Bob to sign - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 100 ether, - vestingCliffCredit: 10 ether, - unlockingCliffCredit: 10 ether, - vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert - ); - } - -// function _guardiansSignAndTppPass() internal returns(bytes32) { -// // Guardians to sign agreements and register on MetaVesTController -// -// bytes32 contractIdAlice = _signAndCreateContract( -// guardianSafe, -// alice, -// alicePrivateKey, -// "ipfs.io/ipfs/[cid]", -// BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year -// tokenStreamTotal: 60 ether, -// vestingCliffCredit: 30 ether, -// unlockingCliffCredit: 30 ether, -// vestingRate: 1 ether, -// vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter -// unlockRate: 1 ether, -// unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter -// }), -// new BaseAllocation.Milestone[](0) -// ); -// -// // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions -// -// bytes32 minterRole = zkToken.MINTER_ROLE(); -// vm.prank(zkTokenAdmin); -// zkToken.grantRole(minterRole, address(zkCappedMinter)); -// -// return contractIdAlice; -// } -} diff --git a/test/controller.t.sol b/test/controller.t.sol index 9b2f5ec..bbcc8e6 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -9,1611 +9,699 @@ import "../src/VestingAllocation.sol"; import "../src/VestingAllocationFactory.sol"; import "../src/interfaces/IAllocationFactory.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; -import "../src/metavestController.sol"; +import "./lib/MetaVesTControllerTestBase.sol"; import "./mocks/MockCondition.sol"; -import "forge-std/Test.sol"; -import {console} from "forge-std/console.sol"; -abstract contract ERC20 { +contract MetaVestControllerTest is MetaVesTControllerTestBase { + address authority = guardianSafe; + address dao = guardianSafe; + address grantee = alice; + address transferee = address(0x101); + // Parameters + bytes32 salt = keccak256("MetaVesTControllerTest"); + uint256 cap = 2000 ether; + uint48 cappedMinterStartTime = uint48(block.timestamp); // Minter start now + uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 1600); // Minter expired 1600 seconds after start - /// @dev The total supply has overflowed. - error TotalSupplyOverflow(); + function setUp() public { + vm.startPrank(deployer); - /// @dev The allowance has overflowed. - error AllowanceOverflow(); + // Deploy MetaVesT controller - /// @dev The allowance has underflowed. - error AllowanceUnderflow(); + vestingAllocationFactory = new VestingAllocationFactory(); - /// @dev Insufficient balance. - error InsufficientBalance(); + controller = new metavestController{salt: salt}( + guardianSafe, + guardianSafe, + address(vestingAllocationFactory) + ); - /// @dev Insufficient allowance. - error InsufficientAllowance(); + // Deploy ZK Capped Minter v2 - /// @dev The permit is invalid. - error InvalidPermit(); + zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + cap, + cappedMinterStartTime, + cappedMinterExpirationTime, + uint256(salt) + )); - /// @dev The permit has expired. - error PermitExpired(); + vm.stopPrank(); - - /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 amount); - - /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. - uint256 private constant _TRANSFER_EVENT_SIGNATURE = - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; - - /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. - uint256 private constant _APPROVAL_EVENT_SIGNATURE = - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; - - - /// @dev The storage slot for the total supply. - uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; - - /// @dev The balance slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _BALANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let balanceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; - - /// @dev The allowance slot of (`owner`, `spender`) is given by: - /// ``` - /// mstore(0x20, spender) - /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let allowanceSlot := keccak256(0x0c, 0x34) - /// ``` - uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; - - /// @dev The nonce slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _NONCES_SLOT_SEED) - /// mstore(0x00, owner) - /// let nonceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _NONCES_SLOT_SEED = 0x38377508; - - - /// @dev Returns the name of the token. - function name() public view virtual returns (string memory); - - /// @dev Returns the symbol of the token. - function symbol() public view virtual returns (string memory); - - /// @dev Returns the decimals places of the token. - function decimals() public view virtual returns (uint8) { - return 18; + vm.startPrank(guardianSafe); + controller.setZkCappedMinter(address(zkCappedMinter)); + controller.createSet("testSet"); + vm.stopPrank(); } - - /// @dev Returns the amount of tokens in existence. - function totalSupply() public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - result := sload(_TOTAL_SUPPLY_SLOT) - } + function testCreateVestingAllocation() public { + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 60 ether, + vestingCliffCredit: 30 ether, + unlockingCliffCredit: 30 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0) + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + // Anyone can create MetaVesT (per agreements) to start vesting + VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); + + // Grantees should be able to withdraw all remaining tokens after sufficient time passed + skip(61); + _granteeWithdrawAndAsserts(vestingAllocationAlice, 60 ether, "Alice full"); } - /// @dev Returns the amount of tokens owned by `owner`. - function balanceOf(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. - function allowance(address owner, address spender) - public - view - virtual - returns (uint256 result) - { - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x34)) - } - } +// function testCreateTokenOptionAllocation() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// address tokenOptionAllocation = controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// ); +// +// assertEq(zkToken.balanceOf(address(tokenOptionAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// //assertEq(controller.tokenOptionAllocations(grantee, 0), tokenOptionAllocation); +// } - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - /// - /// Emits a {Approval} event. - function approve(address spender, uint256 amount) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } +// function testCreateRestrictedTokenAward() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// address restrictedTokenAward = controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// +// ); +// +// assertEq(zkToken.balanceOf(address(restrictedTokenAward)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); +// } - /// @dev Atomically increases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Add to the allowance. - let allowanceAfter := add(allowanceBefore, difference) - // Revert upon overflow. - if lt(allowanceAfter, allowanceBefore) { - mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated allowance. - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; + function testUpdateTransferability() public { + uint256 startTimestamp = block.timestamp; + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + //compute msg.data for updateMetavestTransferability(vestingAllocation, true) + bytes4 selector = controller.updateMetavestTransferability.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, true); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, true); + vm.prank(authority); + controller.updateMetavestTransferability(vestingAllocation, true); + vm.prank(grantee); + VestingAllocation(vestingAllocation).transferRights(transferee); + vm.prank(transferee); + VestingAllocation(vestingAllocation).confirmTransfer(); + uint256 newTimestamp = startTimestamp + 100; // 101 + vm.warp(newTimestamp); + skip(10); + vm.prank(transferee); + uint256 balance = VestingAllocation(vestingAllocation).getAmountWithdrawable(); + + + //warp ahead 100 blocks + + vm.prank(transferee); + VestingAllocation(vestingAllocation).withdraw(balance); + + // assertTrue(BaseAllocation(vestingAllocation).transferable()); } - /// @dev Atomically decreases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Revert if will underflow. - if lt(allowanceBefore, difference) { - mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - let allowanceAfter := sub(allowanceBefore, difference) - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; + function testGetGovPower() public { + address vestingAllocation = createDummyVestingAllocation(); + BaseAllocation(vestingAllocation).getGoverningPower(); } - /// @dev Transfer `amount` tokens from the caller to `to`. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(msg.sender, to, amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, caller()) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) - } - _afterTokenTransfer(msg.sender, to, amount); - return true; - } + function testProposeMajorityMetavestAmendment() public { + address vestingAllocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, true); - /// @dev Transfers `amount` tokens from `from` to `to`. - /// - /// Note: Does not update the allowance if it is the maximum uint256 value. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the allowance slot and load its value. - mstore(0x20, caller()) - mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - return true; - } + vm.prank(authority); + controller.addMetaVestToSet("testSet", vestingAllocation); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + vm.prank(grantee); + controller.voteOnMetavestAmendment(vestingAllocation, "testSet", msgSig, true); - /// @dev Returns the current nonce for `owner`. - /// This value is used to compute the signature for EIP-2612 permit. - function nonces(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } + vm.prank(authority); + controller.updateMetavestTransferability(vestingAllocation, true); } - /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, - /// authorized by a signed approval by `owner`. - /// - /// Emits a {Approval} event. - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - bytes32 domainSeparator = DOMAIN_SEPARATOR(); - /// @solidity memory-safe-assembly - assembly { - // Grab the free memory pointer. - let m := mload(0x40) - // Revert if the block timestamp greater than `deadline`. - if gt(timestamp(), deadline) { - mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. - revert(0x1c, 0x04) - } - // Clean the upper 96 bits. - owner := shr(96, shl(96, owner)) - spender := shr(96, shl(96, spender)) - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - let nonceSlot := keccak256(0x0c, 0x20) - let nonceValue := sload(nonceSlot) - // Increment and store the updated nonce. - sstore(nonceSlot, add(nonceValue, 1)) - // Prepare the inner hash. - // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. - // forgefmt: disable-next-item - mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) - mstore(add(m, 0x20), owner) - mstore(add(m, 0x40), spender) - mstore(add(m, 0x60), value) - mstore(add(m, 0x80), nonceValue) - mstore(add(m, 0xa0), deadline) - // Prepare the outer hash. - mstore(0, 0x1901) - mstore(0x20, domainSeparator) - mstore(0x40, keccak256(m, 0xc0)) - // Prepare the ecrecover calldata. - mstore(0, keccak256(0x1e, 0x42)) - mstore(0x20, and(0xff, v)) - mstore(0x40, r) - mstore(0x60, s) - pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) - // If the ecrecover fails, the returndatasize will be 0x00, - // `owner` will be be checked if it equals the hash at 0x00, - // which evaluates to false (i.e. 0), and we will revert. - // If the ecrecover succeeds, the returndatasize will be 0x20, - // `owner` will be compared against the returned address at 0x20. - if iszero(eq(mload(returndatasize()), owner)) { - mstore(0x00, 0xddafbaef) // `InvalidPermit()`. - revert(0x1c, 0x04) - } - // Compute the allowance slot and store the value. - // The `owner` is already at slot 0x20. - mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) - sstore(keccak256(0x2c, 0x34), value) - // Emit the {Approval} event. - log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) - mstore(0x40, m) // Restore the free memory pointer. - mstore(0x60, 0) // Restore the zero pointer. - } - } - /// @dev Returns the EIP-2612 domains separator. - function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) // Grab the free memory pointer. - } - // We simply calculate it on-the-fly to allow for cases where the `name` may change. - bytes32 nameHash = keccak256(bytes(name())); - /// @solidity memory-safe-assembly - assembly { - let m := result - // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - // forgefmt: disable-next-item - mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) - mstore(add(m, 0x20), nameHash) - // `keccak256("1")`. - // forgefmt: disable-next-item - mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) - mstore(add(m, 0x60), chainid()) - mstore(add(m, 0x80), address()) - result := keccak256(m, 0xa0) - } + function test_RevertIf_ReProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + vm.warp(block.timestamp + 30 days); + /* + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true);*/ + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); } + function testReProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - /// @dev Mints `amount` tokens to `to`, increasing the total supply. - /// - /// Emits a {Transfer} event. - function _mint(address to, uint256 amount) internal virtual { - _beforeTokenTransfer(address(0), to, amount); - /// @solidity memory-safe-assembly - assembly { - let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) - let totalSupplyAfter := add(totalSupplyBefore, amount) - // Revert if the total supply overflows. - if lt(totalSupplyAfter, totalSupplyBefore) { - mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) - } - _afterTokenTransfer(address(0), to, amount); - } + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + vm.warp(block.timestamp + 30 days); - /// @dev Burns `amount` tokens from `from`, reducing the total supply. - /// - /// Emits a {Transfer} event. - function _burn(address from, uint256 amount) internal virtual { - _beforeTokenTransfer(from, address(0), amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, from) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Subtract and store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) - // Emit the {Transfer} event. - mstore(0x00, amount) - log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) - } - _afterTokenTransfer(from, address(0), amount); - } + vm.prank(authority); + controller.cancelExpiredMajorityMetavestAmendment("testSet", msgSig); - /// @dev Moves `amount` of tokens from `from` to `to`. - function _transfer(address from, address to, uint256 amount) internal virtual { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - } + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - } } - /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. - /// - /// Emits a {Approval} event. - function _approve(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - let owner_ := shl(96, owner) - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) - } + function test_RevertIf_RemoveNonExistantMetaVestFromSet() public { + address mockAllocation2 = createDummyVestingAllocation(); + vm.startPrank(authority); + // controller.createSet("testSet"); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_MetaVestNotInSet.selector)); + controller.removeMetaVestFromSet("testSet", mockAllocation2); } - /// @dev Hook that is called before any transfer of tokens. - /// This includes minting and burning. - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - /// @dev Hook that is called after any transfer of tokens. - /// This includes minting and burning. - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} - -abstract contract ERC20Stable { - - /// @dev The total supply has overflowed. - error TotalSupplyOverflow(); - - /// @dev The allowance has overflowed. - error AllowanceOverflow(); - - /// @dev The allowance has underflowed. - error AllowanceUnderflow(); - - /// @dev Insufficient balance. - error InsufficientBalance(); - - /// @dev Insufficient allowance. - error InsufficientAllowance(); - - /// @dev The permit is invalid. - error InvalidPermit(); - - /// @dev The permit has expired. - error PermitExpired(); - - - /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 amount); - - /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. - uint256 private constant _TRANSFER_EVENT_SIGNATURE = - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; - - /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. - uint256 private constant _APPROVAL_EVENT_SIGNATURE = - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; - - /// @dev The storage slot for the total supply. - uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; - - /// @dev The balance slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _BALANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let balanceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; +// function testUpdateExercisePrice() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// +// //compute msg.data for updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18) +// bytes4 selector = controller.updateExerciseOrRepurchasePrice.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, tokenOptionAllocation, 2e18); +// +// controller.proposeMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, msgData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, true); +// +// controller.updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18); +// +// assertEq(TokenOptionAllocation(tokenOptionAllocation).exercisePrice(), 2e18); +// } - /// @dev The allowance slot of (`owner`, `spender`) is given by: - /// ``` - /// mstore(0x20, spender) - /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let allowanceSlot := keccak256(0x0c, 0x34) - /// ``` - uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; + function testRemoveMilestone() public { + address vestingAllocation = createDummyVestingAllocation(); + //create array of addresses and include vestingAllocation address + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + vm.prank(grantee); + //consent to amendment for the removemetavestmilestone method sig function consentToMetavestAmendment(address _metavest, bytes4 _msgSig, bool _inFavor) external { + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.prank(authority); + controller.removeMetavestMilestone(vestingAllocation, 0); + + //BaseAllocation.Milestone memory milestone = BaseAllocation(vestingAllocation).milestones(0); + //assertEq(milestone.milestoneAward, 0); + } - /// @dev The nonce slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _NONCES_SLOT_SEED) - /// mstore(0x00, owner) - /// let nonceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _NONCES_SLOT_SEED = 0x38377508; + function testAddMilestone() public { + address vestingAllocation = createDummyVestingAllocation(); - /// @dev Returns the name of the token. - function name() public view virtual returns (string memory); + BaseAllocation.Milestone memory newMilestone = BaseAllocation.Milestone({ + milestoneAward: 50e18, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); - /// @dev Returns the symbol of the token. - function symbol() public view virtual returns (string memory); + vm.prank(authority); + controller.addMetavestMilestone(vestingAllocation, newMilestone); - /// @dev Returns the decimals places of the token. - function decimals() public view virtual returns (uint8) { - return 6; + // BaseAllocation.Milestone memory addedMilestone = BaseAllocation(vestingAllocation).milestones[0]; + // assertEq(addedMilestone.milestoneAward, 50e18); } - /// @dev Returns the amount of tokens in existence. - function totalSupply() public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - result := sload(_TOTAL_SUPPLY_SLOT) - } + function testUpdateUnlockRate() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(authority); + controller.updateMetavestUnlockRate(vestingAllocation, 20e18); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 20e18); } - /// @dev Returns the amount of tokens owned by `owner`. - function balanceOf(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } + function testUpdateUnlockRateZeroEmergency() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(authority); + controller.updateMetavestUnlockRate(vestingAllocation, 0); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 0); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.prank(authority); + controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); + updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 1e20); } - /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. - function allowance(address owner, address spender) - public - view - virtual - returns (uint256 result) - { - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x34)) - } + function test_RevertIf_UpdateUnlockRateZeroEmergency() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); + vm.prank(authority); + controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 10e18); } - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - /// - /// Emits a {Approval} event. - function approve(address spender, uint256 amount) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; + function test_RevertIf_UpdateUnlockRateZeroEmergencyTerminated() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(authority); + controller.updateMetavestUnlockRate(vestingAllocation, 0); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 0); + + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); + vm.prank(authority); + controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); + updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 0); } - /// @dev Atomically increases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Add to the allowance. - let allowanceAfter := add(allowanceBefore, difference) - // Revert upon overflow. - if lt(allowanceAfter, allowanceBefore) { - mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated allowance. - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; + function testUpdateVestingRate() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestVestingRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, true); + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 20e18); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.vestingRate, 20e18); } - /// @dev Atomically decreases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Revert if will underflow. - if lt(allowanceBefore, difference) { - mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - let allowanceAfter := sub(allowanceBefore, difference) - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } +// function testUpdateStopTimes() public { +// +// address vestingAllocation = createDummyRestrictedTokenAward(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, uint48(block.timestamp + 500 days)); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, msgData); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, true); +// uint48 newShortStopTime = uint48(block.timestamp + 500 days); +// +// controller.updateMetavestStopTimes(vestingAllocation, newShortStopTime); +// } - /// @dev Transfer `amount` tokens from the caller to `to`. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(msg.sender, to, amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, caller()) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) - } - _afterTokenTransfer(msg.sender, to, amount); - return true; - } + function testTerminateVesting() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); - /// @dev Transfers `amount` tokens from `from` to `to`. - /// - /// Note: Does not update the allowance if it is the maximum uint256 value. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the allowance slot and load its value. - mstore(0x20, caller()) - mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - return true; + assertTrue(BaseAllocation(vestingAllocation).terminated()); } +// function testRepurchaseTokens() public { +// uint256 startingBalance = paymentToken.balanceOf(grantee); +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 repurchaseAmount = 5e18; +// uint256 snapshot = token.balanceOf(authority); +// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); +// controller.terminateMetavestVesting(restrictedTokenAward); +// paymentToken.approve(address(restrictedTokenAward), payment); +// vm.warp(block.timestamp + 20 days); +// vm.prank(authority); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +// +// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +// +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +// } - /// @dev Returns the current nonce for `owner`. - /// This value is used to compute the signature for EIP-2612 permit. - function nonces(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } +// function testRepurchaseTokensFuture() public { +// uint256 startingBalance = paymentToken.balanceOf(grantee); +// address restrictedTokenAward = createDummyRestrictedTokenAwardFuture(); +// +// uint256 snapshot = token.balanceOf(authority); +// +// controller.terminateMetavestVesting(restrictedTokenAward); +// uint256 repurchaseAmount = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); +// paymentToken.approve(address(restrictedTokenAward), payment); +// vm.warp(block.timestamp + 20 days); +// vm.prank(authority); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +// +// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +// +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); +// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +// +// } - /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, - /// authorized by a signed approval by `owner`. - /// - /// Emits a {Approval} event. - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - bytes32 domainSeparator = DOMAIN_SEPARATOR(); - /// @solidity memory-safe-assembly - assembly { - // Grab the free memory pointer. - let m := mload(0x40) - // Revert if the block timestamp greater than `deadline`. - if gt(timestamp(), deadline) { - mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. - revert(0x1c, 0x04) - } - // Clean the upper 96 bits. - owner := shr(96, shl(96, owner)) - spender := shr(96, shl(96, spender)) - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - let nonceSlot := keccak256(0x0c, 0x20) - let nonceValue := sload(nonceSlot) - // Increment and store the updated nonce. - sstore(nonceSlot, add(nonceValue, 1)) - // Prepare the inner hash. - // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. - // forgefmt: disable-next-item - mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) - mstore(add(m, 0x20), owner) - mstore(add(m, 0x40), spender) - mstore(add(m, 0x60), value) - mstore(add(m, 0x80), nonceValue) - mstore(add(m, 0xa0), deadline) - // Prepare the outer hash. - mstore(0, 0x1901) - mstore(0x20, domainSeparator) - mstore(0x40, keccak256(m, 0xc0)) - // Prepare the ecrecover calldata. - mstore(0, keccak256(0x1e, 0x42)) - mstore(0x20, and(0xff, v)) - mstore(0x40, r) - mstore(0x60, s) - pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) - // If the ecrecover fails, the returndatasize will be 0x00, - // `owner` will be be checked if it equals the hash at 0x00, - // which evaluates to false (i.e. 0), and we will revert. - // If the ecrecover succeeds, the returndatasize will be 0x20, - // `owner` will be compared against the returned address at 0x20. - if iszero(eq(mload(returndatasize()), owner)) { - mstore(0x00, 0xddafbaef) // `InvalidPermit()`. - revert(0x1c, 0x04) - } - // Compute the allowance slot and store the value. - // The `owner` is already at slot 0x20. - mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) - sstore(keccak256(0x2c, 0x34), value) - // Emit the {Approval} event. - log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) - mstore(0x40, m) // Restore the free memory pointer. - mstore(0x60, 0) // Restore the zero pointer. - } + function testTerminateTokensFuture() public { + address vestingAllocation = createDummyVestingAllocationLargeFuture(); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); } - /// @dev Returns the EIP-2612 domains separator. - function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) // Grab the free memory pointer. - } - // We simply calculate it on-the-fly to allow for cases where the `name` may change. - bytes32 nameHash = keccak256(bytes(name())); - /// @solidity memory-safe-assembly - assembly { - let m := result - // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - // forgefmt: disable-next-item - mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) - mstore(add(m, 0x20), nameHash) - // `keccak256("1")`. - // forgefmt: disable-next-item - mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) - mstore(add(m, 0x60), chainid()) - mstore(add(m, 0x80), address()) - result := keccak256(m, 0xa0) - } - } + function testUpdateAuthority() public { + address newAuthority = address(0x4); + vm.prank(authority); + controller.initiateAuthorityUpdate(newAuthority); - /// @dev Mints `amount` tokens to `to`, increasing the total supply. - /// - /// Emits a {Transfer} event. - function _mint(address to, uint256 amount) internal virtual { - _beforeTokenTransfer(address(0), to, amount); - /// @solidity memory-safe-assembly - assembly { - let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) - let totalSupplyAfter := add(totalSupplyBefore, amount) - // Revert if the total supply overflows. - if lt(totalSupplyAfter, totalSupplyBefore) { - mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) - } - _afterTokenTransfer(address(0), to, amount); - } + vm.prank(newAuthority); + controller.acceptAuthorityRole(); - /// @dev Burns `amount` tokens from `from`, reducing the total supply. - /// - /// Emits a {Transfer} event. - function _burn(address from, uint256 amount) internal virtual { - _beforeTokenTransfer(from, address(0), amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, from) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Subtract and store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) - // Emit the {Transfer} event. - mstore(0x00, amount) - log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) - } - _afterTokenTransfer(from, address(0), amount); + assertEq(controller.authority(), newAuthority); } + function testUpdateDao() public { + address newDao = address(0x5); - /// @dev Moves `amount` of tokens from `from` to `to`. - function _transfer(address from, address to, uint256 amount) internal virtual { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - } + vm.prank(dao); + controller.initiateDaoUpdate(newDao); + vm.prank(newDao); + controller.acceptDaoRole(); - /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - } + assertEq(controller.dao(), newDao); } - /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. - /// - /// Emits a {Approval} event. - function _approve(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - let owner_ := shl(96, owner) - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) - } + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation() internal returns (address) { + return createDummyVestingAllocation(""); // Expect no reverts } - /// @dev Hook that is called before any transfer of tokens. - /// This includes minting and burning. - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - /// @dev Hook that is called after any transfer of tokens. - /// This includes minting and burning. - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} - -contract MockERC20 is ERC20 { - constructor(string memory name, string memory symbol) ERC20() {} - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - function name() public view override returns (string memory) { - return "Test Token"; - } - function symbol() public view override returns (string memory) { - return "TT"; + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation(bytes memory expectRevertData) internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000 ether, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + return controller.createMetavest(contractIdAlice); } -} -contract MockERC20Stable is ERC20Stable { - constructor(string memory name, string memory symbol) ERC20Stable() {} - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - function name() public view override returns (string memory) { - return "Test Token"; + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationNoUnlock() internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000 ether, + unlockOnCompletion: false, + complete: false, + conditionContracts: new address[](0) + }); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + return controller.createMetavest(contractIdAlice); } - function symbol() public view override returns (string memory) { - return "TT"; + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationSlowUnlock() internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000 ether, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 5 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + return controller.createMetavest(contractIdAlice); } -} -// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter -contract MetaVestControllerTest is Test { -// // zkSync Era Sepolia @ 5576300 -// address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; -// IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); -// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); -//// // zkSync Era mainnet -//// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; -//// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); -//// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); -// -// IZkCappedMinterV2 zkCappedMinter; -// -// metavestController public controller; -// MockERC20Stable public paymentToken; -// -// address public authority; -// address public dao; -// address public grantee; -// address public transferee; -// -// bytes32 salt = keccak256("MetaVesTFactoryTest"); -// -// function setUp() public { -// authority = address(this); -// dao = address(0x2); -// grantee = address(0x3); -// transferee = address(0x4); -// -// paymentToken = new MockERC20Stable("Payment Token", "PT"); -// -// VestingAllocationFactory factory = new VestingAllocationFactory(); -// TokenOptionFactory tokenFactory = new TokenOptionFactory(); -// RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); -// -// controller = new metavestController( -// authority, -// dao, -// address(factory), -// address(tokenFactory), -// address(restrictedTokenFactory) -// ); -// -// // Deploy ZK Capped Minter v2 -// zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( -// address(zkToken), -// address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT -// 9999e18, -// uint48(block.timestamp), // start now -// uint48(block.timestamp + 365 days * 2), -// uint256(salt) -// )); -// -// vm.startPrank(authority); -// controller.setZkCappedMinter(address(zkCappedMinter)); -// controller.createSet("testSet"); -// vm.stopPrank(); -// -// // Simulate TPP approval and ZK Token admin to grant our ZkCappedMinter access -// bytes32 minterRole = zkToken.MINTER_ROLE(); -// vm.prank(zkTokenAdmin); -// zkToken.grantRole(minterRole, address(zkCappedMinter)); -// } -// -// function testCreateVestingAllocation() public { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 100e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); -// -// VestingAllocation vestingAllocation = VestingAllocation(controller.createMetavest( -// metavestController.metavestType.Vesting, -// grantee, -// allocation, -// milestones, -// 0, -// address(0), -// 0, -// 0 -// -// )); -// -// assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); -// assertEq(vestingAllocation.getAmountWithdrawable(), 100e18, "Should be able to withdraw cliff amount"); -// } -// -//// function testCreateTokenOptionAllocation() public { -//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -//// tokenContract: address(zkToken), -//// tokenStreamTotal: 1000e18, -//// vestingCliffCredit: 100e18, -//// unlockingCliffCredit: 100e18, -//// vestingRate: 10e18, -//// vestingStartTime: uint48(block.timestamp), -//// unlockRate: 10e18, -//// unlockStartTime: uint48(block.timestamp) -//// }); -//// -//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -//// milestones[0] = BaseAllocation.Milestone({ -//// milestoneAward: 100e18, -//// unlockOnCompletion: true, -//// complete: false, -//// conditionContracts: new address[](0) -//// }); -//// -//// address tokenOptionAllocation = controller.createMetavest( -//// metavestController.metavestType.TokenOption, -//// grantee, -//// allocation, -//// milestones, -//// 1e18, -//// address(paymentToken), -//// 365 days, -//// 0 -//// ); -//// -//// assertEq(zkToken.balanceOf(address(tokenOptionAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); -//// //assertEq(controller.tokenOptionAllocations(grantee, 0), tokenOptionAllocation); -//// } -// -//// function testCreateRestrictedTokenAward() public { -//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -//// tokenContract: address(zkToken), -//// tokenStreamTotal: 1000e18, -//// vestingCliffCredit: 100e18, -//// unlockingCliffCredit: 100e18, -//// vestingRate: 10e18, -//// vestingStartTime: uint48(block.timestamp), -//// unlockRate: 10e18, -//// unlockStartTime: uint48(block.timestamp) -//// }); -//// -//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -//// milestones[0] = BaseAllocation.Milestone({ -//// milestoneAward: 100e18, -//// unlockOnCompletion: true, -//// complete: false, -//// conditionContracts: new address[](0) -//// }); -//// -//// address restrictedTokenAward = controller.createMetavest( -//// metavestController.metavestType.RestrictedTokenAward, -//// grantee, -//// allocation, -//// milestones, -//// 1e18, -//// address(paymentToken), -//// 365 days, -//// 0 -//// -//// ); -//// -//// assertEq(zkToken.balanceOf(address(restrictedTokenAward)), 0, "Vesting contract should not have any token (it mints on-demand)"); -//// //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); -//// } -// -// function testUpdateTransferability() public { -// uint256 startTimestamp = block.timestamp; -// address vestingAllocation = createDummyVestingAllocation(); -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// //compute msg.data for updateMetavestTransferability(vestingAllocation, true) -// bytes4 selector = controller.updateMetavestTransferability.selector; -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, true); -// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, msgData); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, true); -// -// controller.updateMetavestTransferability(vestingAllocation, true); -// vm.prank(grantee); -// RestrictedTokenAward(vestingAllocation).transferRights(transferee); -// vm.prank(transferee); -// RestrictedTokenAward(vestingAllocation).confirmTransfer(); -// uint256 newTimestamp = startTimestamp + 100; // 101 -// vm.warp(newTimestamp); -// skip(10); -// vm.prank(transferee); -// uint256 balance = RestrictedTokenAward(vestingAllocation).getAmountWithdrawable(); -// -// -// //warp ahead 100 blocks -// -// vm.prank(transferee); -// RestrictedTokenAward(vestingAllocation).withdraw(balance); -// -// // assertTrue(BaseAllocation(vestingAllocation).transferable()); -// } -// -// function testGetGovPower() public { -// address vestingAllocation = createDummyVestingAllocation(); -// BaseAllocation(vestingAllocation).getGoverningPower(); -// } -// -// function testProposeMajorityMetavestAmendment() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// vm.warp(block.timestamp + 1 days); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true); -// } -// -// -// function test_RevertIf_ReProposeMajorityMetavestAmendment() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// vm.warp(block.timestamp + 1 days); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// vm.warp(block.timestamp + 30 days); -// /* -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true);*/ -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// } -// -// function testReProposeMajorityMetavestAmendment() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// vm.warp(block.timestamp + 1 days); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// vm.warp(block.timestamp + 30 days); -// -// vm.prank(authority); -// controller.cancelExpiredMajorityMetavestAmendment("testSet", msgSig); -// -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// } -// -// function test_RevertIf_RemoveNonExistantMetaVestFromSet() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// vm.startPrank(authority); -// // controller.createSet("testSet"); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_MetaVestNotInSet.selector)); -// controller.removeMetaVestFromSet("testSet", mockAllocation2); -// } -// -// -//// function testUpdateExercisePrice() public { -//// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -//// -//// //compute msg.data for updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18) -//// bytes4 selector = controller.updateExerciseOrRepurchasePrice.selector; -//// bytes memory msgData = abi.encodeWithSelector(selector, tokenOptionAllocation, 2e18); -//// -//// controller.proposeMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, msgData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, true); -//// -//// controller.updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18); -//// -//// assertEq(TokenOptionAllocation(tokenOptionAllocation).exercisePrice(), 2e18); -//// } -// -// function testRemoveMilestone() public { -// address vestingAllocation = createDummyVestingAllocation(); -// //create array of addresses and include vestingAllocation address -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); -// controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); -// vm.prank(grantee); -// //consent to amendment for the removemetavestmilestone method sig function consentToMetavestAmendment(address _metavest, bytes4 _msgSig, bool _inFavor) external { -// controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); -// controller.removeMetavestMilestone(vestingAllocation, 0); -// -// //BaseAllocation.Milestone memory milestone = BaseAllocation(vestingAllocation).milestones(0); -// //assertEq(milestone.milestoneAward, 0); -// } -// -// function testAddMilestone() public { -// address vestingAllocation = createDummyVestingAllocation(); -// -// BaseAllocation.Milestone memory newMilestone = BaseAllocation.Milestone({ -// milestoneAward: 50e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); -// -// controller.addMetavestMilestone(vestingAllocation, newMilestone); -// -// // BaseAllocation.Milestone memory addedMilestone = BaseAllocation(vestingAllocation).milestones[0]; -// // assertEq(addedMilestone.milestoneAward, 50e18); -// } -// -// function testUpdateUnlockRate() public { -// address vestingAllocation = createDummyVestingAllocation(); -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = controller.updateMetavestUnlockRate.selector; -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); -// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); -// -// controller.updateMetavestUnlockRate(vestingAllocation, 20e18); -// -// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); -// assertEq(updatedAllocation.unlockRate, 20e18); -// } -// -// function testUpdateUnlockRateZeroEmergency() public { -// address vestingAllocation = createDummyVestingAllocation(); -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = controller.updateMetavestUnlockRate.selector; -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); -// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); -// -// controller.updateMetavestUnlockRate(vestingAllocation, 0); -// -// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); -// assertEq(updatedAllocation.unlockRate, 0); -// -// controller.terminateMetavestVesting(vestingAllocation); -// -// controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); -// updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); -// assertEq(updatedAllocation.unlockRate, 1e20); -// } -// -// function test_RevertIf_UpdateUnlockRateZeroEmergency() public { -// address vestingAllocation = createDummyVestingAllocation(); -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = controller.updateMetavestUnlockRate.selector; -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); -// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); -// -// controller.terminateMetavestVesting(vestingAllocation); -// -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); -// controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); -// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); -// assertEq(updatedAllocation.unlockRate, 10e18); -// } -// -// function test_RevertIf_UpdateUnlockRateZeroEmergencyTerminated() public { -// address vestingAllocation = createDummyVestingAllocation(); -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = controller.updateMetavestUnlockRate.selector; -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); -// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); -// -// controller.updateMetavestUnlockRate(vestingAllocation, 0); -// -// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); -// assertEq(updatedAllocation.unlockRate, 0); -// -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); -// controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); -// updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); -// assertEq(updatedAllocation.unlockRate, 0); -// } -// -// function testUpdateVestingRate() public { -// address vestingAllocation = createDummyVestingAllocation(); -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = controller.updateMetavestVestingRate.selector; -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); -// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, msgData); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, true); -// -// controller.updateMetavestVestingRate(vestingAllocation, 20e18); -// -// BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); -// assertEq(updatedAllocation.vestingRate, 20e18); -// } -// -//// function testUpdateStopTimes() public { -//// -//// address vestingAllocation = createDummyRestrictedTokenAward(); -//// address[] memory addresses = new address[](1); -//// addresses[0] = vestingAllocation; -//// bytes4 selector = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); -//// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, uint48(block.timestamp + 500 days)); -//// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, msgData); -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, true); -//// uint48 newShortStopTime = uint48(block.timestamp + 500 days); -//// -//// controller.updateMetavestStopTimes(vestingAllocation, newShortStopTime); -//// } -// -// function testTerminateVesting() public { -// address vestingAllocation = createDummyVestingAllocation(); -// -// controller.terminateMetavestVesting(vestingAllocation); -// -// assertTrue(BaseAllocation(vestingAllocation).terminated()); -// } -// -//// function testRepurchaseTokens() public { -//// uint256 startingBalance = paymentToken.balanceOf(grantee); -//// address restrictedTokenAward = createDummyRestrictedTokenAward(); -//// uint256 repurchaseAmount = 5e18; -//// uint256 snapshot = token.balanceOf(authority); -//// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); -//// controller.terminateMetavestVesting(restrictedTokenAward); -//// paymentToken.approve(address(restrictedTokenAward), payment); -//// vm.warp(block.timestamp + 20 days); -//// vm.prank(authority); -//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); -//// -//// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); -//// -//// vm.prank(grantee); -//// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -//// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); -//// } -// -//// function testRepurchaseTokensFuture() public { -//// uint256 startingBalance = paymentToken.balanceOf(grantee); -//// address restrictedTokenAward = createDummyRestrictedTokenAwardFuture(); -//// -//// uint256 snapshot = token.balanceOf(authority); -//// -//// controller.terminateMetavestVesting(restrictedTokenAward); -//// uint256 repurchaseAmount = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -//// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); -//// paymentToken.approve(address(restrictedTokenAward), payment); -//// vm.warp(block.timestamp + 20 days); -//// vm.prank(authority); -//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); -//// -//// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); -//// -//// vm.prank(grantee); -//// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -//// console.log(token.balanceOf(restrictedTokenAward)); -//// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); -//// -//// } -// -// function testTerminateTokensFuture() public { -// uint256 startingBalance = paymentToken.balanceOf(grantee); -// address vestingAllocation = createDummyVestingAllocationLargeFuture(); -// -// controller.terminateMetavestVesting(vestingAllocation); -// } -// -// function testUpdateAuthority() public { -// address newAuthority = address(0x4); -// -// controller.initiateAuthorityUpdate(newAuthority); -// -// vm.prank(newAuthority); -// controller.acceptAuthorityRole(); -// -// assertEq(controller.authority(), newAuthority); -// } -// -// function testUpdateDao() public { -// address newDao = address(0x5); -// -// vm.prank(dao); -// controller.initiateDaoUpdate(newDao); -// -// vm.prank(newDao); -// controller.acceptDaoRole(); -// -// assertEq(controller.dao(), newDao); -// } -// -// // Helper functions to create dummy allocations for testing -// function createDummyVestingAllocation() internal returns (address) { + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationLarge() internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 0 ether, + unlockingCliffCredit: 0 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + return controller.createMetavest(contractIdAlice); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationLargeFuture() internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 0 ether, + unlockingCliffCredit: 0 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp + 2000), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp + 2000) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + return controller.createMetavest(contractIdAlice); + } + +// function createDummyTokenOptionAllocation() internal returns (address) { // BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), +// tokenContract: address(token), // tokenStreamTotal: 1000e18, // vestingCliffCredit: 100e18, // unlockingCliffCredit: 100e18, @@ -1631,23 +719,24 @@ contract MetaVestControllerTest is Test { // conditionContracts: new address[](0) // }); // +// token.approve(address(controller), 2000e18); +// // return controller.createMetavest( -// metavestController.metavestType.Vesting, +// metavestController.metavestType.TokenOption, // grantee, // allocation, // milestones, -// 0, -// address(0), -// 0, +// 5e17, +// address(paymentToken), +// 1 days, // 0 -// // ); // } -// -// // Helper functions to create dummy allocations for testing -// function createDummyVestingAllocationNoUnlock() internal returns (address) { + + +// function createDummyRestrictedTokenAward() internal returns (address) { // BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), +// tokenContract: address(token), // tokenStreamTotal: 1000e18, // vestingCliffCredit: 100e18, // unlockingCliffCredit: 100e18, @@ -1660,35 +749,36 @@ contract MetaVestControllerTest is Test { // BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); // milestones[0] = BaseAllocation.Milestone({ // milestoneAward: 1000e18, -// unlockOnCompletion: false, +// unlockOnCompletion: true, // complete: false, // conditionContracts: new address[](0) // }); // +// token.approve(address(controller), 2100e18); +// // return controller.createMetavest( -// metavestController.metavestType.Vesting, +// metavestController.metavestType.RestrictedTokenAward, // grantee, // allocation, // milestones, -// 0, -// address(0), -// 0, +// 1e18, +// address(paymentToken), +// 1 days, // 0 // // ); // } // -// // Helper functions to create dummy allocations for testing -// function createDummyVestingAllocationSlowUnlock() internal returns (address) { +// function createDummyRestrictedTokenAwardFuture() internal returns (address) { // BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), +// tokenContract: address(token), // tokenStreamTotal: 1000e18, // vestingCliffCredit: 100e18, // unlockingCliffCredit: 100e18, // vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 5e18, -// unlockStartTime: uint48(block.timestamp) +// vestingStartTime: uint48(block.timestamp+1000), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp+1000) // }); // // BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); @@ -1699,741 +789,682 @@ contract MetaVestControllerTest is Test { // conditionContracts: new address[](0) // }); // -// return controller.createMetavest( -// metavestController.metavestType.Vesting, -// grantee, -// allocation, -// milestones, -// 0, -// address(0), -// 0, -// 0 -// -// ); -// } -// -// // Helper functions to create dummy allocations for testing -// function createDummyVestingAllocationLarge() internal returns (address) { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 0, -// unlockingCliffCredit: 0, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); -// -// -// return controller.createMetavest( -// metavestController.metavestType.Vesting, -// grantee, -// allocation, -// milestones, -// 0, -// address(0), -// 0, -// 0 -// -// ); -// } -// -// // Helper functions to create dummy allocations for testing -// function createDummyVestingAllocationLargeFuture() internal returns (address) { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 0, -// unlockingCliffCredit: 0, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp+2000), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp+2000) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); -// +// token.approve(address(controller), 2100e18); // // return controller.createMetavest( -// metavestController.metavestType.Vesting, +// metavestController.metavestType.RestrictedTokenAward, // grantee, // allocation, // milestones, -// 0, -// address(0), -// 0, -// 0 -// -// ); -// } -// -//// function createDummyTokenOptionAllocation() internal returns (address) { -//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -//// tokenContract: address(token), -//// tokenStreamTotal: 1000e18, -//// vestingCliffCredit: 100e18, -//// unlockingCliffCredit: 100e18, -//// vestingRate: 10e18, -//// vestingStartTime: uint48(block.timestamp), -//// unlockRate: 10e18, -//// unlockStartTime: uint48(block.timestamp) -//// }); -//// -//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -//// milestones[0] = BaseAllocation.Milestone({ -//// milestoneAward: 1000e18, -//// unlockOnCompletion: true, -//// complete: false, -//// conditionContracts: new address[](0) -//// }); -//// -//// token.approve(address(controller), 2000e18); -//// -//// return controller.createMetavest( -//// metavestController.metavestType.TokenOption, -//// grantee, -//// allocation, -//// milestones, -//// 5e17, -//// address(paymentToken), -//// 1 days, -//// 0 -//// ); -//// } -// -// -//// function createDummyRestrictedTokenAward() internal returns (address) { -//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -//// tokenContract: address(token), -//// tokenStreamTotal: 1000e18, -//// vestingCliffCredit: 100e18, -//// unlockingCliffCredit: 100e18, -//// vestingRate: 10e18, -//// vestingStartTime: uint48(block.timestamp), -//// unlockRate: 10e18, -//// unlockStartTime: uint48(block.timestamp) -//// }); -//// -//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -//// milestones[0] = BaseAllocation.Milestone({ -//// milestoneAward: 1000e18, -//// unlockOnCompletion: true, -//// complete: false, -//// conditionContracts: new address[](0) -//// }); -//// -//// token.approve(address(controller), 2100e18); -//// -//// return controller.createMetavest( -//// metavestController.metavestType.RestrictedTokenAward, -//// grantee, -//// allocation, -//// milestones, -//// 1e18, -//// address(paymentToken), -//// 1 days, -//// 0 -//// -//// ); -//// } -//// -//// function createDummyRestrictedTokenAwardFuture() internal returns (address) { -//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -//// tokenContract: address(token), -//// tokenStreamTotal: 1000e18, -//// vestingCliffCredit: 100e18, -//// unlockingCliffCredit: 100e18, -//// vestingRate: 10e18, -//// vestingStartTime: uint48(block.timestamp+1000), -//// unlockRate: 10e18, -//// unlockStartTime: uint48(block.timestamp+1000) -//// }); -//// -//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -//// milestones[0] = BaseAllocation.Milestone({ -//// milestoneAward: 1000e18, -//// unlockOnCompletion: true, -//// complete: false, -//// conditionContracts: new address[](0) -//// }); -//// -//// token.approve(address(controller), 2100e18); -//// -//// return controller.createMetavest( -//// metavestController.metavestType.RestrictedTokenAward, -//// grantee, -//// allocation, -//// milestones, -//// 1e18, -//// address(paymentToken), -//// 1 days, -//// 0 -//// -//// ); -//// } -// -// -// function testGetMetaVestType() public { -// address vestingAllocation = createDummyVestingAllocation(); -//// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -//// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// -// assertEq(controller.getMetaVestType(vestingAllocation), 1); -//// assertEq(controller.getMetaVestType(tokenOptionAllocation), 2); -//// assertEq(controller.getMetaVestType(restrictedTokenAward), 3); -// } -// -//// function testWithdrawFromController() public { -//// uint256 amount = 100e18; -//// token.transfer(address(controller), amount); -//// -//// uint256 initialBalance = token.balanceOf(authority); -//// controller.withdrawFromController(address(token)); -//// uint256 finalBalance = token.balanceOf(authority); -//// -//// assertEq(finalBalance - initialBalance, amount); -//// assertEq(token.balanceOf(address(controller)), 0); -//// } -// -// function test_RevertIf_CreateMetavestWithZeroAddress() public { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(0), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); -// -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ZeroAddress.selector)); -// controller.createMetavest( -// metavestController.metavestType.Vesting, -// address(0), -// allocation, -// milestones, -// 0, -// address(0), -// 0, +// 1e18, +// address(paymentToken), +// 1 days, // 0 // // ); // } + + + function testGetMetaVestType() public { + address vestingAllocation = createDummyVestingAllocation(); +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// address restrictedTokenAward = createDummyRestrictedTokenAward(); + + assertEq(controller.getMetaVestType(vestingAllocation), 1); +// assertEq(controller.getMetaVestType(tokenOptionAllocation), 2); +// assertEq(controller.getMetaVestType(restrictedTokenAward), 3); + } + +// function testWithdrawFromController() public { +// uint256 amount = 100e18; +// token.transfer(address(controller), amount); // -// function test_RevertIf_CreateMetavestWithInsufficientApproval() public { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); -// -// // Not approving any tokens -// controller.createMetavest( -// metavestController.metavestType.Vesting, -// grantee, -// allocation, -// milestones, -// 0, -// address(0), -// 0, -// 0 +// uint256 initialBalance = token.balanceOf(authority); +// controller.withdrawFromController(address(token)); +// uint256 finalBalance = token.balanceOf(authority); // -// ); +// assertEq(finalBalance - initialBalance, amount); +// assertEq(token.balanceOf(address(controller)), 0); // } -// -// function testTerminateVestAndRecovers() public { -// address vestingAllocation = createDummyVestingAllocation(); -// uint256 snapshot = zkToken.balanceOf(authority); -// VestingAllocation(vestingAllocation).confirmMilestone(0); + + function test_RevertIf_CreateMetavestWithZeroAddress() public { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(0), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ZeroAddress.selector)); + controller.createMetavest(contractIdAlice); + } + + function testTerminateVestAndRecovers() public { + address vestingAllocation = createDummyVestingAllocation(); + uint256 snapshot = zkToken.balanceOf(authority); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.warp(block.timestamp + 50 seconds); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(authority), 0); + } + + function testTerminateVestAndRecoverSlowUnlock() public { + address vestingAllocation = createDummyVestingAllocationSlowUnlock(); + uint256 snapshot = zkToken.balanceOf(authority); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.warp(block.timestamp + 25 seconds); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.warp(block.timestamp + 25 seconds); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(vestingAllocation), 0); + } + + function testTerminateRecoverAll() public { + address vestingAllocation = createDummyVestingAllocationLarge(); + uint256 snapshot = zkToken.balanceOf(authority); + vm.warp(block.timestamp + 25 seconds); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(authority), 0); + } + + function testTerminateRecoverChunksBefore() public { + address vestingAllocation = createDummyVestingAllocationLarge(); + uint256 snapshot = zkToken.balanceOf(authority); + vm.warp(block.timestamp + 25 seconds); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + vm.warp(block.timestamp + 25 seconds); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(authority), 0); + } + +// function testConfirmingMilestoneRestrictedTokenAllocation() public { +// address vestingAllocation = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// RestrictedTokenAward(vestingAllocation).confirmMilestone(0); // vm.warp(block.timestamp + 50 seconds); -// controller.terminateMetavestVesting(vestingAllocation); // vm.startPrank(grantee); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// RestrictedTokenAward(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// assertEq(zkToken.balanceOf(authority), 0); // } // -// function testTerminateVestAndRecoverSlowUnlock() public { -// address vestingAllocation = createDummyVestingAllocationSlowUnlock(); -// uint256 snapshot = zkToken.balanceOf(authority); -// VestingAllocation(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 25 seconds); -// controller.terminateMetavestVesting(vestingAllocation); +// function testConfirmingMilestoneTokenOption() public { +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); // vm.startPrank(grantee); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.warp(block.timestamp + 25 seconds); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// assertEq(zkToken.balanceOf(vestingAllocation), 0); // } -// -// function testTerminateRecoverAll() public { -// address vestingAllocation = createDummyVestingAllocationLarge(); -// uint256 snapshot = zkToken.balanceOf(authority); -// vm.warp(block.timestamp + 25 seconds); -// controller.terminateMetavestVesting(vestingAllocation); + + function testUnlockMilestoneNotUnlocked() public { + address vestingAllocation = createDummyVestingAllocationNoUnlock(); + uint256 snapshot = zkToken.balanceOf(authority); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.warp(block.timestamp + 50 seconds); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.warp(block.timestamp + 1050 seconds); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + +// function testTerminateTokenOptionAndRecover() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// vm.prank(grantee); +// ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); +// vm.prank(grantee); +// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18); +// controller.terminateMetavestVesting(tokenOptionAllocation); // vm.startPrank(grantee); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.warp(block.timestamp + 1 days + 25 seconds); +// assertEq(TokenOptionAllocation(tokenOptionAllocation).getAmountExercisable(), 0); +// TokenOptionAllocation(tokenOptionAllocation).withdraw(TokenOptionAllocation(tokenOptionAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// assertEq(zkToken.balanceOf(authority), 0); +// assertEq(token.balanceOf(tokenOptionAllocation), 0); +// vm.warp(block.timestamp + 365 days); +// vm.prank(authority); +// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); // } -// -// function testTerminateRecoverChunksBefore() public { -// address vestingAllocation = createDummyVestingAllocationLarge(); -// uint256 snapshot = zkToken.balanceOf(authority); -// vm.warp(block.timestamp + 25 seconds); -// vm.startPrank(grantee); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); -// vm.warp(block.timestamp + 25 seconds); -// -// controller.terminateMetavestVesting(vestingAllocation); + +// function testTerminateEarlyTokenOptionAndRecover() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 5 seconds); +// // vm.prank(grantee); +// /* ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); +// vm.prank(grantee); +// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18);*/ +// controller.terminateMetavestVesting(tokenOptionAllocation); +// vm.warp(block.timestamp + 365 days); +// vm.prank(authority); +// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +// } + + +// function testTerminateRestrictedTokenAwardAndRecover() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// controller.terminateMetavestVesting(restrictedTokenAward); // vm.startPrank(grantee); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); // vm.stopPrank(); -// assertEq(zkToken.balanceOf(authority), 0); -// } +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// vm.warp(block.timestamp + 20 days); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); // -//// function testConfirmingMilestoneRestrictedTokenAllocation() public { -//// address vestingAllocation = createDummyRestrictedTokenAward(); -//// uint256 snapshot = token.balanceOf(authority); -//// RestrictedTokenAward(vestingAllocation).confirmMilestone(0); -//// vm.warp(block.timestamp + 50 seconds); -//// vm.startPrank(grantee); -//// RestrictedTokenAward(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -//// vm.stopPrank(); -//// } -//// -//// function testConfirmingMilestoneTokenOption() public { -//// address vestingAllocation = createDummyTokenOptionAllocation(); -//// uint256 snapshot = token.balanceOf(authority); -//// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); -//// vm.warp(block.timestamp + 50 seconds); -//// vm.startPrank(grantee); -//// //exercise max available -//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -//// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -//// vm.stopPrank(); -//// } -// -// function testUnlockMilestoneNotUnlocked() public { -// address vestingAllocation = createDummyVestingAllocationNoUnlock(); -// uint256 snapshot = zkToken.balanceOf(authority); -// VestingAllocation(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 50 seconds); // vm.startPrank(grantee); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.warp(block.timestamp + 1050 seconds); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// assertEq(token.balanceOf(restrictedTokenAward), 0); +// assertEq(paymentToken.balanceOf(restrictedTokenAward), 0); // } + +// function testChangeVestingAndUnlockingRate() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); // -//// function testTerminateTokenOptionAndRecover() public { -//// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -//// uint256 snapshot = token.balanceOf(authority); -//// vm.warp(block.timestamp + 25 seconds); -//// vm.prank(grantee); -//// ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); -//// vm.prank(grantee); -//// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18); -//// controller.terminateMetavestVesting(tokenOptionAllocation); -//// vm.startPrank(grantee); -//// vm.warp(block.timestamp + 1 days + 25 seconds); -//// assertEq(TokenOptionAllocation(tokenOptionAllocation).getAmountExercisable(), 0); -//// TokenOptionAllocation(tokenOptionAllocation).withdraw(TokenOptionAllocation(tokenOptionAllocation).getAmountWithdrawable()); -//// vm.stopPrank(); -//// assertEq(token.balanceOf(tokenOptionAllocation), 0); -//// vm.warp(block.timestamp + 365 days); -//// vm.prank(authority); -//// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); -//// } -// -//// function testTerminateEarlyTokenOptionAndRecover() public { -//// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -//// uint256 snapshot = token.balanceOf(authority); -//// vm.warp(block.timestamp + 5 seconds); -//// // vm.prank(grantee); -//// /* ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); -//// vm.prank(grantee); -//// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18);*/ -//// controller.terminateMetavestVesting(tokenOptionAllocation); -//// vm.warp(block.timestamp + 365 days); -//// vm.prank(authority); -//// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); -//// } -// +// bytes4 msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); // -//// function testTerminateRestrictedTokenAwardAndRecover() public { -//// address restrictedTokenAward = createDummyRestrictedTokenAward(); -//// uint256 snapshot = token.balanceOf(authority); -//// vm.warp(block.timestamp + 25 seconds); -//// controller.terminateMetavestVesting(restrictedTokenAward); -//// vm.startPrank(grantee); -//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -//// vm.stopPrank(); -//// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -//// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); -//// vm.warp(block.timestamp + 20 days); -//// paymentToken.approve(address(restrictedTokenAward), payamt); -//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); -//// -//// vm.startPrank(grantee); -//// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -//// assertEq(token.balanceOf(restrictedTokenAward), 0); -//// assertEq(paymentToken.balanceOf(restrictedTokenAward), 0); -//// } +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); // -//// function testChangeVestingAndUnlockingRate() public { -//// address restrictedTokenAward = createDummyRestrictedTokenAward(); -//// uint256 snapshot = token.balanceOf(authority); -//// vm.warp(block.timestamp + 25 seconds); -//// -//// bytes4 msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); -//// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestUnlockRate(restrictedTokenAward, 50e18); -//// -//// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -//// callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestVestingRate(restrictedTokenAward, 50e18); -//// -//// vm.startPrank(grantee); -//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -//// vm.stopPrank(); -//// -//// } +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); // -//// function testZeroReclaim() public { -//// address restrictedTokenAward = createDummyRestrictedTokenAward(); -//// vm.warp(block.timestamp + 15 seconds); -//// vm.startPrank(grantee); -//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -//// vm.stopPrank(); -//// //create call data to propose setting vesting to 0 -//// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -//// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestVestingRate(restrictedTokenAward, 0); -//// -//// vm.startPrank(authority); -//// controller.terminateMetavestVesting(restrictedTokenAward); -//// vm.warp(block.timestamp + 155 days); -//// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -//// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); -//// paymentToken.approve(address(restrictedTokenAward), payamt); -//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); -//// vm.stopPrank(); -//// vm.prank(grantee); -//// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -//// console.log(token.balanceOf(restrictedTokenAward)); -//// } +// vm.prank(authority); +// controller.updateMetavestUnlockRate(restrictedTokenAward, 50e18); // -// function testZeroReclaimVesting() public { -// address vestingAllocation = createDummyVestingAllocation(); -// vm.warp(block.timestamp + 15 seconds); -// vm.startPrank(grantee); -// RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); -// //create call data to propose setting vesting to 0 -// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 0); +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); // // vm.prank(authority); -// controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); // // vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); // // vm.prank(authority); -// controller.updateMetavestVestingRate(vestingAllocation, 0); +// controller.updateMetavestVestingRate(restrictedTokenAward, 50e18); // -// vm.startPrank(authority); -// controller.terminateMetavestVesting(vestingAllocation); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); // vm.stopPrank(); -// } // -// function testSlightReduc() public { -// address vestingAllocation = createDummyVestingAllocation(); -// vm.warp(block.timestamp + 5 seconds); +// } + +// function testZeroReclaim() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// vm.warp(block.timestamp + 15 seconds); // vm.startPrank(grantee); -// RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); // vm.stopPrank(); // //create call data to propose setting vesting to 0 // bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 80e18); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); // // vm.prank(authority); -// controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); // // vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); // // vm.prank(authority); -// controller.updateMetavestVestingRate(vestingAllocation, 80e18); -// vm.warp(block.timestamp + 5 seconds); +// controller.updateMetavestVestingRate(restrictedTokenAward, 0); +// // vm.startPrank(authority); -// controller.terminateMetavestVesting(vestingAllocation); -// vm.stopPrank(); -// vm.warp(block.timestamp + 155 seconds); -// vm.startPrank(grantee); -// RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.warp(block.timestamp + 155 days); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// vm.stopPrank(); +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); // } -// -// function testLargeReduc() public { -// address vestingAllocation = createDummyVestingAllocation(); + + function testZeroReclaimVesting() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.warp(block.timestamp + 15 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + //create call data to propose setting vesting to 0 + bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 0); + + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 0); + + vm.startPrank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.stopPrank(); + } + + function testSlightReduc() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + //create call data to propose setting vesting to 0 + bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 80e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 80e18); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.stopPrank(); + vm.warp(block.timestamp + 155 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + + function testLargeReduc() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + //create call data to propose setting vesting to 0 + bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 10e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 10e18); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.stopPrank(); + vm.warp(block.timestamp + 155 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + +// function testLargeReducOption() public { +// address restrictedTokenAward = createDummyTokenOptionAllocation(); // vm.warp(block.timestamp + 5 seconds); // vm.startPrank(grantee); -// RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); +// //approve amount to exercise by getting amount to exercise and price +// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); +// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); // vm.stopPrank(); // //create call data to propose setting vesting to 0 // bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 10e18); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); // // vm.prank(authority); -// controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); // // vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); // // vm.prank(authority); -// controller.updateMetavestVestingRate(vestingAllocation, 10e18); +// controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); // vm.warp(block.timestamp + 5 seconds); // vm.startPrank(authority); -// controller.terminateMetavestVesting(vestingAllocation); +// controller.terminateMetavestVesting(restrictedTokenAward); // vm.stopPrank(); // vm.warp(block.timestamp + 155 seconds); // vm.startPrank(grantee); -// RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); +// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); +// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); // vm.stopPrank(); +// console.log(token.balanceOf(restrictedTokenAward)); // } + + + +// function testReclaim() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// vm.warp(block.timestamp + 15 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); // -//// function testLargeReducOption() public { -//// address restrictedTokenAward = createDummyTokenOptionAllocation(); -//// vm.warp(block.timestamp + 5 seconds); -//// vm.startPrank(grantee); -//// //approve amount to exercise by getting amount to exercise and price -//// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); -//// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); -//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -//// vm.stopPrank(); -//// //create call data to propose setting vesting to 0 -//// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -//// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); -//// vm.warp(block.timestamp + 5 seconds); -//// vm.startPrank(authority); -//// controller.terminateMetavestVesting(restrictedTokenAward); -//// vm.stopPrank(); -//// vm.warp(block.timestamp + 155 seconds); -//// vm.startPrank(grantee); -//// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); -//// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); -//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -//// vm.stopPrank(); -//// console.log(token.balanceOf(restrictedTokenAward)); -//// } -// -// -// -//// function testReclaim() public { -//// address restrictedTokenAward = createDummyRestrictedTokenAward(); -//// vm.warp(block.timestamp + 15 seconds); -//// vm.startPrank(grantee); -//// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -//// vm.stopPrank(); -//// -//// vm.startPrank(authority); -//// controller.terminateMetavestVesting(restrictedTokenAward); -//// vm.warp(block.timestamp + 155 days); -//// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -//// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); -//// paymentToken.approve(address(restrictedTokenAward), payamt); -//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); -//// vm.stopPrank(); -//// vm.prank(grantee); -//// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -//// console.log(token.balanceOf(restrictedTokenAward)); -//// } -// -// -// -//// function test_RevertIf_UpdateExercisePriceForVesting() public { -//// address vestingAllocation = createDummyVestingAllocation(); -//// controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); -//// } -// -//// function test_RevertIf_RepurchaseTokensAfterExpiry() public { -//// address restrictedTokenAward = createDummyRestrictedTokenAward(); -//// -//// // Fast forward time to after the short stop date -//// vm.warp(block.timestamp + 366 days); -//// -//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); -//// } -// -//// function test_RevertIf_RepurchaseTokensInsufficientAllowance() public { -//// address restrictedTokenAward = createDummyRestrictedTokenAward(); -//// -//// // Not approving any tokens -//// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); -//// } -// -// function test_RevertIf_InitiateAuthorityUpdateNonAuthority() public { -// vm.prank(address(0x1234)); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); -// controller.initiateAuthorityUpdate(address(0x5678)); -// } -// -// function test_RevertIf_AcceptAuthorityRoleNonPendingAuthority() public { -// controller.initiateAuthorityUpdate(address(0x5678)); -// -// vm.prank(address(0x1234)); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingAuthority.selector)); -// controller.acceptAuthorityRole(); -// } -// -// function test_RevertIf_InitiateDaoUpdateNonDao() public { -// vm.prank(address(0x1234)); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); -// controller.initiateDaoUpdate(address(0x5678)); -// } -// -// function test_RevertIf_AcceptDaoRoleNonPendingDao() public { -// vm.prank(dao); -// controller.initiateDaoUpdate(address(0x5678)); -// -// vm.prank(address(0x1234)); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingDao.selector)); -// controller.acceptDaoRole(); -// } -// -// function testUpdateFunctionCondition() public { -// bytes4 functionSig = bytes4(keccak256("testFunction()")); -// /* constructor( -// address[] memory _signers, -// uint256 _threshold, -// Logic _logic -// ) */ -// address[] memory signers = new address[](2); -// signers[0] = address(0x1); -// signers[1] = address(0x2); -// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); -// -// vm.prank(dao); -// controller.updateFunctionCondition(address(condition), functionSig); -// -// assertEq(controller.functionToConditions(functionSig, 0), address(condition)); -// } -// -// function test_RevertIf_UpdateFunctionConditionNonDao() public { -// bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); -// address condition = address(0x1234); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); -// controller.updateFunctionCondition(condition, functionSig); +// vm.startPrank(authority); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.warp(block.timestamp + 155 days); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// vm.stopPrank(); +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); // } -// -// -// function testRemoveFunctionCondition() public { -// bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); -// /* constructor( -// address[] memory _signers, -// uint256 _threshold, -// Logic _logic -// ) */ -// address[] memory signers = new address[](2); -// signers[0] = address(0x1); -// signers[1] = address(0x2); -// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); -// -// vm.prank(dao); -// controller.updateFunctionCondition(address(condition), functionSig); -// assert(controller.functionToConditions(functionSig, 0) == address(condition)); -// vm.prank(dao); -// controller.removeFunctionCondition(address(condition), functionSig); + + + +// function test_RevertIf_UpdateExercisePriceForVesting() public { +// address vestingAllocation = createDummyVestingAllocation(); +// controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); // } + +// function test_RevertIf_RepurchaseTokensAfterExpiry() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); // -// function test_RevertIf_CheckFunctionCondition() public { -// bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); -// /* constructor( -// address[] memory _signers, -// uint256 _threshold, -// Logic _logic -// ) */ -// address[] memory signers = new address[](2); -// signers[0] = address(0x1); -// signers[1] = address(0x2); -// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); +// // Fast forward time to after the short stop date +// vm.warp(block.timestamp + 366 days); // -// vm.prank(dao); -// controller.updateFunctionCondition(address(condition), functionSig); -// assert(controller.functionToConditions(functionSig, 0) == address(condition)); -// //create a dummy metavest -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ConditionNotSatisfied.selector, condition)); -// address vestingAllocation = createDummyVestingAllocation(); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); // } + +// function test_RevertIf_RepurchaseTokensInsufficientAllowance() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); // -// function test_RevertIf_AddDuplicateCondition() public { -// bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); -// /* constructor( -// address[] memory _signers, -// uint256 _threshold, -// Logic _logic -// ) */ -// address[] memory signers = new address[](2); -// signers[0] = address(0x1); -// signers[1] = address(0x2); -// SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); -// -// vm.prank(dao); -// controller.updateFunctionCondition(address(condition), functionSig); -// assert(controller.functionToConditions(functionSig, 0) == address(condition)); -// vm.prank(dao); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_DuplicateCondition.selector)); -// controller.updateFunctionCondition(address(condition), functionSig); +// // Not approving any tokens +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); // } + + function test_RevertIf_InitiateAuthorityUpdateNonAuthority() public { + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); + controller.initiateAuthorityUpdate(address(0x5678)); + } + + function test_RevertIf_AcceptAuthorityRoleNonPendingAuthority() public { + vm.prank(authority); + controller.initiateAuthorityUpdate(address(0x5678)); + + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingAuthority.selector)); + controller.acceptAuthorityRole(); + } + + function test_RevertIf_InitiateDaoUpdateNonDao() public { + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); + controller.initiateDaoUpdate(address(0x5678)); + } + + function test_RevertIf_AcceptDaoRoleNonPendingDao() public { + vm.prank(dao); + controller.initiateDaoUpdate(address(0x5678)); + + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingDao.selector)); + controller.acceptDaoRole(); + } + + function testUpdateFunctionCondition() public { + bytes4 functionSig = bytes4(keccak256("testFunction()")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + + assertEq(controller.functionToConditions(functionSig, 0), address(condition)); + } + + function test_RevertIf_UpdateFunctionConditionNonDao() public { + bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); + address condition = address(0x1234); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); + controller.updateFunctionCondition(condition, functionSig); + } + + + function testRemoveFunctionCondition() public { + bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + assert(controller.functionToConditions(functionSig, 0) == address(condition)); + vm.prank(dao); + controller.removeFunctionCondition(address(condition), functionSig); + } + + function test_RevertIf_CheckFunctionCondition() public { + bytes4 functionSig = bytes4(keccak256("createMetavest(bytes32)")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + assert(controller.functionToConditions(functionSig, 0) == address(condition)); + // create a dummy metavest + createDummyVestingAllocation( + abi.encodeWithSelector(metavestController.MetaVesTController_ConditionNotSatisfied.selector, condition) // Expected revert + ); + } + + function test_RevertIf_AddDuplicateCondition() public { + bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + assert(controller.functionToConditions(functionSig, 0) == address(condition)); + vm.prank(dao); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_DuplicateCondition.selector)); + controller.updateFunctionCondition(address(condition), functionSig); + } + + function test_RevertIf_ExceedCap() public { + // Add a large grant that exceeds the cap + bytes32 contractIdChad = _signAndCreateContract( + guardianSafe, + chad, + chadPrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 2001 ether, + vestingCliffCredit: 2001 ether, + unlockingCliffCredit: 2001 ether, + vestingRate: 0, + vestingStartTime: 0, + unlockRate: 0, + unlockStartTime: 0 + }), + new BaseAllocation.Milestone[](0) + ); + VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); + + vm.prank(chad); + vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(vestingAllocationChad), 2001 ether)); + vestingAllocationChad.withdraw(2001 ether); + } + + function test_RevertIf_NotAuthority() public { + // Non Guardian SAFE should not be able to accept agreement and create contract + _signAndCreateContract( + deployer, // Not authority + alice, + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert + ); + } + + function test_RevertIf_IncorrectAgreementSignature() public { + // Register Alice with someone else's signature should fail + _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use someone else to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert + ); + } + + function test_DelegateSignature() public { + // Alice to delegate to Bob + vm.prank(alice); + controller.setDelegation(bob, block.timestamp + 60); + assertTrue(controller.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); + + // Bob should be able to sign for Alice now + bytes32 contractId = _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use Bob to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0) + ); + metavestController.AgreementData memory agreement = controller.getAgreement(contractId); + assertEq(agreement.signedData.grantee, alice, "Alice should be the grantee"); + + // Wait until expiry + skip(61); + + // Bob should no longer be able to sign for Alice + assertFalse(controller.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); + _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use Bob to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert + ); + } } From 6674b794f076ff181c4aede57f8f91ff14b648b2 Mon Sep 17 00:00:00 2001 From: detoo Date: Sat, 2 Aug 2025 22:10:33 -0700 Subject: [PATCH 16/68] test: fix all the rest --- test/AuditBaseA.t.sol | 78 +- test/AuditBaseA2.t.sol | 362 ++++---- test/AuditBaseC.t.sol | 86 +- test/AuditBaseC3.t.sol | 141 +-- test/amendement.t.sol | 1869 ++++++++++++++-------------------------- 5 files changed, 1003 insertions(+), 1533 deletions(-) diff --git a/test/AuditBaseA.t.sol b/test/AuditBaseA.t.sol index 3bbfde0..1846b52 100644 --- a/test/AuditBaseA.t.sol +++ b/test/AuditBaseA.t.sol @@ -15,43 +15,45 @@ contract EvilGrant { } contract Audit is MetaVestControllerTest { -// function test_RevertIf_AuditArbitraryVote() public { -// // template from testVoteOnMetavestAmendment -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", address(mockAllocation)); -// -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// address attacker = address(0x31337); -// address evil_grant = address(new EvilGrant()); -// -// vm.prank(attacker); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); -// -// (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); -// console.log("attacker made vote and power is" , currentVotingPower); -// } -// -// function test_RevertIf_AuditRemoveConfirmedMilestone() public { -// // template from testRemoveMilestone -// address vestingAllocation = createDummyVestingAllocation(); -// VestingAllocation(vestingAllocation).confirmMilestone(0); -// -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); -// controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); -// controller.removeMetavestMilestone(vestingAllocation, 0); -// } + function test_RevertIf_AuditArbitraryVote() public { + // template from testVoteOnMetavestAmendment + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + address attacker = address(0x31337); + address evil_grant = address(new EvilGrant()); + + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); + + (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + console.log("attacker made vote and power is" , currentVotingPower); + } + + function test_RevertIf_AuditRemoveConfirmedMilestone() public { + // template from testRemoveMilestone + address vestingAllocation = createDummyVestingAllocation(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); + vm.prank(authority); + controller.removeMetavestMilestone(vestingAllocation, 0); + } } \ No newline at end of file diff --git a/test/AuditBaseA2.t.sol b/test/AuditBaseA2.t.sol index a150577..6a4288e 100644 --- a/test/AuditBaseA2.t.sol +++ b/test/AuditBaseA2.t.sol @@ -16,201 +16,205 @@ contract EvilGrant { // TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract Audit is MetaVestControllerTest { -// function test_RevertIf_AuditArbitraryVote() public { -// // template from testVoteOnMetavestAmendment -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", address(mockAllocation)); + function test_RevertIf_AuditArbitraryVote() public { + // template from testVoteOnMetavestAmendment + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + address attacker = address(0x31337); + address evil_grant = address(new EvilGrant()); + + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); + + (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + console.log("attacker made vote and power is" , currentVotingPower); + } + + function test_RevertIf_AuditRemoveConfirmedMilestone() public { + // template from testRemoveMilestone + address vestingAllocation = createDummyVestingAllocation(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); + vm.prank(authority); + controller.removeMetavestMilestone(vestingAllocation, 0); + } + + function testAuditProposeMajorityMetavestAmendmentExpire() public { + // template from testProposeMajorityMetavestAmendment + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + // proposal expired + uint256 AMENDMENT_TIME_LIMIT = 604800; + vm.warp(block.timestamp + AMENDMENT_TIME_LIMIT + 1); + + // MetaVesTController_AmendmentAlreadyPending even expired + vm.prank(authority); + vm.expectRevert(); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + } + +// function testAuditModifiedCalldataProposal() public { +// // template from testCreateSetWithThreeTokenOptionsAndChangeExercisePrice +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); // // vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// address attacker = address(0x31337); -// address evil_grant = address(new EvilGrant()); -// -// vm.prank(attacker); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); -// -// (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); -// console.log("attacker made vote and power is" , currentVotingPower); -// } -// -// function test_RevertIf_AuditRemoveConfirmedMilestone() public { -// // template from testRemoveMilestone -// address vestingAllocation = createDummyVestingAllocation(); -// VestingAllocation(vestingAllocation).confirmMilestone(0); -// -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); -// controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); // -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); -// controller.removeMetavestMilestone(vestingAllocation, 0); -// } // -// function testAuditProposeMajorityMetavestAmendmentExpire() public { -// // template from testProposeMajorityMetavestAmendment -// address mockAllocation2 = createDummyVestingAllocation(); -// address mockAllocation3 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); // -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// vm.warp(block.timestamp + 1 days); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); // -// // proposal expired -// uint256 AMENDMENT_TIME_LIMIT = 604800; -// vm.warp(block.timestamp + AMENDMENT_TIME_LIMIT + 1); +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); // -// // MetaVesTController_AmendmentAlreadyPending even expired // vm.prank(authority); -// vm.expectRevert(); // controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// } -// -//// function testAuditModifiedCalldataProposal() public { -//// // template from testCreateSetWithThreeTokenOptionsAndChangeExercisePrice -//// address allocation1 = createDummyTokenOptionAllocation(); -//// address allocation2 = createDummyTokenOptionAllocation(); -//// address allocation3 = createDummyTokenOptionAllocation(); -//// -//// vm.prank(authority); -//// controller.addMetaVestToSet("testSet", allocation1); -//// controller.addMetaVestToSet("testSet", allocation2); -//// controller.addMetaVestToSet("testSet", allocation3); -//// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); -//// vm.warp(block.timestamp + 25 seconds); -//// -//// -//// vm.startPrank(grantee); -//// ERC20(paymentToken).approve(address(allocation1), 2000e18); -//// ERC20(paymentToken).approve(address(allocation2), 2000e18); -//// -//// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); -//// -//// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); -//// vm.stopPrank(); -//// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -//// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); -//// -//// vm.prank(authority); -//// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); -//// -//// vm.prank(grantee); -//// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); -//// -//// vm.prank(authority); -//// vm.expectRevert(); -//// controller.updateExerciseOrRepurchasePrice(allocation1, 999999999999999999999e18); -//// -//// // Bypass MetaVesTController_AmendmentNeitherMutualNorMajorityConsented -//// vm.prank(authority); -//// bytes memory p = abi.encodeWithSelector(msgSig, allocation1, 999999999999999999999e18, 2e18); -//// address(controller).call(p); -//// -//// console.log('Modified excercise price: ', TokenOptionAllocation(allocation1).exercisePrice()); -//// } -// -// function testAuditConsentToMetavestAmendmentInFlavor() public { -// // template from testRemoveMilestone -// address vestingAllocation = createDummyVestingAllocation(); -// VestingAllocation(vestingAllocation).confirmMilestone(0); -// -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); -// controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); // // vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, false); -// // emit MetaVesTController_AmendmentConsentUpdated(msgSig: 0x75b89e4f00000000000000000000000000000000000000000000000000000000, grantee: ECAdd: [0x0000000000000000000000000000000000000006], inFavor: false) -// console.log("expected inFavor: false"); -// (,,bool inFavor) = controller.functionToGranteeToAmendmentPending(selector, vestingAllocation); -// console.log("output: ", inFavor); -// assertEq(inFavor, false); -// -// } +// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); // -// function test_RevertIf_AuditProposeMajorityMetavestAmendmentNewGranteeDuringProposal() public { -// // template from testProposeMajorityMetavestAmendment -// address mockAllocation2 = createDummyVestingAllocation(); -// address mockAllocation3 = createDummyVestingAllocation(); -// address mockAllocation4 = createDummyVestingAllocation(); -// address mockAllocation5 = createDummyVestingAllocation(); -// address mockAllocation6 = createDummyVestingAllocation(); -// address mockAllocation7 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); // // vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// vm.warp(block.timestamp + 1 days); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.startPrank(authority); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); -// controller.addMetaVestToSet("testSet", mockAllocation4); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); -// controller.addMetaVestToSet("testSet", mockAllocation5); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); -// controller.addMetaVestToSet("testSet", mockAllocation6); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); -// controller.addMetaVestToSet("testSet", mockAllocation7); -// vm.stopPrank(); -// -// vm.startPrank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(mockAllocation5, "testSet", msgSig, true); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(mockAllocation6, "testSet", msgSig, true); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(mockAllocation7, "testSet", msgSig, true); -// vm.stopPrank(); -// -// (uint256 totalVotingPower, uint256 currentVotingPower,,,) = controller.functionToSetMajorityProposal(msgSig, "testSet"); -// console.log("totalVotingPower: ", totalVotingPower); -// console.log("currentVotingPower: ", currentVotingPower); -// } -// -// function testCreateSetAddVestingThenRemoveSet() public { -// -// // template from testCreateSetAddVestingThenRemoveSet -// address allocation1 = createDummyVestingAllocation(); -// address allocation2 = createDummyVestingAllocation(); -// address allocation3 = createDummyVestingAllocation(); +// vm.expectRevert(); +// controller.updateExerciseOrRepurchasePrice(allocation1, 999999999999999999999e18); // -// vm.startPrank(authority); -// controller.createSet("testSetB"); -// controller.addMetaVestToSet("testSetB", allocation1); -// controller.addMetaVestToSet("testSetB", allocation2); -// controller.addMetaVestToSet("testSetB", allocation3); -// controller.removeSet("testSetB"); -// controller.createSet("testSetB"); -// vm.stopPrank(); +// // Bypass MetaVesTController_AmendmentNeitherMutualNorMajorityConsented +// vm.prank(authority); +// bytes memory p = abi.encodeWithSelector(msgSig, allocation1, 999999999999999999999e18, 2e18); +// address(controller).call(p); // +// console.log('Modified excercise price: ', TokenOptionAllocation(allocation1).exercisePrice()); // } + function testAuditConsentToMetavestAmendmentInFlavor() public { + // template from testRemoveMilestone + address vestingAllocation = createDummyVestingAllocation(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, false); + // emit MetaVesTController_AmendmentConsentUpdated(msgSig: 0x75b89e4f00000000000000000000000000000000000000000000000000000000, grantee: ECAdd: [0x0000000000000000000000000000000000000006], inFavor: false) + console.log("expected inFavor: false"); + (,,bool inFavor) = controller.functionToGranteeToAmendmentPending(selector, vestingAllocation); + console.log("output: ", inFavor); + assertEq(inFavor, false); + + } + + function test_RevertIf_AuditProposeMajorityMetavestAmendmentNewGranteeDuringProposal() public { + // template from testProposeMajorityMetavestAmendment + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + address mockAllocation4 = createDummyVestingAllocation(); + address mockAllocation5 = createDummyVestingAllocation(); + address mockAllocation6 = createDummyVestingAllocation(); + address mockAllocation7 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.startPrank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation4); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation5); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation6); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation7); + vm.stopPrank(); + + vm.startPrank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation5, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation6, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation7, "testSet", msgSig, true); + vm.stopPrank(); + + (uint256 totalVotingPower, uint256 currentVotingPower,,,) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + console.log("totalVotingPower: ", totalVotingPower); + console.log("currentVotingPower: ", currentVotingPower); + } + + function testCreateSetAddVestingThenRemoveSet() public { + + // template from testCreateSetAddVestingThenRemoveSet + address allocation1 = createDummyVestingAllocation(); + address allocation2 = createDummyVestingAllocation(); + address allocation3 = createDummyVestingAllocation(); + + vm.startPrank(authority); + controller.createSet("testSetB"); + controller.addMetaVestToSet("testSetB", allocation1); + controller.addMetaVestToSet("testSetB", allocation2); + controller.addMetaVestToSet("testSetB", allocation3); + controller.removeSet("testSetB"); + controller.createSet("testSetB"); + vm.stopPrank(); + + } + } \ No newline at end of file diff --git a/test/AuditBaseC.t.sol b/test/AuditBaseC.t.sol index b0521dd..b466e23 100644 --- a/test/AuditBaseC.t.sol +++ b/test/AuditBaseC.t.sol @@ -7,55 +7,55 @@ import "../test/controller.t.sol"; // TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract Audit is MetaVestControllerTest { -// function testAuditTerminateFailAfterWithdrawFixCheck() public { + function testAuditTerminateFailAfterWithdrawFixCheck() public { + // template from testTerminateVestAndRecoverSlowUnlock + address vestingAllocation = createDummyVestingAllocationSlowUnlock(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.startPrank(grantee); + skip(25 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 1000e18 + 100e18 + 25 * 5e18, "Unexpected amount after cliff and milestone"); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + skip(25 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 25 * 5e18, "Unexpected amount after the second vesting period"); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + skip(1200 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), (10e18 - 5e18) * 50, "Unexpected amount after termination"); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + +// function testAuditTerminateFailAfterWithdrawFixCheckOptions() public { // // template from testTerminateVestAndRecoverSlowUnlock -// address vestingAllocation = createDummyVestingAllocationSlowUnlock(); +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); // VestingAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 5 seconds); // vm.startPrank(grantee); -// skip(25 seconds); -// assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 1000e18 + 100e18 + 25 * 5e18, "Unexpected amount after cliff and milestone"); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// skip(25 seconds); -// assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 25 * 5e18, "Unexpected amount after the second vesting period"); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.warp(block.timestamp + 5 seconds); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// +// vm.warp(block.timestamp + 5 seconds); // controller.terminateMetavestVesting(vestingAllocation); -// vm.warp(block.timestamp + 365 days); -// assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), (10e18 - 5e18) * 50, "Unexpected amount after termination"); +// // vm.startPrank(grantee); -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// } +// vm.warp(block.timestamp + 365 days); // -//// function testAuditTerminateFailAfterWithdrawFixCheckOptions() public { -//// // template from testTerminateVestAndRecoverSlowUnlock -//// address vestingAllocation = createDummyTokenOptionAllocation(); -//// uint256 snapshot = token.balanceOf(authority); -//// VestingAllocation(vestingAllocation).confirmMilestone(0); -//// vm.warp(block.timestamp + 5 seconds); -//// vm.startPrank(grantee); -//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -//// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -//// vm.warp(block.timestamp + 5 seconds); -//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -//// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -//// vm.stopPrank(); -//// vm.warp(block.timestamp + 5 seconds); -//// controller.terminateMetavestVesting(vestingAllocation); -//// -//// vm.startPrank(grantee); -//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -//// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -//// vm.stopPrank(); -//// vm.warp(block.timestamp + 365 days); -//// -//// vm.prank(authority); -//// TokenOptionAllocation(vestingAllocation).recoverForfeitTokens(); -//// //check balance of the vesting contract -//// assertEq(token.balanceOf(vestingAllocation), 0); -//// } +// vm.prank(authority); +// TokenOptionAllocation(vestingAllocation).recoverForfeitTokens(); +// //check balance of the vesting contract +// assertEq(token.balanceOf(vestingAllocation), 0); +// } } \ No newline at end of file diff --git a/test/AuditBaseC3.t.sol b/test/AuditBaseC3.t.sol index 89f1819..31f356e 100644 --- a/test/AuditBaseC3.t.sol +++ b/test/AuditBaseC3.t.sol @@ -7,81 +7,82 @@ import "../test/controller.t.sol"; // TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract Audit is MetaVestControllerTest { -// function testAuditTerminateVestAndRecovers() public { -// // template from testTerminateVestAndRecovers -// address vestingAllocation = createDummyVestingAllocation(); + function testAuditTerminateVestAndRecovers() public { + // template from testTerminateVestAndRecovers + address vestingAllocation = createDummyVestingAllocation(); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000e18, + unlockOnCompletion: false, + complete: false, + conditionContracts: new address[](0) + }); + vm.prank(authority); + controller.addMetavestMilestone(vestingAllocation, milestones[0]); + VestingAllocation(vestingAllocation).confirmMilestone(1); + + skip(50 seconds); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + assertEq(VestingAllocation(vestingAllocation).getVestedTokenAmount(), 100e18 + 1000e18 + 10e18 * 50, "Unexpected vested amount after termination"); + assertEq(VestingAllocation(vestingAllocation).getUnlockedTokenAmount(), 100e18 + 5e18 * 50 + 5e18 * 50, "Unexpected unlocked amount after termination"); + + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + // assertEq(token.balanceOf(vestingAllocation), 0); + } + +// function test_RevertIf_AuditRounding() public { +// // template from testConfirmingMilestoneTokenOption +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); // -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 1000e18, -// unlockOnCompletion: false, -// complete: false, -// conditionContracts: new address[](0) -// }); +// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e6)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e11)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(9.99e11)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e12)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1.1e12)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e13)); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1.1e12); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); // -// controller.addMetavestMilestone(vestingAllocation, milestones[0]); -// VestingAllocation(vestingAllocation).confirmMilestone(1); +// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } // -// skip(50 seconds); -// controller.terminateMetavestVesting(vestingAllocation); +// function testAuditExcercisePrice() public { +// // template from testConfirmingMilestoneTokenOption +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); // vm.startPrank(grantee); -// assertEq(VestingAllocation(vestingAllocation).getVestedTokenAmount(), 100e18 + 1000e18 + 10e18 * 50, "Unexpected vested amount after termination"); -// assertEq(VestingAllocation(vestingAllocation).getUnlockedTokenAmount(), 100e18 + 5e18 * 50 + 5e18 * 50, "Unexpected unlocked amount after termination"); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// +// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); // -// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e18); +// +// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); -// // assertEq(token.balanceOf(vestingAllocation), 0); // } -// -//// function test_RevertIf_AuditRounding() public { -//// // template from testConfirmingMilestoneTokenOption -//// address vestingAllocation = createDummyTokenOptionAllocation(); -//// uint256 snapshot = token.balanceOf(authority); -//// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); -//// vm.warp(block.timestamp + 50 seconds); -//// vm.startPrank(grantee); -//// //exercise max available -//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -//// -//// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); -//// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e6)); -//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e11)); -//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(9.99e11)); -//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e12)); -//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1.1e12)); -//// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e13)); -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1.1e12); -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -//// -//// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); -//// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -//// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -//// vm.stopPrank(); -//// } -//// -//// function testAuditExcercisePrice() public { -//// // template from testConfirmingMilestoneTokenOption -//// address vestingAllocation = createDummyTokenOptionAllocation(); -//// uint256 snapshot = token.balanceOf(authority); -//// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); -//// vm.warp(block.timestamp + 50 seconds); -//// vm.startPrank(grantee); -//// //exercise max available -//// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -//// -//// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); -//// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -//// -//// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e18); -//// -//// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); -//// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -//// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -//// vm.stopPrank(); -//// } } diff --git a/test/amendement.t.sol b/test/amendement.t.sol index 123ec51..2edf871 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; -import "../src/metavestController.sol"; import "../src/BaseAllocation.sol"; import "../src/RestrictedTokenAllocation.sol"; import "../src/interfaces/IAllocationFactory.sol"; @@ -10,627 +9,130 @@ import "../src/VestingAllocationFactory.sol"; import "../src/TokenOptionFactory.sol"; import "../src/RestrictedTokenFactory.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "./lib/MetaVesTControllerTestBase.sol"; -abstract contract ERC20 { - - /// @dev The total supply has overflowed. - error TotalSupplyOverflow(); - - /// @dev The allowance has overflowed. - error AllowanceOverflow(); - - /// @dev The allowance has underflowed. - error AllowanceUnderflow(); - - /// @dev Insufficient balance. - error InsufficientBalance(); - - /// @dev Insufficient allowance. - error InsufficientAllowance(); - - /// @dev The permit is invalid. - error InvalidPermit(); - - /// @dev The permit has expired. - error PermitExpired(); - - /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 amount); +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract MetaVestControllerTest is MetaVesTControllerTestBase { + address public authority = guardianSafe; + address public dao = guardianSafe; + address public grantee = alice; - /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. - event Approval(address indexed owner, address indexed spender, uint256 amount); + bytes32 salt = keccak256("MetaVestControllerTest"); + uint256 cap = 2000 ether; + uint48 cappedMinterStartTime = uint48(block.timestamp); // Minter start now + uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 1600); // Minter expired 1600 seconds after start - /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. - uint256 private constant _TRANSFER_EVENT_SIGNATURE = - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; + address public vestingAllocation; - /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. - uint256 private constant _APPROVAL_EVENT_SIGNATURE = - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; + function setUp() public { + vm.startPrank(deployer); - /// @dev The storage slot for the total supply. - uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; + // Deploy MetaVesT controller - /// @dev The balance slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _BALANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let balanceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; + vestingAllocationFactory = new VestingAllocationFactory(); - /// @dev The allowance slot of (`owner`, `spender`) is given by: - /// ``` - /// mstore(0x20, spender) - /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let allowanceSlot := keccak256(0x0c, 0x34) - /// ``` - uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; + controller = new metavestController{salt: salt}( + guardianSafe, + guardianSafe, + address(vestingAllocationFactory) + ); - /// @dev The nonce slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _NONCES_SLOT_SEED) - /// mstore(0x00, owner) - /// let nonceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _NONCES_SLOT_SEED = 0x38377508; + // Deploy ZK Capped Minter v2 + zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + cap, + cappedMinterStartTime, + cappedMinterExpirationTime, + uint256(salt) + )); - /// @dev Returns the name of the token. - function name() public view virtual returns (string memory); + vm.stopPrank(); - /// @dev Returns the symbol of the token. - function symbol() public view virtual returns (string memory); + vm.startPrank(guardianSafe); + controller.setZkCappedMinter(address(zkCappedMinter)); + controller.createSet("testSet"); + vm.stopPrank(); - /// @dev Returns the decimals places of the token. - function decimals() public view virtual returns (uint8) { - return 18; + vestingAllocation = createDummyVestingAllocation(); } + function testProposeMetavestAmendment() public { + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); - /// @dev Returns the amount of tokens in existence. - function totalSupply() public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - result := sload(_TOTAL_SUPPLY_SLOT) - } - } + vm.prank(authority); + controller.proposeMetavestAmendment(address(vestingAllocation), msgSig, callData); - /// @dev Returns the amount of tokens owned by `owner`. - function balanceOf(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } + (bool isPending, bytes32 dataHash, bool inFavor) = controller.functionToGranteeToAmendmentPending(msgSig, address(vestingAllocation)); - /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. - function allowance(address owner, address spender) - public - view - virtual - returns (uint256 result) - { - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x34)) - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - /// - /// Emits a {Approval} event. - function approve(address spender, uint256 amount) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically increases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Add to the allowance. - let allowanceAfter := add(allowanceBefore, difference) - // Revert upon overflow. - if lt(allowanceAfter, allowanceBefore) { - mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated allowance. - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically decreases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Revert if will underflow. - if lt(allowanceBefore, difference) { - mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - let allowanceAfter := sub(allowanceBefore, difference) - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; + assertTrue(isPending); + assertEq(dataHash, keccak256(callData)); + assertFalse(inFavor); } - /// @dev Transfer `amount` tokens from the caller to `to`. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(msg.sender, to, amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, caller()) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) - } - _afterTokenTransfer(msg.sender, to, amount); - return true; + function test_RevertIf_ProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + address mockAllocation4 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation4); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + //log the current withdrawable + console.log(VestingAllocation(mockAllocation2).getAmountWithdrawable()); + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(mockAllocation2, true); } - /// @dev Transfers `amount` tokens from `from` to `to`. - /// - /// Note: Does not update the allowance if it is the maximum uint256 value. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the allowance slot and load its value. - mstore(0x20, caller()) - mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - return true; + function testQuickProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + address mockAllocation4 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation4); + vm.warp(block.timestamp + 15 seconds); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.startPrank(grantee); + //log the current withdrawable + console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); + + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); + vm.stopPrank(); + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true); } - /// @dev Returns the current nonce for `owner`. - /// This value is used to compute the signature for EIP-2612 permit. - function nonces(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, - /// authorized by a signed approval by `owner`. - /// - /// Emits a {Approval} event. - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - bytes32 domainSeparator = DOMAIN_SEPARATOR(); - /// @solidity memory-safe-assembly - assembly { - // Grab the free memory pointer. - let m := mload(0x40) - // Revert if the block timestamp greater than `deadline`. - if gt(timestamp(), deadline) { - mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. - revert(0x1c, 0x04) - } - // Clean the upper 96 bits. - owner := shr(96, shl(96, owner)) - spender := shr(96, shl(96, spender)) - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - let nonceSlot := keccak256(0x0c, 0x20) - let nonceValue := sload(nonceSlot) - // Increment and store the updated nonce. - sstore(nonceSlot, add(nonceValue, 1)) - // Prepare the inner hash. - // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. - // forgefmt: disable-next-item - mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) - mstore(add(m, 0x20), owner) - mstore(add(m, 0x40), spender) - mstore(add(m, 0x60), value) - mstore(add(m, 0x80), nonceValue) - mstore(add(m, 0xa0), deadline) - // Prepare the outer hash. - mstore(0, 0x1901) - mstore(0x20, domainSeparator) - mstore(0x40, keccak256(m, 0xc0)) - // Prepare the ecrecover calldata. - mstore(0, keccak256(0x1e, 0x42)) - mstore(0x20, and(0xff, v)) - mstore(0x40, r) - mstore(0x60, s) - pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) - // If the ecrecover fails, the returndatasize will be 0x00, - // `owner` will be be checked if it equals the hash at 0x00, - // which evaluates to false (i.e. 0), and we will revert. - // If the ecrecover succeeds, the returndatasize will be 0x20, - // `owner` will be compared against the returned address at 0x20. - if iszero(eq(mload(returndatasize()), owner)) { - mstore(0x00, 0xddafbaef) // `InvalidPermit()`. - revert(0x1c, 0x04) - } - // Compute the allowance slot and store the value. - // The `owner` is already at slot 0x20. - mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) - sstore(keccak256(0x2c, 0x34), value) - // Emit the {Approval} event. - log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) - mstore(0x40, m) // Restore the free memory pointer. - mstore(0x60, 0) // Restore the zero pointer. - } - } - - /// @dev Returns the EIP-2612 domains separator. - function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) // Grab the free memory pointer. - } - // We simply calculate it on-the-fly to allow for cases where the `name` may change. - bytes32 nameHash = keccak256(bytes(name())); - /// @solidity memory-safe-assembly - assembly { - let m := result - // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - // forgefmt: disable-next-item - mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) - mstore(add(m, 0x20), nameHash) - // `keccak256("1")`. - // forgefmt: disable-next-item - mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) - mstore(add(m, 0x60), chainid()) - mstore(add(m, 0x80), address()) - result := keccak256(m, 0xa0) - } - } - - /// @dev Mints `amount` tokens to `to`, increasing the total supply. - /// - /// Emits a {Transfer} event. - function _mint(address to, uint256 amount) internal virtual { - _beforeTokenTransfer(address(0), to, amount); - /// @solidity memory-safe-assembly - assembly { - let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) - let totalSupplyAfter := add(totalSupplyBefore, amount) - // Revert if the total supply overflows. - if lt(totalSupplyAfter, totalSupplyBefore) { - mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) - } - _afterTokenTransfer(address(0), to, amount); - } - - /// @dev Burns `amount` tokens from `from`, reducing the total supply. - /// - /// Emits a {Transfer} event. - function _burn(address from, uint256 amount) internal virtual { - _beforeTokenTransfer(from, address(0), amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, from) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Subtract and store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) - // Emit the {Transfer} event. - mstore(0x00, amount) - log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) - } - _afterTokenTransfer(from, address(0), amount); - } - - /// @dev Moves `amount` of tokens from `from` to `to`. - function _transfer(address from, address to, uint256 amount) internal virtual { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - } - - /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. - /// - /// Emits a {Approval} event. - function _approve(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - let owner_ := shl(96, owner) - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) - } - } - - /// @dev Hook that is called before any transfer of tokens. - /// This includes minting and burning. - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - /// @dev Hook that is called after any transfer of tokens. - /// This includes minting and burning. - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} - -contract MockERC20 is ERC20 { - constructor(string memory name, string memory symbol) ERC20() {} - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - function name() public view override returns (string memory) { - return "Test Token"; - } - function symbol() public view override returns (string memory) { - return "TT"; - } -} - -// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter -contract MetaVestControllerTest is Test { -// // zkSync Era Sepolia @ 5576300 -// address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; -// IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); -// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); -//// // zkSync Era mainnet -//// address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; -//// IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); -//// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); -// -// IZkCappedMinterV2 zkCappedMinter; -// -// metavestController public controller; -//// MockERC20 public paymentToken; -// address public authority; -// address public dao; -// address public vestingFactory; -// address public tokenOptionFactory; -// address public restrictedTokenFactory; -// address public grantee; -// address public mockAllocation; -// -// bytes32 salt = keccak256("MetaVestControllerTest"); -// -// function setUp() public { -// authority = address(this); -// dao = address(2); -// VestingAllocationFactory factory = new VestingAllocationFactory(); -// TokenOptionFactory tokenFactory = new TokenOptionFactory(); -// RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); -// grantee = address(6); -// -//// paymentToken = new MockERC20("Payment Token", "PT"); -// -// controller = new metavestController( -// authority, -// dao, -// address(factory), -// address(tokenFactory), -// address(restrictedTokenFactory) -// ); -// -//// paymentToken.mint(authority, 1000000e58); -//// -//// paymentToken.transfer(address(grantee), 1000e25); -// -// vm.prank(authority); -// controller.createSet("testSet"); -// -// // Deploy ZK Capped Minter v2 -// zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( -// address(zkToken), -// address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT -// 1000e18, -// uint48(block.timestamp), // start now -// uint48(block.timestamp + 365 days * 2), -// uint256(salt) -// )); -// -// vm.prank(authority); -// controller.setZkCappedMinter(address(zkCappedMinter)); -// -// mockAllocation = createDummyVestingAllocation(); -// } -// -// function testProposeMetavestAmendment() public { -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(address(mockAllocation), msgSig, callData); -// -// (bool isPending, bytes32 dataHash, bool inFavor) = controller.functionToGranteeToAmendmentPending(msgSig, address(mockAllocation)); -// -// assertTrue(isPending); -// assertEq(dataHash, keccak256(callData)); -// assertFalse(inFavor); -// } -// -// function test_RevertIf_ProposeMajorityMetavestAmendment() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// address mockAllocation3 = createDummyVestingAllocation(); -// address mockAllocation4 = createDummyVestingAllocation(); +// function testMajorityPowerMetavestAmendment() public { +// address mockAllocation2 = createDummyTokenOptionAllocation(); +// address mockAllocation3 = createDummyTokenOptionAllocation(); +// address mockAllocation4 = createDummyTokenOptionAllocation(); // bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); // bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); // @@ -639,263 +141,291 @@ contract MetaVestControllerTest is Test { // controller.addMetaVestToSet("testSet", mockAllocation3); // controller.addMetaVestToSet("testSet", mockAllocation4); // vm.warp(block.timestamp + 1 days); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// //log the current withdrawable -// console.log(VestingAllocation(mockAllocation2).getAmountWithdrawable()); -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); -// -// vm.prank(authority); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); -// controller.updateMetavestTransferability(mockAllocation2, true); -// } -// -// function testQuickProposeMajorityMetavestAmendment() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// address mockAllocation3 = createDummyVestingAllocation(); -// address mockAllocation4 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// controller.addMetaVestToSet("testSet", mockAllocation4); -// vm.warp(block.timestamp + 15 seconds); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// // vm.startPrank(grantee); -// //log the current withdrawable -// console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); -// -// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); -// -// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); // vm.stopPrank(); // vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true); -// } -// -// -//// function testMajorityPowerMetavestAmendment() public { -//// address mockAllocation2 = createDummyTokenOptionAllocation(); -//// address mockAllocation3 = createDummyTokenOptionAllocation(); -//// address mockAllocation4 = createDummyTokenOptionAllocation(); -//// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -//// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -//// -//// vm.prank(authority); -//// controller.addMetaVestToSet("testSet", mockAllocation2); -//// controller.addMetaVestToSet("testSet", mockAllocation3); -//// controller.addMetaVestToSet("testSet", mockAllocation4); -//// vm.warp(block.timestamp + 1 days); -//// vm.startPrank(grantee); -//// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); -//// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); -//// vm.stopPrank(); -//// vm.prank(authority); -//// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); -//// vm.prank(grantee); -//// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestTransferability(mockAllocation2, true); -//// } -// -//// function test_RevertIf_MajorityPowerMetavestAmendment() public { -//// address mockAllocation2 = createDummyTokenOptionAllocation(); -//// address mockAllocation3 = createDummyTokenOptionAllocation(); -//// address mockAllocation4 = createDummyTokenOptionAllocation(); -//// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -//// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -//// -//// vm.prank(authority); -//// controller.addMetaVestToSet("testSet", mockAllocation2); -//// controller.addMetaVestToSet("testSet", mockAllocation3); -//// controller.addMetaVestToSet("testSet", mockAllocation4); -//// vm.warp(block.timestamp + 1 days); -//// vm.startPrank(grantee); -//// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); -//// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); -//// vm.stopPrank(); -//// vm.prank(authority); -//// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); -//// vm.prank(grantee); -//// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestTransferability(mockAllocation2, true); -//// vm.prank(authority); -//// controller.updateMetavestTransferability(mockAllocation2, true); -//// } -// -// function testProposeMajorityMetavestAmendment() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// address mockAllocation3 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// vm.warp(block.timestamp + 1 days); -// vm.prank(authority); // controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); // // vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); // // vm.prank(authority); // controller.updateMetavestTransferability(mockAllocation2, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation3, true); // } -// -// function testProposeMajorityMetavestAmendmentReAdd() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// address mockAllocation3 = createDummyVestingAllocation(); + +// function test_RevertIf_MajorityPowerMetavestAmendment() public { +// address mockAllocation2 = createDummyTokenOptionAllocation(); +// address mockAllocation3 = createDummyTokenOptionAllocation(); +// address mockAllocation4 = createDummyTokenOptionAllocation(); // bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); // bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); // // vm.prank(authority); // controller.addMetaVestToSet("testSet", mockAllocation2); // controller.addMetaVestToSet("testSet", mockAllocation3); +// controller.addMetaVestToSet("testSet", mockAllocation4); // vm.warp(block.timestamp + 1 days); +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +// vm.stopPrank(); // vm.prank(authority); // controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); // // vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation3, true); -// -// vm.prank(authority); -// controller.removeMetaVestFromSet("testSet", mockAllocation3); -// // vm.prank(authority); -// // controller.updateMetavestTransferability(mockAllocation3, true); -// vm.warp(block.timestamp + 90 days); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); // vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); +// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); // // vm.prank(authority); // controller.updateMetavestTransferability(mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation3, true); -// } -// -// function test_RevertIf_NoPassProposeMajorityMetavestAmendment() public { -// address mockAllocation2 = createDummyVestingAllocation(); -// address mockAllocation3 = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// vm.warp(block.timestamp + 1 days); // vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(authority); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); // controller.updateMetavestTransferability(mockAllocation2, true); -// -// vm.prank(authority); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); -// controller.updateMetavestTransferability(mockAllocation3, true); -// } -// -// function testVoteOnMetavestAmendment() public { -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", address(mockAllocation)); -// -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); -// -// (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); -// -// } -// -// function test_RevertIf_VoteOnMetavestAmendmentTwice() public { -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", address(mockAllocation)); -// -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.startPrank(grantee); -// controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AlreadyVoted.selector)); -// controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); -// vm.stopPrank(); // } + + function testProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation3, true); + } + + function testProposeMajorityMetavestAmendmentReAdd() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation3, true); + + vm.prank(authority); + controller.removeMetaVestFromSet("testSet", mockAllocation3); + // vm.prank(authority); + // controller.updateMetavestTransferability(mockAllocation3, true); + vm.warp(block.timestamp + 90 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation3, true); + } + + function test_RevertIf_NoPassProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(mockAllocation2, true); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(mockAllocation3, true); + } + + function testVoteOnMetavestAmendment() public { + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); + + (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + + } + + function test_RevertIf_VoteOnMetavestAmendmentTwice() public { + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.startPrank(grantee); + controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AlreadyVoted.selector)); + controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); + vm.stopPrank(); + } + + function testSetManagement() public { + vm.startPrank(authority); + + // Test creating a new set + controller.createSet("newSet"); + + // Test adding a MetaVest to a set + controller.addMetaVestToSet("newSet", address(vestingAllocation)); + + + // Test removing a MetaVest from a set + controller.removeMetaVestFromSet("newSet", address(vestingAllocation)); + + + // Test removing a set + controller.removeSet("newSet"); + + + vm.stopPrank(); + } + + function test_RevertIf_CreateDuplicateSet() public { + vm.startPrank(authority); + controller.createSet("duplicateSet"); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetAlreadyExists.selector)); + controller.createSet("duplicateSet"); + vm.stopPrank(); + } + + function test_RevertIf_NonAuthorityCreateSet() public { + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); + controller.createSet("unauthorizedSet"); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation() internal returns (address) { + return createDummyVestingAllocation(""); // Expect no reverts + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation(bytes memory expectRevertData) internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 100 ether, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + return controller.createMetavest(contractIdAlice); + } + +// function createDummyTokenOptionAllocation() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); // -// function testSetManagement() public { -// vm.startPrank(authority); -// -// // Test creating a new set -// controller.createSet("newSet"); -// -// // Test adding a MetaVest to a set -// controller.addMetaVestToSet("newSet", address(mockAllocation)); -// -// -// // Test removing a MetaVest from a set -// controller.removeMetaVestFromSet("newSet", address(mockAllocation)); -// -// -// // Test removing a set -// controller.removeSet("newSet"); -// -// -// vm.stopPrank(); -// } +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); // -// function test_RevertIf_CreateDuplicateSet() public { -// vm.startPrank(authority); -// controller.createSet("duplicateSet"); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetAlreadyExists.selector)); -// controller.createSet("duplicateSet"); -// vm.stopPrank(); -// } +// token.approve(address(controller), 1100e18); // -// function test_RevertIf_NonAuthorityCreateSet() public { -// vm.prank(grantee); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); -// controller.createSet("unauthorizedSet"); +// return controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// ); // } // -// // Helper functions to create dummy allocations for testing -// function createDummyVestingAllocation() internal returns (address) { +// function createDummyRestrictedTokenAward() internal returns (address) { // BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), +// tokenContract: address(token), // tokenStreamTotal: 1000e18, // vestingCliffCredit: 100e18, // unlockingCliffCredit: 100e18, @@ -913,158 +443,206 @@ contract MetaVestControllerTest is Test { // conditionContracts: new address[](0) // }); // +// token.approve(address(controller), 1100e18); +// // return controller.createMetavest( -// metavestController.metavestType.Vesting, +// metavestController.metavestType.RestrictedTokenAward, // grantee, // allocation, // milestones, -// 0, -// address(0), -// 0, +// 1e18, +// address(paymentToken), +// 365 days, // 0 // // ); // } + + //write a test for every consentcheck function in metavest controller + function testConsentCheck() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(allocation, true); + } + + function test_RevertIf_ConsentCheck() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(allocation, true); + } + + function test_RevertIf_ConsentCheckNoProposal() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); + } + + function test_RevertIf_ConsentCheckNoVote() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(allocation, true); + } + + function test_RevertIf_ConsentCheckNoUpdate() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); + } + + function test_RevertIf_ConsentCheckNoVoteUpdate() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); + } + +// function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); // -//// function createDummyTokenOptionAllocation() internal returns (address) { -//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -//// tokenContract: address(token), -//// tokenStreamTotal: 1000e18, -//// vestingCliffCredit: 100e18, -//// unlockingCliffCredit: 100e18, -//// vestingRate: 10e18, -//// vestingStartTime: uint48(block.timestamp), -//// unlockRate: 10e18, -//// unlockStartTime: uint48(block.timestamp) -//// }); -//// -//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -//// milestones[0] = BaseAllocation.Milestone({ -//// milestoneAward: 100e18, -//// unlockOnCompletion: true, -//// complete: false, -//// conditionContracts: new address[](0) -//// }); -//// -//// token.approve(address(controller), 1100e18); -//// -//// return controller.createMetavest( -//// metavestController.metavestType.TokenOption, -//// grantee, -//// allocation, -//// milestones, -//// 1e18, -//// address(paymentToken), -//// 365 days, -//// 0 -//// ); -//// } -//// -//// function createDummyRestrictedTokenAward() internal returns (address) { -//// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -//// tokenContract: address(token), -//// tokenStreamTotal: 1000e18, -//// vestingCliffCredit: 100e18, -//// unlockingCliffCredit: 100e18, -//// vestingRate: 10e18, -//// vestingStartTime: uint48(block.timestamp), -//// unlockRate: 10e18, -//// unlockStartTime: uint48(block.timestamp) -//// }); -//// -//// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -//// milestones[0] = BaseAllocation.Milestone({ -//// milestoneAward: 100e18, -//// unlockOnCompletion: true, -//// complete: false, -//// conditionContracts: new address[](0) -//// }); -//// -//// token.approve(address(controller), 1100e18); -//// -//// return controller.createMetavest( -//// metavestController.metavestType.RestrictedTokenAward, -//// grantee, -//// allocation, -//// milestones, -//// 1e18, -//// address(paymentToken), -//// 365 days, -//// 0 -//// -//// ); -//// } -// -// //write a test for every consentcheck function in metavest controller -// function testConsentCheck() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); // // vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); // // vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); +// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); // // vm.prank(authority); -// controller.updateMetavestTransferability(allocation, true); -// } +// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); // -// function test_RevertIf_ConsentCheck() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); // // vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); // -// vm.prank(grantee); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); +// // Check that the exercise price was updated +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +// } + +// function test_RevertIf_CreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); // // vm.prank(authority); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); -// controller.updateMetavestTransferability(allocation, true); -// } +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); // -// function test_RevertIf_ConsentCheckNoProposal() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); // -// vm.prank(grantee); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); -// } +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); // -// function test_RevertIf_ConsentCheckNoVote() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); // // vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// //vm.prank(grantee); +// // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// // vm.prank(grantee); +// // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); // // vm.prank(authority); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); -// controller.updateMetavestTransferability(allocation, true); -// } +// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); // -// function test_RevertIf_ConsentCheckNoUpdate() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); // // vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); // -// vm.prank(grantee); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); +// // Check that the exercise price was updated +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); // } -// -// function test_RevertIf_ConsentCheckNoVoteUpdate() public { -// address allocation = createDummyVestingAllocation(); + + function test_RevertIf_consentToNoPendingAmendment() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_NoPendingAmendment.selector, msgSig, allocation)); + controller.consentToMetavestAmendment(allocation, msgSig, true); + } + +// function testEveryUpdateAmendmentFunction() public { +// address allocation = createDummyTokenOptionAllocation(); // bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); // bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); // @@ -1072,266 +650,13 @@ contract MetaVestControllerTest is Test { // controller.proposeMetavestAmendment(allocation, msgSig, callData); // // vm.prank(grantee); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); -// controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); -// } -// -//// function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { -//// address allocation1 = createDummyTokenOptionAllocation(); -//// address allocation2 = createDummyTokenOptionAllocation(); -//// address allocation3 = createDummyTokenOptionAllocation(); -//// -//// vm.prank(authority); -//// controller.addMetaVestToSet("testSet", allocation1); -//// controller.addMetaVestToSet("testSet", allocation2); -//// controller.addMetaVestToSet("testSet", allocation3); -//// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); -//// vm.warp(block.timestamp + 25 seconds); -//// -//// -//// vm.startPrank(grantee); -//// ERC20(paymentToken).approve(address(allocation1), 2000e18); -//// ERC20(paymentToken).approve(address(allocation2), 2000e18); -//// -//// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); -//// -//// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); -//// vm.stopPrank(); -//// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -//// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); -//// -//// vm.prank(authority); -//// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); -//// -//// vm.prank(grantee); -//// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); -//// -//// vm.prank(authority); -//// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); -//// -//// vm.prank(authority); -//// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); -//// -//// // Check that the exercise price was updated -//// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); -//// } -// -//// function test_RevertIf_CreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { -//// address allocation1 = createDummyTokenOptionAllocation(); -//// address allocation2 = createDummyTokenOptionAllocation(); -//// address allocation3 = createDummyTokenOptionAllocation(); -//// -//// vm.prank(authority); -//// controller.addMetaVestToSet("testSet", allocation1); -//// controller.addMetaVestToSet("testSet", allocation2); -//// controller.addMetaVestToSet("testSet", allocation3); -//// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); -//// vm.warp(block.timestamp + 25 seconds); -//// -//// -//// vm.startPrank(grantee); -//// ERC20(paymentToken).approve(address(allocation1), 2000e18); -//// ERC20(paymentToken).approve(address(allocation2), 2000e18); -//// -//// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); -//// -//// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); -//// vm.stopPrank(); -//// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -//// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); -//// -//// vm.prank(authority); -//// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -//// -//// //vm.prank(grantee); -//// // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); -//// -//// // vm.prank(grantee); -//// // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); -//// -//// vm.prank(authority); -//// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); -//// -//// vm.prank(authority); -//// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); -//// -//// // Check that the exercise price was updated -//// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); -//// } -// -// function test_RevertIf_consentToNoPendingAmendment() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -// -// vm.prank(grantee); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_NoPendingAmendment.selector, msgSig, allocation)); // controller.consentToMetavestAmendment(allocation, msgSig, true); -// } // -//// function testEveryUpdateAmendmentFunction() public { -//// address allocation = createDummyTokenOptionAllocation(); -//// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -//// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestTransferability(allocation, true); -//// -//// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -//// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); -//// -//// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); -//// callData = abi.encodeWithSelector(msgSig, allocation, 0); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.removeMetavestMilestone(allocation, 0); -//// -//// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); -//// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestUnlockRate(allocation, 20e18); -//// -//// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -//// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestVestingRate(allocation, 20e18); -//// -//// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); -//// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); -//// } -// -//// function testEveryUpdateAmendmentFunctionRestricted() public { -//// address allocation = createDummyRestrictedTokenAward(); -//// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -//// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestTransferability(allocation, true); -//// -//// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -//// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); -//// -//// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); -//// callData = abi.encodeWithSelector(msgSig, allocation, 0); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.removeMetavestMilestone(allocation, 0); -//// -//// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); -//// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestUnlockRate(allocation, 20e18); -//// -//// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -//// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.updateMetavestVestingRate(allocation, 20e18); -//// -//// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); -//// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); -//// -//// vm.prank(authority); -//// controller.proposeMetavestAmendment(allocation, msgSig, callData); -//// -//// vm.prank(grantee); -//// controller.consentToMetavestAmendment(allocation, msgSig, true); -//// -//// vm.prank(authority); -//// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); -//// } -// -// function testEveryUpdateAmendmentFunctionVesting() public { -// address allocation = createDummyVestingAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// vm.prank(authority); +// controller.updateMetavestTransferability(allocation, true); +// +// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); // // vm.prank(authority); // controller.proposeMetavestAmendment(allocation, msgSig, callData); @@ -1340,7 +665,7 @@ contract MetaVestControllerTest is Test { // controller.consentToMetavestAmendment(allocation, msgSig, true); // // vm.prank(authority); -// controller.updateMetavestTransferability(allocation, true); +// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); // // msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); // callData = abi.encodeWithSelector(msgSig, allocation, 0); @@ -1390,9 +715,9 @@ contract MetaVestControllerTest is Test { // vm.prank(authority); // controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); // } -// -// function test_RevertIf_EveryUpdateAmendmentFunctionVesting() public { -// address allocation = createDummyVestingAllocation(); + +// function testEveryUpdateAmendmentFunctionRestricted() public { +// address allocation = createDummyRestrictedTokenAward(); // bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); // bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); // @@ -1405,6 +730,18 @@ contract MetaVestControllerTest is Test { // vm.prank(authority); // controller.updateMetavestTransferability(allocation, true); // +// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); +// // msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); // callData = abi.encodeWithSelector(msgSig, allocation, 0); // @@ -1447,8 +784,134 @@ contract MetaVestControllerTest is Test { // vm.prank(authority); // controller.proposeMetavestAmendment(allocation, msgSig, callData); // +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// // vm.prank(authority); -// vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); // controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); // } + + function testEveryUpdateAmendmentFunctionVesting() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(allocation, true); + + msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + callData = abi.encodeWithSelector(msgSig, allocation, 0); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.removeMetavestMilestone(allocation, 0); + + msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestUnlockRate(allocation, 20e18); + + msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(allocation, 20e18); + + msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); + callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); + } + + function test_RevertIf_EveryUpdateAmendmentFunctionVesting() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(allocation, true); + + msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + callData = abi.encodeWithSelector(msgSig, allocation, 0); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.removeMetavestMilestone(allocation, 0); + + msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestUnlockRate(allocation, 20e18); + + msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(allocation, 20e18); + + msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); + callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); + } } From 0d97fbf29137d5a207f6497253fe6684ee3e4ea8 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 6 Aug 2025 11:51:55 -0700 Subject: [PATCH 17/68] wip: test: temporarily remove breaking tests --- test/AuditBaseA.t.sol | 59 -- test/AuditBaseA2.t.sol | 220 ------ test/AuditBaseC.t.sol | 61 -- test/AuditBaseC3.t.sol | 88 --- test/amendement.t.sol | 917 ------------------------- test/controller.t.sol | 1470 ---------------------------------------- 6 files changed, 2815 deletions(-) delete mode 100644 test/AuditBaseA.t.sol delete mode 100644 test/AuditBaseA2.t.sol delete mode 100644 test/AuditBaseC.t.sol delete mode 100644 test/AuditBaseC3.t.sol delete mode 100644 test/amendement.t.sol delete mode 100644 test/controller.t.sol diff --git a/test/AuditBaseA.t.sol b/test/AuditBaseA.t.sol deleted file mode 100644 index 1846b52..0000000 --- a/test/AuditBaseA.t.sol +++ /dev/null @@ -1,59 +0,0 @@ -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; - -import "../test/amendement.t.sol"; - -contract EvilGrant { - - function grantee () public view returns (address) { - return address(0x31337); - } - function getGoverningPower() public view returns (uint256) { - return 99999999999999999999999999999; - } -} - -contract Audit is MetaVestControllerTest { - function test_RevertIf_AuditArbitraryVote() public { - // template from testVoteOnMetavestAmendment - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(vestingAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - address attacker = address(0x31337); - address evil_grant = address(new EvilGrant()); - - vm.prank(attacker); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("attacker made vote and power is" , currentVotingPower); - } - - function test_RevertIf_AuditRemoveConfirmedMilestone() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); - vm.prank(authority); - controller.removeMetavestMilestone(vestingAllocation, 0); - } - -} \ No newline at end of file diff --git a/test/AuditBaseA2.t.sol b/test/AuditBaseA2.t.sol deleted file mode 100644 index 6a4288e..0000000 --- a/test/AuditBaseA2.t.sol +++ /dev/null @@ -1,220 +0,0 @@ -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; - -import "../test/amendement.t.sol"; - -contract EvilGrant { - - function grantee () public view returns (address) { - return address(0x31337); - } - function getGoverningPower() public view returns (uint256) { - return 99999999999999999999999999999; - } -} - -// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter -contract Audit is MetaVestControllerTest { - function test_RevertIf_AuditArbitraryVote() public { - // template from testVoteOnMetavestAmendment - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(vestingAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - address attacker = address(0x31337); - address evil_grant = address(new EvilGrant()); - - vm.prank(attacker); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("attacker made vote and power is" , currentVotingPower); - } - - function test_RevertIf_AuditRemoveConfirmedMilestone() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); - vm.prank(authority); - controller.removeMetavestMilestone(vestingAllocation, 0); - } - - function testAuditProposeMajorityMetavestAmendmentExpire() public { - // template from testProposeMajorityMetavestAmendment - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - // proposal expired - uint256 AMENDMENT_TIME_LIMIT = 604800; - vm.warp(block.timestamp + AMENDMENT_TIME_LIMIT + 1); - - // MetaVesTController_AmendmentAlreadyPending even expired - vm.prank(authority); - vm.expectRevert(); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - } - -// function testAuditModifiedCalldataProposal() public { -// // template from testCreateSetWithThreeTokenOptionsAndChangeExercisePrice -// address allocation1 = createDummyTokenOptionAllocation(); -// address allocation2 = createDummyTokenOptionAllocation(); -// address allocation3 = createDummyTokenOptionAllocation(); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", allocation1); -// controller.addMetaVestToSet("testSet", allocation2); -// controller.addMetaVestToSet("testSet", allocation3); -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); -// vm.warp(block.timestamp + 25 seconds); -// -// -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(allocation1), 2000e18); -// ERC20(paymentToken).approve(address(allocation2), 2000e18); -// -// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); -// -// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); -// vm.stopPrank(); -// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); -// -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); -// -// vm.prank(authority); -// vm.expectRevert(); -// controller.updateExerciseOrRepurchasePrice(allocation1, 999999999999999999999e18); -// -// // Bypass MetaVesTController_AmendmentNeitherMutualNorMajorityConsented -// vm.prank(authority); -// bytes memory p = abi.encodeWithSelector(msgSig, allocation1, 999999999999999999999e18, 2e18); -// address(controller).call(p); -// -// console.log('Modified excercise price: ', TokenOptionAllocation(allocation1).exercisePrice()); -// } - - function testAuditConsentToMetavestAmendmentInFlavor() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, false); - // emit MetaVesTController_AmendmentConsentUpdated(msgSig: 0x75b89e4f00000000000000000000000000000000000000000000000000000000, grantee: ECAdd: [0x0000000000000000000000000000000000000006], inFavor: false) - console.log("expected inFavor: false"); - (,,bool inFavor) = controller.functionToGranteeToAmendmentPending(selector, vestingAllocation); - console.log("output: ", inFavor); - assertEq(inFavor, false); - - } - - function test_RevertIf_AuditProposeMajorityMetavestAmendmentNewGranteeDuringProposal() public { - // template from testProposeMajorityMetavestAmendment - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - address mockAllocation4 = createDummyVestingAllocation(); - address mockAllocation5 = createDummyVestingAllocation(); - address mockAllocation6 = createDummyVestingAllocation(); - address mockAllocation7 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.startPrank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.addMetaVestToSet("testSet", mockAllocation5); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.addMetaVestToSet("testSet", mockAllocation6); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - controller.addMetaVestToSet("testSet", mockAllocation7); - vm.stopPrank(); - - vm.startPrank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(mockAllocation5, "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(mockAllocation6, "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(mockAllocation7, "testSet", msgSig, true); - vm.stopPrank(); - - (uint256 totalVotingPower, uint256 currentVotingPower,,,) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("totalVotingPower: ", totalVotingPower); - console.log("currentVotingPower: ", currentVotingPower); - } - - function testCreateSetAddVestingThenRemoveSet() public { - - // template from testCreateSetAddVestingThenRemoveSet - address allocation1 = createDummyVestingAllocation(); - address allocation2 = createDummyVestingAllocation(); - address allocation3 = createDummyVestingAllocation(); - - vm.startPrank(authority); - controller.createSet("testSetB"); - controller.addMetaVestToSet("testSetB", allocation1); - controller.addMetaVestToSet("testSetB", allocation2); - controller.addMetaVestToSet("testSetB", allocation3); - controller.removeSet("testSetB"); - controller.createSet("testSetB"); - vm.stopPrank(); - - } - -} \ No newline at end of file diff --git a/test/AuditBaseC.t.sol b/test/AuditBaseC.t.sol deleted file mode 100644 index b466e23..0000000 --- a/test/AuditBaseC.t.sol +++ /dev/null @@ -1,61 +0,0 @@ -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; - -import "../test/controller.t.sol"; - -// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter -contract Audit is MetaVestControllerTest { - - function testAuditTerminateFailAfterWithdrawFixCheck() public { - // template from testTerminateVestAndRecoverSlowUnlock - address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.startPrank(grantee); - skip(25 seconds); - assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 1000e18 + 100e18 + 25 * 5e18, "Unexpected amount after cliff and milestone"); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - skip(25 seconds); - assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 25 * 5e18, "Unexpected amount after the second vesting period"); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - vm.prank(authority); - controller.terminateMetavestVesting(vestingAllocation); - skip(1200 seconds); - assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), (10e18 - 5e18) * 50, "Unexpected amount after termination"); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - -// function testAuditTerminateFailAfterWithdrawFixCheckOptions() public { -// // template from testTerminateVestAndRecoverSlowUnlock -// address vestingAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); -// VestingAllocation(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 5 seconds); -// vm.startPrank(grantee); -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.warp(block.timestamp + 5 seconds); -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); -// vm.warp(block.timestamp + 5 seconds); -// controller.terminateMetavestVesting(vestingAllocation); -// -// vm.startPrank(grantee); -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); -// vm.warp(block.timestamp + 365 days); -// -// vm.prank(authority); -// TokenOptionAllocation(vestingAllocation).recoverForfeitTokens(); -// //check balance of the vesting contract -// assertEq(token.balanceOf(vestingAllocation), 0); -// } -} \ No newline at end of file diff --git a/test/AuditBaseC3.t.sol b/test/AuditBaseC3.t.sol deleted file mode 100644 index 31f356e..0000000 --- a/test/AuditBaseC3.t.sol +++ /dev/null @@ -1,88 +0,0 @@ -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; - -import "../test/controller.t.sol"; - -// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter -contract Audit is MetaVestControllerTest { - - function testAuditTerminateVestAndRecovers() public { - // template from testTerminateVestAndRecovers - address vestingAllocation = createDummyVestingAllocation(); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: false, - complete: false, - conditionContracts: new address[](0) - }); - vm.prank(authority); - controller.addMetavestMilestone(vestingAllocation, milestones[0]); - VestingAllocation(vestingAllocation).confirmMilestone(1); - - skip(50 seconds); - vm.prank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - assertEq(VestingAllocation(vestingAllocation).getVestedTokenAmount(), 100e18 + 1000e18 + 10e18 * 50, "Unexpected vested amount after termination"); - assertEq(VestingAllocation(vestingAllocation).getUnlockedTokenAmount(), 100e18 + 5e18 * 50 + 5e18 * 50, "Unexpected unlocked amount after termination"); - - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - // assertEq(token.balanceOf(vestingAllocation), 0); - } - -// function test_RevertIf_AuditRounding() public { -// // template from testConfirmingMilestoneTokenOption -// address vestingAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); -// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 50 seconds); -// vm.startPrank(grantee); -// //exercise max available -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// -// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); -// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e6)); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e11)); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(9.99e11)); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e12)); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1.1e12)); -// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e13)); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1.1e12); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); -// -// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); -// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); -// } -// -// function testAuditExcercisePrice() public { -// // template from testConfirmingMilestoneTokenOption -// address vestingAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); -// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 50 seconds); -// vm.startPrank(grantee); -// //exercise max available -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// -// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); -// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -// -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e18); -// -// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); -// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); -// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); -// } -} diff --git a/test/amendement.t.sol b/test/amendement.t.sol deleted file mode 100644 index 2edf871..0000000 --- a/test/amendement.t.sol +++ /dev/null @@ -1,917 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; -import "../src/BaseAllocation.sol"; -import "../src/RestrictedTokenAllocation.sol"; -import "../src/interfaces/IAllocationFactory.sol"; -import "../src/VestingAllocationFactory.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "../src/interfaces/zk-governance/IZkTokenV1.sol"; -import "./lib/MetaVesTControllerTestBase.sol"; - -// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter -contract MetaVestControllerTest is MetaVesTControllerTestBase { - address public authority = guardianSafe; - address public dao = guardianSafe; - address public grantee = alice; - - bytes32 salt = keccak256("MetaVestControllerTest"); - uint256 cap = 2000 ether; - uint48 cappedMinterStartTime = uint48(block.timestamp); // Minter start now - uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 1600); // Minter expired 1600 seconds after start - - address public vestingAllocation; - - function setUp() public { - vm.startPrank(deployer); - - // Deploy MetaVesT controller - - vestingAllocationFactory = new VestingAllocationFactory(); - - controller = new metavestController{salt: salt}( - guardianSafe, - guardianSafe, - address(vestingAllocationFactory) - ); - - // Deploy ZK Capped Minter v2 - - zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( - address(zkToken), - address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT - cap, - cappedMinterStartTime, - cappedMinterExpirationTime, - uint256(salt) - )); - - vm.stopPrank(); - - vm.startPrank(guardianSafe); - controller.setZkCappedMinter(address(zkCappedMinter)); - controller.createSet("testSet"); - vm.stopPrank(); - - vestingAllocation = createDummyVestingAllocation(); - } - - function testProposeMetavestAmendment() public { - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); - - vm.prank(authority); - controller.proposeMetavestAmendment(address(vestingAllocation), msgSig, callData); - - (bool isPending, bytes32 dataHash, bool inFavor) = controller.functionToGranteeToAmendmentPending(msgSig, address(vestingAllocation)); - - assertTrue(isPending); - assertEq(dataHash, keccak256(callData)); - assertFalse(inFavor); - } - - function test_RevertIf_ProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - address mockAllocation4 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - //log the current withdrawable - console.log(VestingAllocation(mockAllocation2).getAmountWithdrawable()); - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.updateMetavestTransferability(mockAllocation2, true); - } - - function testQuickProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - address mockAllocation4 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.warp(block.timestamp + 15 seconds); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.startPrank(grantee); - //log the current withdrawable - console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); - - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - vm.stopPrank(); - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - } - - -// function testMajorityPowerMetavestAmendment() public { -// address mockAllocation2 = createDummyTokenOptionAllocation(); -// address mockAllocation3 = createDummyTokenOptionAllocation(); -// address mockAllocation4 = createDummyTokenOptionAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// controller.addMetaVestToSet("testSet", mockAllocation4); -// vm.warp(block.timestamp + 1 days); -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); -// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); -// vm.stopPrank(); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true); -// } - -// function test_RevertIf_MajorityPowerMetavestAmendment() public { -// address mockAllocation2 = createDummyTokenOptionAllocation(); -// address mockAllocation3 = createDummyTokenOptionAllocation(); -// address mockAllocation4 = createDummyTokenOptionAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", mockAllocation2); -// controller.addMetaVestToSet("testSet", mockAllocation3); -// controller.addMetaVestToSet("testSet", mockAllocation4); -// vm.warp(block.timestamp + 1 days); -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); -// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); -// vm.stopPrank(); -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true); -// vm.prank(authority); -// controller.updateMetavestTransferability(mockAllocation2, true); -// } - - function testProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation3, true); - } - - function testProposeMajorityMetavestAmendmentReAdd() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation3, true); - - vm.prank(authority); - controller.removeMetaVestFromSet("testSet", mockAllocation3); - // vm.prank(authority); - // controller.updateMetavestTransferability(mockAllocation3, true); - vm.warp(block.timestamp + 90 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation3); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation3, true); - } - - function test_RevertIf_NoPassProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - address mockAllocation3 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation3); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.updateMetavestTransferability(mockAllocation2, true); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.updateMetavestTransferability(mockAllocation3, true); - } - - function testVoteOnMetavestAmendment() public { - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(vestingAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - - } - - function test_RevertIf_VoteOnMetavestAmendmentTwice() public { - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(vestingAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.startPrank(grantee); - controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AlreadyVoted.selector)); - controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); - vm.stopPrank(); - } - - function testSetManagement() public { - vm.startPrank(authority); - - // Test creating a new set - controller.createSet("newSet"); - - // Test adding a MetaVest to a set - controller.addMetaVestToSet("newSet", address(vestingAllocation)); - - - // Test removing a MetaVest from a set - controller.removeMetaVestFromSet("newSet", address(vestingAllocation)); - - - // Test removing a set - controller.removeSet("newSet"); - - - vm.stopPrank(); - } - - function test_RevertIf_CreateDuplicateSet() public { - vm.startPrank(authority); - controller.createSet("duplicateSet"); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetAlreadyExists.selector)); - controller.createSet("duplicateSet"); - vm.stopPrank(); - } - - function test_RevertIf_NonAuthorityCreateSet() public { - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); - controller.createSet("unauthorizedSet"); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocation() internal returns (address) { - return createDummyVestingAllocation(""); // Expect no reverts - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocation(bytes memory expectRevertData) internal returns (address) { - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100 ether, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( - guardianSafe, - alice, // = grantee - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000 ether, - vestingCliffCredit: 100 ether, - unlockingCliffCredit: 100 ether, - vestingRate: 10 ether, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10 ether, - unlockStartTime: uint48(block.timestamp) - }), - milestones - ); - - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - if (expectRevertData.length > 0) { - vm.expectRevert(expectRevertData); - } - return controller.createMetavest(contractIdAlice); - } - -// function createDummyTokenOptionAllocation() internal returns (address) { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 100e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); -// -// token.approve(address(controller), 1100e18); -// -// return controller.createMetavest( -// metavestController.metavestType.TokenOption, -// grantee, -// allocation, -// milestones, -// 1e18, -// address(paymentToken), -// 365 days, -// 0 -// ); -// } -// -// function createDummyRestrictedTokenAward() internal returns (address) { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 100e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); -// -// token.approve(address(controller), 1100e18); -// -// return controller.createMetavest( -// metavestController.metavestType.RestrictedTokenAward, -// grantee, -// allocation, -// milestones, -// 1e18, -// address(paymentToken), -// 365 days, -// 0 -// -// ); -// } - - //write a test for every consentcheck function in metavest controller - function testConsentCheck() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - } - - function test_RevertIf_ConsentCheck() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.updateMetavestTransferability(allocation, true); - } - - function test_RevertIf_ConsentCheckNoProposal() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); - } - - function test_RevertIf_ConsentCheckNoVote() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.updateMetavestTransferability(allocation, true); - } - - function test_RevertIf_ConsentCheckNoUpdate() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); - } - - function test_RevertIf_ConsentCheckNoVoteUpdate() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); - controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); - } - -// function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { -// address allocation1 = createDummyTokenOptionAllocation(); -// address allocation2 = createDummyTokenOptionAllocation(); -// address allocation3 = createDummyTokenOptionAllocation(); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", allocation1); -// controller.addMetaVestToSet("testSet", allocation2); -// controller.addMetaVestToSet("testSet", allocation3); -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); -// vm.warp(block.timestamp + 25 seconds); -// -// -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(allocation1), 2000e18); -// ERC20(paymentToken).approve(address(allocation2), 2000e18); -// -// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); -// -// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); -// vm.stopPrank(); -// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); -// -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); -// -// vm.prank(grantee); -// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); -// -// // Check that the exercise price was updated -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); -// } - -// function test_RevertIf_CreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { -// address allocation1 = createDummyTokenOptionAllocation(); -// address allocation2 = createDummyTokenOptionAllocation(); -// address allocation3 = createDummyTokenOptionAllocation(); -// -// vm.prank(authority); -// controller.addMetaVestToSet("testSet", allocation1); -// controller.addMetaVestToSet("testSet", allocation2); -// controller.addMetaVestToSet("testSet", allocation3); -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); -// vm.warp(block.timestamp + 25 seconds); -// -// -// vm.startPrank(grantee); -// ERC20(paymentToken).approve(address(allocation1), 2000e18); -// ERC20(paymentToken).approve(address(allocation2), 2000e18); -// -// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); -// -// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); -// vm.stopPrank(); -// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); -// -// vm.prank(authority); -// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); -// -// //vm.prank(grantee); -// // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); -// -// // vm.prank(grantee); -// // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); -// -// // Check that the exercise price was updated -// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); -// } - - function test_RevertIf_consentToNoPendingAmendment() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(grantee); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_NoPendingAmendment.selector, msgSig, allocation)); - controller.consentToMetavestAmendment(allocation, msgSig, true); - } - -// function testEveryUpdateAmendmentFunction() public { -// address allocation = createDummyTokenOptionAllocation(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(allocation, true); -// -// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); -// -// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 0); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.removeMetavestMilestone(allocation, 0); -// -// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestUnlockRate(allocation, 20e18); -// -// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestVestingRate(allocation, 20e18); -// -// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); -// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); -// } - -// function testEveryUpdateAmendmentFunctionRestricted() public { -// address allocation = createDummyRestrictedTokenAward(); -// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestTransferability(allocation, true); -// -// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); -// -// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 0); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.removeMetavestMilestone(allocation, 0); -// -// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestUnlockRate(allocation, 20e18); -// -// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestVestingRate(allocation, 20e18); -// -// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); -// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(allocation, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(allocation, msgSig, true); -// -// vm.prank(authority); -// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); -// } - - function testEveryUpdateAmendmentFunctionVesting() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - - msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.removeMetavestMilestone(allocation, 0); - - msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(allocation, 20e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(allocation, 20e18); - - msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); - callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); - } - - function test_RevertIf_EveryUpdateAmendmentFunctionVesting() public { - address allocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - - msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.removeMetavestMilestone(allocation, 0); - - msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(allocation, 20e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(allocation, 20e18); - - msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); - callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(authority); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); - controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); - } -} diff --git a/test/controller.t.sol b/test/controller.t.sol deleted file mode 100644 index bbcc8e6..0000000 --- a/test/controller.t.sol +++ /dev/null @@ -1,1470 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.20; - -import "../src/RestrictedTokenAllocation.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "../src/TokenOptionAllocation.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/VestingAllocation.sol"; -import "../src/VestingAllocationFactory.sol"; -import "../src/interfaces/IAllocationFactory.sol"; -import "../src/interfaces/zk-governance/IZkTokenV1.sol"; -import "./lib/MetaVesTControllerTestBase.sol"; -import "./mocks/MockCondition.sol"; - -contract MetaVestControllerTest is MetaVesTControllerTestBase { - address authority = guardianSafe; - address dao = guardianSafe; - address grantee = alice; - address transferee = address(0x101); - - // Parameters - bytes32 salt = keccak256("MetaVesTControllerTest"); - uint256 cap = 2000 ether; - uint48 cappedMinterStartTime = uint48(block.timestamp); // Minter start now - uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 1600); // Minter expired 1600 seconds after start - - function setUp() public { - vm.startPrank(deployer); - - // Deploy MetaVesT controller - - vestingAllocationFactory = new VestingAllocationFactory(); - - controller = new metavestController{salt: salt}( - guardianSafe, - guardianSafe, - address(vestingAllocationFactory) - ); - - // Deploy ZK Capped Minter v2 - - zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( - address(zkToken), - address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT - cap, - cappedMinterStartTime, - cappedMinterExpirationTime, - uint256(salt) - )); - - vm.stopPrank(); - - vm.startPrank(guardianSafe); - controller.setZkCappedMinter(address(zkCappedMinter)); - controller.createSet("testSet"); - vm.stopPrank(); - } - - function testCreateVestingAllocation() public { - bytes32 contractIdAlice = _signAndCreateContract( - guardianSafe, - alice, - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: 60 ether, - vestingCliffCredit: 30 ether, - unlockingCliffCredit: 30 ether, - vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0) - ); - - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - // Anyone can create MetaVesT (per agreements) to start vesting - VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); - - // Grantees should be able to withdraw all remaining tokens after sufficient time passed - skip(61); - _granteeWithdrawAndAsserts(vestingAllocationAlice, 60 ether, "Alice full"); - } - -// function testCreateTokenOptionAllocation() public { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 100e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); -// -// address tokenOptionAllocation = controller.createMetavest( -// metavestController.metavestType.TokenOption, -// grantee, -// allocation, -// milestones, -// 1e18, -// address(paymentToken), -// 365 days, -// 0 -// ); -// -// assertEq(zkToken.balanceOf(address(tokenOptionAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); -// //assertEq(controller.tokenOptionAllocations(grantee, 0), tokenOptionAllocation); -// } - -// function testCreateRestrictedTokenAward() public { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 100e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); -// -// address restrictedTokenAward = controller.createMetavest( -// metavestController.metavestType.RestrictedTokenAward, -// grantee, -// allocation, -// milestones, -// 1e18, -// address(paymentToken), -// 365 days, -// 0 -// -// ); -// -// assertEq(zkToken.balanceOf(address(restrictedTokenAward)), 0, "Vesting contract should not have any token (it mints on-demand)"); -// //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); -// } - - function testUpdateTransferability() public { - uint256 startTimestamp = block.timestamp; - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - //compute msg.data for updateMetavestTransferability(vestingAllocation, true) - bytes4 selector = controller.updateMetavestTransferability.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, true); - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, true); - vm.prank(authority); - controller.updateMetavestTransferability(vestingAllocation, true); - vm.prank(grantee); - VestingAllocation(vestingAllocation).transferRights(transferee); - vm.prank(transferee); - VestingAllocation(vestingAllocation).confirmTransfer(); - uint256 newTimestamp = startTimestamp + 100; // 101 - vm.warp(newTimestamp); - skip(10); - vm.prank(transferee); - uint256 balance = VestingAllocation(vestingAllocation).getAmountWithdrawable(); - - - //warp ahead 100 blocks - - vm.prank(transferee); - VestingAllocation(vestingAllocation).withdraw(balance); - - // assertTrue(BaseAllocation(vestingAllocation).transferable()); - } - - function testGetGovPower() public { - address vestingAllocation = createDummyVestingAllocation(); - BaseAllocation(vestingAllocation).getGoverningPower(); - } - - function testProposeMajorityMetavestAmendment() public { - address vestingAllocation = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", vestingAllocation); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(vestingAllocation, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(vestingAllocation, true); - } - - - function test_RevertIf_ReProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - vm.warp(block.timestamp + 30 days); - /* - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true);*/ - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - } - - function testReProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - vm.warp(block.timestamp + 1 days); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - vm.warp(block.timestamp + 30 days); - - vm.prank(authority); - controller.cancelExpiredMajorityMetavestAmendment("testSet", msgSig); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - } - - function test_RevertIf_RemoveNonExistantMetaVestFromSet() public { - address mockAllocation2 = createDummyVestingAllocation(); - vm.startPrank(authority); - // controller.createSet("testSet"); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_MetaVestNotInSet.selector)); - controller.removeMetaVestFromSet("testSet", mockAllocation2); - } - - -// function testUpdateExercisePrice() public { -// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -// -// //compute msg.data for updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18) -// bytes4 selector = controller.updateExerciseOrRepurchasePrice.selector; -// bytes memory msgData = abi.encodeWithSelector(selector, tokenOptionAllocation, 2e18); -// -// controller.proposeMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, msgData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, true); -// -// controller.updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18); -// -// assertEq(TokenOptionAllocation(tokenOptionAllocation).exercisePrice(), 2e18); -// } - - function testRemoveMilestone() public { - address vestingAllocation = createDummyVestingAllocation(); - //create array of addresses and include vestingAllocation address - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - vm.prank(grantee); - //consent to amendment for the removemetavestmilestone method sig function consentToMetavestAmendment(address _metavest, bytes4 _msgSig, bool _inFavor) external { - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - vm.prank(authority); - controller.removeMetavestMilestone(vestingAllocation, 0); - - //BaseAllocation.Milestone memory milestone = BaseAllocation(vestingAllocation).milestones(0); - //assertEq(milestone.milestoneAward, 0); - } - - function testAddMilestone() public { - address vestingAllocation = createDummyVestingAllocation(); - - BaseAllocation.Milestone memory newMilestone = BaseAllocation.Milestone({ - milestoneAward: 50e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - vm.prank(authority); - controller.addMetavestMilestone(vestingAllocation, newMilestone); - - // BaseAllocation.Milestone memory addedMilestone = BaseAllocation(vestingAllocation).milestones[0]; - // assertEq(addedMilestone.milestoneAward, 50e18); - } - - function testUpdateUnlockRate() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - vm.prank(authority); - controller.updateMetavestUnlockRate(vestingAllocation, 20e18); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 20e18); - } - - function testUpdateUnlockRateZeroEmergency() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - vm.prank(authority); - controller.updateMetavestUnlockRate(vestingAllocation, 0); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 0); - vm.prank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.prank(authority); - controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 1e20); - } - - function test_RevertIf_UpdateUnlockRateZeroEmergency() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - vm.prank(authority); - controller.terminateMetavestVesting(vestingAllocation); - - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); - vm.prank(authority); - controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 10e18); - } - - function test_RevertIf_UpdateUnlockRateZeroEmergencyTerminated() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestUnlockRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - vm.prank(authority); - controller.updateMetavestUnlockRate(vestingAllocation, 0); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 0); - - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); - vm.prank(authority); - controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 0); - } - - function testUpdateVestingRate() public { - address vestingAllocation = createDummyVestingAllocation(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = controller.updateMetavestVestingRate.selector; - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, true); - vm.prank(authority); - controller.updateMetavestVestingRate(vestingAllocation, 20e18); - - BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.vestingRate, 20e18); - } - -// function testUpdateStopTimes() public { -// -// address vestingAllocation = createDummyRestrictedTokenAward(); -// address[] memory addresses = new address[](1); -// addresses[0] = vestingAllocation; -// bytes4 selector = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); -// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, uint48(block.timestamp + 500 days)); -// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, msgData); -// vm.prank(grantee); -// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, true); -// uint48 newShortStopTime = uint48(block.timestamp + 500 days); -// -// controller.updateMetavestStopTimes(vestingAllocation, newShortStopTime); -// } - - function testTerminateVesting() public { - address vestingAllocation = createDummyVestingAllocation(); - vm.prank(authority); - controller.terminateMetavestVesting(vestingAllocation); - - assertTrue(BaseAllocation(vestingAllocation).terminated()); - } - -// function testRepurchaseTokens() public { -// uint256 startingBalance = paymentToken.balanceOf(grantee); -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// uint256 repurchaseAmount = 5e18; -// uint256 snapshot = token.balanceOf(authority); -// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); -// controller.terminateMetavestVesting(restrictedTokenAward); -// paymentToken.approve(address(restrictedTokenAward), payment); -// vm.warp(block.timestamp + 20 days); -// vm.prank(authority); -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); -// -// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); -// -// vm.prank(grantee); -// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); -// } - -// function testRepurchaseTokensFuture() public { -// uint256 startingBalance = paymentToken.balanceOf(grantee); -// address restrictedTokenAward = createDummyRestrictedTokenAwardFuture(); -// -// uint256 snapshot = token.balanceOf(authority); -// -// controller.terminateMetavestVesting(restrictedTokenAward); -// uint256 repurchaseAmount = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); -// paymentToken.approve(address(restrictedTokenAward), payment); -// vm.warp(block.timestamp + 20 days); -// vm.prank(authority); -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); -// -// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); -// -// vm.prank(grantee); -// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -// console.log(token.balanceOf(restrictedTokenAward)); -// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); -// -// } - - function testTerminateTokensFuture() public { - address vestingAllocation = createDummyVestingAllocationLargeFuture(); - vm.prank(authority); - controller.terminateMetavestVesting(vestingAllocation); - } - - function testUpdateAuthority() public { - address newAuthority = address(0x4); - vm.prank(authority); - controller.initiateAuthorityUpdate(newAuthority); - - vm.prank(newAuthority); - controller.acceptAuthorityRole(); - - assertEq(controller.authority(), newAuthority); - } - - function testUpdateDao() public { - address newDao = address(0x5); - - vm.prank(dao); - controller.initiateDaoUpdate(newDao); - - vm.prank(newDao); - controller.acceptDaoRole(); - - assertEq(controller.dao(), newDao); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocation() internal returns (address) { - return createDummyVestingAllocation(""); // Expect no reverts - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocation(bytes memory expectRevertData) internal returns (address) { - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000 ether, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( - guardianSafe, - alice, // = grantee - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000 ether, - vestingCliffCredit: 100 ether, - unlockingCliffCredit: 100 ether, - vestingRate: 10 ether, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10 ether, - unlockStartTime: uint48(block.timestamp) - }), - milestones - ); - - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - if (expectRevertData.length > 0) { - vm.expectRevert(expectRevertData); - } - return controller.createMetavest(contractIdAlice); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationNoUnlock() internal returns (address) { - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000 ether, - unlockOnCompletion: false, - complete: false, - conditionContracts: new address[](0) - }); - - // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( - guardianSafe, - alice, // = grantee - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000 ether, - vestingCliffCredit: 100 ether, - unlockingCliffCredit: 100 ether, - vestingRate: 10 ether, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10 ether, - unlockStartTime: uint48(block.timestamp) - }), - milestones - ); - - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - return controller.createMetavest(contractIdAlice); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationSlowUnlock() internal returns (address) { - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000 ether, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( - guardianSafe, - alice, // = grantee - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000 ether, - vestingCliffCredit: 100 ether, - unlockingCliffCredit: 100 ether, - vestingRate: 10 ether, - vestingStartTime: uint48(block.timestamp), - unlockRate: 5 ether, - unlockStartTime: uint48(block.timestamp) - }), - milestones - ); - - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - return controller.createMetavest(contractIdAlice); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationLarge() internal returns (address) { - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( - guardianSafe, - alice, // = grantee - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000 ether, - vestingCliffCredit: 0 ether, - unlockingCliffCredit: 0 ether, - vestingRate: 10 ether, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10 ether, - unlockStartTime: uint48(block.timestamp) - }), - milestones - ); - - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - return controller.createMetavest(contractIdAlice); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationLargeFuture() internal returns (address) { - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( - guardianSafe, - alice, // = grantee - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000 ether, - vestingCliffCredit: 0 ether, - unlockingCliffCredit: 0 ether, - vestingRate: 10 ether, - vestingStartTime: uint48(block.timestamp + 2000), - unlockRate: 10 ether, - unlockStartTime: uint48(block.timestamp + 2000) - }), - milestones - ); - - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - return controller.createMetavest(contractIdAlice); - } - -// function createDummyTokenOptionAllocation() internal returns (address) { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 1000e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); -// -// token.approve(address(controller), 2000e18); -// -// return controller.createMetavest( -// metavestController.metavestType.TokenOption, -// grantee, -// allocation, -// milestones, -// 5e17, -// address(paymentToken), -// 1 days, -// 0 -// ); -// } - - -// function createDummyRestrictedTokenAward() internal returns (address) { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 1000e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); -// -// token.approve(address(controller), 2100e18); -// -// return controller.createMetavest( -// metavestController.metavestType.RestrictedTokenAward, -// grantee, -// allocation, -// milestones, -// 1e18, -// address(paymentToken), -// 1 days, -// 0 -// -// ); -// } -// -// function createDummyRestrictedTokenAwardFuture() internal returns (address) { -// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(token), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: uint48(block.timestamp+1000), -// unlockRate: 10e18, -// unlockStartTime: uint48(block.timestamp+1000) -// }); -// -// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); -// milestones[0] = BaseAllocation.Milestone({ -// milestoneAward: 1000e18, -// unlockOnCompletion: true, -// complete: false, -// conditionContracts: new address[](0) -// }); -// -// token.approve(address(controller), 2100e18); -// -// return controller.createMetavest( -// metavestController.metavestType.RestrictedTokenAward, -// grantee, -// allocation, -// milestones, -// 1e18, -// address(paymentToken), -// 1 days, -// 0 -// -// ); -// } - - - function testGetMetaVestType() public { - address vestingAllocation = createDummyVestingAllocation(); -// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -// address restrictedTokenAward = createDummyRestrictedTokenAward(); - - assertEq(controller.getMetaVestType(vestingAllocation), 1); -// assertEq(controller.getMetaVestType(tokenOptionAllocation), 2); -// assertEq(controller.getMetaVestType(restrictedTokenAward), 3); - } - -// function testWithdrawFromController() public { -// uint256 amount = 100e18; -// token.transfer(address(controller), amount); -// -// uint256 initialBalance = token.balanceOf(authority); -// controller.withdrawFromController(address(token)); -// uint256 finalBalance = token.balanceOf(authority); -// -// assertEq(finalBalance - initialBalance, amount); -// assertEq(token.balanceOf(address(controller)), 0); -// } - - function test_RevertIf_CreateMetavestWithZeroAddress() public { - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( - guardianSafe, - alice, // = grantee - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(0), - tokenStreamTotal: 1000 ether, - vestingCliffCredit: 100 ether, - unlockingCliffCredit: 100 ether, - vestingRate: 10 ether, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10 ether, - unlockStartTime: uint48(block.timestamp) - }), - milestones - ); - - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ZeroAddress.selector)); - controller.createMetavest(contractIdAlice); - } - - function testTerminateVestAndRecovers() public { - address vestingAllocation = createDummyVestingAllocation(); - uint256 snapshot = zkToken.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.prank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(zkToken.balanceOf(authority), 0); - } - - function testTerminateVestAndRecoverSlowUnlock() public { - address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = zkToken.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 25 seconds); - vm.prank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 25 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(zkToken.balanceOf(vestingAllocation), 0); - } - - function testTerminateRecoverAll() public { - address vestingAllocation = createDummyVestingAllocationLarge(); - uint256 snapshot = zkToken.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - vm.prank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(zkToken.balanceOf(authority), 0); - } - - function testTerminateRecoverChunksBefore() public { - address vestingAllocation = createDummyVestingAllocationLarge(); - uint256 snapshot = zkToken.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - vm.warp(block.timestamp + 25 seconds); - vm.prank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - assertEq(zkToken.balanceOf(authority), 0); - } - -// function testConfirmingMilestoneRestrictedTokenAllocation() public { -// address vestingAllocation = createDummyRestrictedTokenAward(); -// uint256 snapshot = token.balanceOf(authority); -// RestrictedTokenAward(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 50 seconds); -// vm.startPrank(grantee); -// RestrictedTokenAward(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); -// } -// -// function testConfirmingMilestoneTokenOption() public { -// address vestingAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); -// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); -// vm.warp(block.timestamp + 50 seconds); -// vm.startPrank(grantee); -// //exercise max available -// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); -// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); -// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); -// vm.stopPrank(); -// } - - function testUnlockMilestoneNotUnlocked() public { - address vestingAllocation = createDummyVestingAllocationNoUnlock(); - uint256 snapshot = zkToken.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 1050 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - -// function testTerminateTokenOptionAndRecover() public { -// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); -// vm.warp(block.timestamp + 25 seconds); -// vm.prank(grantee); -// ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); -// vm.prank(grantee); -// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18); -// controller.terminateMetavestVesting(tokenOptionAllocation); -// vm.startPrank(grantee); -// vm.warp(block.timestamp + 1 days + 25 seconds); -// assertEq(TokenOptionAllocation(tokenOptionAllocation).getAmountExercisable(), 0); -// TokenOptionAllocation(tokenOptionAllocation).withdraw(TokenOptionAllocation(tokenOptionAllocation).getAmountWithdrawable()); -// vm.stopPrank(); -// assertEq(token.balanceOf(tokenOptionAllocation), 0); -// vm.warp(block.timestamp + 365 days); -// vm.prank(authority); -// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); -// } - -// function testTerminateEarlyTokenOptionAndRecover() public { -// address tokenOptionAllocation = createDummyTokenOptionAllocation(); -// uint256 snapshot = token.balanceOf(authority); -// vm.warp(block.timestamp + 5 seconds); -// // vm.prank(grantee); -// /* ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); -// vm.prank(grantee); -// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18);*/ -// controller.terminateMetavestVesting(tokenOptionAllocation); -// vm.warp(block.timestamp + 365 days); -// vm.prank(authority); -// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); -// } - - -// function testTerminateRestrictedTokenAwardAndRecover() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// uint256 snapshot = token.balanceOf(authority); -// vm.warp(block.timestamp + 25 seconds); -// controller.terminateMetavestVesting(restrictedTokenAward); -// vm.startPrank(grantee); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -// vm.stopPrank(); -// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); -// vm.warp(block.timestamp + 20 days); -// paymentToken.approve(address(restrictedTokenAward), payamt); -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); -// -// vm.startPrank(grantee); -// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -// assertEq(token.balanceOf(restrictedTokenAward), 0); -// assertEq(paymentToken.balanceOf(restrictedTokenAward), 0); -// } - -// function testChangeVestingAndUnlockingRate() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// uint256 snapshot = token.balanceOf(authority); -// vm.warp(block.timestamp + 25 seconds); -// -// bytes4 msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestUnlockRate(restrictedTokenAward, 50e18); -// -// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestVestingRate(restrictedTokenAward, 50e18); -// -// vm.startPrank(grantee); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -// vm.stopPrank(); -// -// } - -// function testZeroReclaim() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// vm.warp(block.timestamp + 15 seconds); -// vm.startPrank(grantee); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -// vm.stopPrank(); -// //create call data to propose setting vesting to 0 -// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestVestingRate(restrictedTokenAward, 0); -// -// vm.startPrank(authority); -// controller.terminateMetavestVesting(restrictedTokenAward); -// vm.warp(block.timestamp + 155 days); -// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); -// paymentToken.approve(address(restrictedTokenAward), payamt); -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); -// vm.stopPrank(); -// vm.prank(grantee); -// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -// console.log(token.balanceOf(restrictedTokenAward)); -// } - - function testZeroReclaimVesting() public { - address vestingAllocation = createDummyVestingAllocation(); - vm.warp(block.timestamp + 15 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(vestingAllocation, 0); - - vm.startPrank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.stopPrank(); - } - - function testSlightReduc() public { - address vestingAllocation = createDummyVestingAllocation(); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 80e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(vestingAllocation, 80e18); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.stopPrank(); - vm.warp(block.timestamp + 155 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - - function testLargeReduc() public { - address vestingAllocation = createDummyVestingAllocation(); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 10e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(vestingAllocation, 10e18); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(authority); - controller.terminateMetavestVesting(vestingAllocation); - vm.stopPrank(); - vm.warp(block.timestamp + 155 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - -// function testLargeReducOption() public { -// address restrictedTokenAward = createDummyTokenOptionAllocation(); -// vm.warp(block.timestamp + 5 seconds); -// vm.startPrank(grantee); -// //approve amount to exercise by getting amount to exercise and price -// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); -// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -// vm.stopPrank(); -// //create call data to propose setting vesting to 0 -// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); -// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); -// -// vm.prank(authority); -// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); -// -// vm.prank(grantee); -// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); -// -// vm.prank(authority); -// controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); -// vm.warp(block.timestamp + 5 seconds); -// vm.startPrank(authority); -// controller.terminateMetavestVesting(restrictedTokenAward); -// vm.stopPrank(); -// vm.warp(block.timestamp + 155 seconds); -// vm.startPrank(grantee); -// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); -// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -// vm.stopPrank(); -// console.log(token.balanceOf(restrictedTokenAward)); -// } - - - -// function testReclaim() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// vm.warp(block.timestamp + 15 seconds); -// vm.startPrank(grantee); -// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); -// vm.stopPrank(); -// -// vm.startPrank(authority); -// controller.terminateMetavestVesting(restrictedTokenAward); -// vm.warp(block.timestamp + 155 days); -// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); -// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); -// paymentToken.approve(address(restrictedTokenAward), payamt); -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); -// vm.stopPrank(); -// vm.prank(grantee); -// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); -// console.log(token.balanceOf(restrictedTokenAward)); -// } - - - -// function test_RevertIf_UpdateExercisePriceForVesting() public { -// address vestingAllocation = createDummyVestingAllocation(); -// controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); -// } - -// function test_RevertIf_RepurchaseTokensAfterExpiry() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// -// // Fast forward time to after the short stop date -// vm.warp(block.timestamp + 366 days); -// -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); -// } - -// function test_RevertIf_RepurchaseTokensInsufficientAllowance() public { -// address restrictedTokenAward = createDummyRestrictedTokenAward(); -// -// // Not approving any tokens -// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); -// } - - function test_RevertIf_InitiateAuthorityUpdateNonAuthority() public { - vm.prank(address(0x1234)); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); - controller.initiateAuthorityUpdate(address(0x5678)); - } - - function test_RevertIf_AcceptAuthorityRoleNonPendingAuthority() public { - vm.prank(authority); - controller.initiateAuthorityUpdate(address(0x5678)); - - vm.prank(address(0x1234)); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingAuthority.selector)); - controller.acceptAuthorityRole(); - } - - function test_RevertIf_InitiateDaoUpdateNonDao() public { - vm.prank(address(0x1234)); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); - controller.initiateDaoUpdate(address(0x5678)); - } - - function test_RevertIf_AcceptDaoRoleNonPendingDao() public { - vm.prank(dao); - controller.initiateDaoUpdate(address(0x5678)); - - vm.prank(address(0x1234)); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingDao.selector)); - controller.acceptDaoRole(); - } - - function testUpdateFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("testFunction()")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - - assertEq(controller.functionToConditions(functionSig, 0), address(condition)); - } - - function test_RevertIf_UpdateFunctionConditionNonDao() public { - bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); - address condition = address(0x1234); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); - controller.updateFunctionCondition(condition, functionSig); - } - - - function testRemoveFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - assert(controller.functionToConditions(functionSig, 0) == address(condition)); - vm.prank(dao); - controller.removeFunctionCondition(address(condition), functionSig); - } - - function test_RevertIf_CheckFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("createMetavest(bytes32)")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - assert(controller.functionToConditions(functionSig, 0) == address(condition)); - // create a dummy metavest - createDummyVestingAllocation( - abi.encodeWithSelector(metavestController.MetaVesTController_ConditionNotSatisfied.selector, condition) // Expected revert - ); - } - - function test_RevertIf_AddDuplicateCondition() public { - bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); - /* constructor( - address[] memory _signers, - uint256 _threshold, - Logic _logic - ) */ - address[] memory signers = new address[](2); - signers[0] = address(0x1); - signers[1] = address(0x2); - SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - - vm.prank(dao); - controller.updateFunctionCondition(address(condition), functionSig); - assert(controller.functionToConditions(functionSig, 0) == address(condition)); - vm.prank(dao); - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_DuplicateCondition.selector)); - controller.updateFunctionCondition(address(condition), functionSig); - } - - function test_RevertIf_ExceedCap() public { - // Add a large grant that exceeds the cap - bytes32 contractIdChad = _signAndCreateContract( - guardianSafe, - chad, - chadPrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 2001 ether, - vestingCliffCredit: 2001 ether, - unlockingCliffCredit: 2001 ether, - vestingRate: 0, - vestingStartTime: 0, - unlockRate: 0, - unlockStartTime: 0 - }), - new BaseAllocation.Milestone[](0) - ); - VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); - - vm.prank(chad); - vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(vestingAllocationChad), 2001 ether)); - vestingAllocationChad.withdraw(2001 ether); - } - - function test_RevertIf_NotAuthority() public { - // Non Guardian SAFE should not be able to accept agreement and create contract - _signAndCreateContract( - deployer, // Not authority - alice, - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 100 ether, - vestingCliffCredit: 10 ether, - unlockingCliffCredit: 10 ether, - vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert - ); - } - - function test_RevertIf_IncorrectAgreementSignature() public { - // Register Alice with someone else's signature should fail - _signAndCreateContract( - guardianSafe, - alice, - bobPrivateKey, // Use someone else to sign - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 100 ether, - vestingCliffCredit: 10 ether, - unlockingCliffCredit: 10 ether, - vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert - ); - } - - function test_DelegateSignature() public { - // Alice to delegate to Bob - vm.prank(alice); - controller.setDelegation(bob, block.timestamp + 60); - assertTrue(controller.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); - - // Bob should be able to sign for Alice now - bytes32 contractId = _signAndCreateContract( - guardianSafe, - alice, - bobPrivateKey, // Use Bob to sign - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 100 ether, - vestingCliffCredit: 10 ether, - unlockingCliffCredit: 10 ether, - vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0) - ); - metavestController.AgreementData memory agreement = controller.getAgreement(contractId); - assertEq(agreement.signedData.grantee, alice, "Alice should be the grantee"); - - // Wait until expiry - skip(61); - - // Bob should no longer be able to sign for Alice - assertFalse(controller.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); - _signAndCreateContract( - guardianSafe, - alice, - bobPrivateKey, // Use Bob to sign - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 100 ether, - vestingCliffCredit: 10 ether, - unlockingCliffCredit: 10 ether, - vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert - ); - } -} From 50913414fe296f704eea31fb7be71b439738c41a Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 6 Aug 2025 12:05:11 -0700 Subject: [PATCH 18/68] chore: add dependencies to CyberAgreementRegistry --- .gitmodules | 3 +++ lib/cybercorps-contracts | 1 + remappings.txt | 3 ++- src/BaseAllocation.sol | 2 +- src/MetaVesTController.sol | 2 +- src/RestrictedTokenAllocation.sol | 2 +- src/RestrictedTokenFactory.sol | 2 +- src/TokenOptionAllocation.sol | 2 +- src/TokenOptionFactory.sol | 2 +- src/VestingAllocation.sol | 2 +- src/VestingAllocationFactory.sol | 2 +- src/interfaces/IAllocationFactory.sol | 2 +- src/interfaces/IBaseAllocation.sol | 2 +- src/interfaces/IPriceAllocation.sol | 2 +- src/interfaces/IRestrictedTokenAward.sol | 2 +- src/interfaces/zk-governance/IMintable.sol | 2 +- .../zk-governance/IMintableAndDelegatable.sol | 2 +- src/interfaces/zk-governance/IZkCappedMinterV2.sol | 2 +- .../zk-governance/IZkCappedMinterV2Factory.sol | 2 +- src/interfaces/zk-governance/IZkTokenV1.sol | 2 +- test/ZkGuardianCompensation.t.sol | 13 +++++++++++++ test/lib/MetaVesTControllerTestBase.sol | 7 ++++++- test/lib/MetaVesTUtils.sol | 2 +- test/mocks/MockCondition.sol | 2 +- 24 files changed, 44 insertions(+), 21 deletions(-) create mode 160000 lib/cybercorps-contracts diff --git a/.gitmodules b/.gitmodules index 6022e5c..9e651f1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/zk-governance"] path = lib/zk-governance url = https://github.com/zksync-association/zk-governance +[submodule "lib/cybercorps-contracts"] + path = lib/cybercorps-contracts + url = https://github.com/MetaLex-Tech/cybercorps-contracts diff --git a/lib/cybercorps-contracts b/lib/cybercorps-contracts new file mode 160000 index 0000000..e2afc8b --- /dev/null +++ b/lib/cybercorps-contracts @@ -0,0 +1 @@ +Subproject commit e2afc8b1fbecff89f97b8b23d6134508ed00437a diff --git a/remappings.txt b/remappings.txt index 217f5fe..9bce3c7 100644 --- a/remappings.txt +++ b/remappings.txt @@ -8,4 +8,5 @@ openzeppelin-contracts/=lib/zk-governance/l1-contracts/lib/openzeppelin-contract @openzeppelin/contracts-upgradeable=lib/zk-governance/l2-contracts/lib/openzeppelin-contracts-upgradeable/contracts @openzeppelin/contracts/=lib/zk-governance/l2-contracts/lib/openzeppelin-contracts/contracts/ solmate/=lib/zk-governance/l2-contracts/lib/flexible-voting/lib/solmate/src/ -zk-governance/=lib/zk-governance/ \ No newline at end of file +zk-governance/=lib/zk-governance/ +cybercorps-contracts/=lib/cybercorps-contracts/src/ diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index 1764cfa..9d03056 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "./interfaces/zk-governance/IZkCappedMinterV2.sol"; diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 5a762ef..9dbe870 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -6,7 +6,7 @@ ************************************* */ -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "./BaseAllocation.sol"; diff --git a/src/RestrictedTokenAllocation.sol b/src/RestrictedTokenAllocation.sol index 64fa8bd..ba9e988 100644 --- a/src/RestrictedTokenAllocation.sol +++ b/src/RestrictedTokenAllocation.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import "./BaseAllocation.sol"; -pragma solidity 0.8.24; +pragma solidity ^0.8.24; contract RestrictedTokenAward is BaseAllocation { diff --git a/src/RestrictedTokenFactory.sol b/src/RestrictedTokenFactory.sol index 8db8f87..6497f53 100644 --- a/src/RestrictedTokenFactory.sol +++ b/src/RestrictedTokenFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "./RestrictedTokenAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; diff --git a/src/TokenOptionAllocation.sol b/src/TokenOptionAllocation.sol index 46c37a3..f5c2947 100644 --- a/src/TokenOptionAllocation.sol +++ b/src/TokenOptionAllocation.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import "./BaseAllocation.sol"; -pragma solidity 0.8.24; +pragma solidity ^0.8.24; contract TokenOptionAllocation is BaseAllocation { diff --git a/src/TokenOptionFactory.sol b/src/TokenOptionFactory.sol index a23041d..3237c5d 100644 --- a/src/TokenOptionFactory.sol +++ b/src/TokenOptionFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "./TokenOptionAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; diff --git a/src/VestingAllocation.sol b/src/VestingAllocation.sol index ffffe2a..80feea9 100644 --- a/src/VestingAllocation.sol +++ b/src/VestingAllocation.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import "./BaseAllocation.sol"; -pragma solidity 0.8.24; +pragma solidity ^0.8.24; contract VestingAllocation is BaseAllocation { diff --git a/src/VestingAllocationFactory.sol b/src/VestingAllocationFactory.sol index e0b70de..693064f 100644 --- a/src/VestingAllocationFactory.sol +++ b/src/VestingAllocationFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "./VestingAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; diff --git a/src/interfaces/IAllocationFactory.sol b/src/interfaces/IAllocationFactory.sol index 175c1ed..7f0fd1a 100644 --- a/src/interfaces/IAllocationFactory.sol +++ b/src/interfaces/IAllocationFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "../VestingAllocation.sol"; diff --git a/src/interfaces/IBaseAllocation.sol b/src/interfaces/IBaseAllocation.sol index f8789d2..c12680f 100644 --- a/src/interfaces/IBaseAllocation.sol +++ b/src/interfaces/IBaseAllocation.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; interface IBaseAllocation { function getVestingType() external view returns (uint256); diff --git a/src/interfaces/IPriceAllocation.sol b/src/interfaces/IPriceAllocation.sol index 264c7bf..029ee08 100644 --- a/src/interfaces/IPriceAllocation.sol +++ b/src/interfaces/IPriceAllocation.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; interface IPriceAllocation { function getVestingType() external view returns (uint256); diff --git a/src/interfaces/IRestrictedTokenAward.sol b/src/interfaces/IRestrictedTokenAward.sol index 66e3bc5..6a775bf 100644 --- a/src/interfaces/IRestrictedTokenAward.sol +++ b/src/interfaces/IRestrictedTokenAward.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "./IBaseAllocation.sol"; diff --git a/src/interfaces/zk-governance/IMintable.sol b/src/interfaces/zk-governance/IMintable.sol index 4780d96..a80461f 100644 --- a/src/interfaces/zk-governance/IMintable.sol +++ b/src/interfaces/zk-governance/IMintable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.24; +pragma solidity ^0.8.24; interface IMintable { function mint(address _to, uint256 _amount) external; diff --git a/src/interfaces/zk-governance/IMintableAndDelegatable.sol b/src/interfaces/zk-governance/IMintableAndDelegatable.sol index 9838277..3e72b8c 100644 --- a/src/interfaces/zk-governance/IMintableAndDelegatable.sol +++ b/src/interfaces/zk-governance/IMintableAndDelegatable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import {IMintable} from "./IMintable.sol"; diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2.sol b/src/interfaces/zk-governance/IZkCappedMinterV2.sol index 6113aa4..d8b2e2d 100644 --- a/src/interfaces/zk-governance/IZkCappedMinterV2.sol +++ b/src/interfaces/zk-governance/IZkCappedMinterV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.24; +pragma solidity ^0.8.24; interface IZkCappedMinterV2 { error ZkCappedMinterV2__CapExceeded(address minter, uint256 amount); diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol b/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol index 007c9f7..901e958 100644 --- a/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol +++ b/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.24; +pragma solidity ^0.8.24; interface IZkCappedMinterV2Factory { function createCappedMinter( diff --git a/src/interfaces/zk-governance/IZkTokenV1.sol b/src/interfaces/zk-governance/IZkTokenV1.sol index 6be1a9c..83c03ac 100644 --- a/src/interfaces/zk-governance/IZkTokenV1.sol +++ b/src/interfaces/zk-governance/IZkTokenV1.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import {IERC20} from "forge-std/interfaces/IERC20.sol"; diff --git a/test/ZkGuardianCompensation.t.sol b/test/ZkGuardianCompensation.t.sol index 3b4f21a..1cc0dfb 100644 --- a/test/ZkGuardianCompensation.t.sol +++ b/test/ZkGuardianCompensation.t.sol @@ -7,6 +7,8 @@ import "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; import "./lib/MetaVesTUtils.sol"; import "./lib/MetaVesTControllerTestBase.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; // Test by forge test --zksync --via-ir contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { @@ -22,6 +24,17 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { vm.startPrank(deployer); + // Deploy CyberAgreementRegistry + // TODO who should be the owner of auth? + auth = new BorgAuth{salt: salt}(deployer); + registry = CyberAgreementRegistry(address(new ERC1967Proxy{salt: salt}( + address(new CyberAgreementRegistry{salt: salt}()), + abi.encodeWithSelector( + CyberAgreementRegistry.initialize.selector, + address(auth) + ) + ))); + // Deploy MetaVesT controller vestingAllocationFactory = new VestingAllocationFactory(); diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index d3279c3..2f06f77 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "forge-std/Test.sol"; import "../../src/MetaVesTController.sol"; import "../../src/VestingAllocationFactory.sol"; import "../../src/interfaces/zk-governance/IZkTokenV1.sol"; import "./MetaVesTUtils.sol"; +import {BorgAuth} from "cybercorps-contracts/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/CyberAgreementRegistry.sol"; contract MetaVesTControllerTestBase is Test { // // zkSync Era Sepolia @ 5576300 @@ -29,6 +31,9 @@ contract MetaVesTControllerTestBase is Test { uint256 chadPrivateKey = 3; address chad = vm.addr(chadPrivateKey); + BorgAuth auth; + CyberAgreementRegistry registry; + VestingAllocationFactory vestingAllocationFactory; metavestController controller; diff --git a/test/lib/MetaVesTUtils.sol b/test/lib/MetaVesTUtils.sol index 247c8d5..cd48983 100644 --- a/test/lib/MetaVesTUtils.sol +++ b/test/lib/MetaVesTUtils.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "../../src/MetaVesTController.sol"; import {Vm} from "forge-std/Test.sol"; diff --git a/test/mocks/MockCondition.sol b/test/mocks/MockCondition.sol index cd96bf4..c0111a5 100644 --- a/test/mocks/MockCondition.sol +++ b/test/mocks/MockCondition.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "../../src/BaseAllocation.sol"; From 20875284dd8a6ee6cbcc2af81f300641221cfe52 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 6 Aug 2025 13:57:43 -0700 Subject: [PATCH 19/68] wip: feat: adopt CyberAgreementRegistry --- remappings.txt | 3 +- src/MetaVesTController.sol | 234 ++++++------- test/ZkGuardianCompensation.t.sol | 432 ++++++++++++++---------- test/lib/MetaVesTControllerTestBase.sol | 72 ++-- test/lib/MetaVesTUtils.sol | 83 ----- 5 files changed, 428 insertions(+), 396 deletions(-) delete mode 100644 test/lib/MetaVesTUtils.sol diff --git a/remappings.txt b/remappings.txt index 9bce3c7..217f5fe 100644 --- a/remappings.txt +++ b/remappings.txt @@ -8,5 +8,4 @@ openzeppelin-contracts/=lib/zk-governance/l1-contracts/lib/openzeppelin-contract @openzeppelin/contracts-upgradeable=lib/zk-governance/l2-contracts/lib/openzeppelin-contracts-upgradeable/contracts @openzeppelin/contracts/=lib/zk-governance/l2-contracts/lib/openzeppelin-contracts/contracts/ solmate/=lib/zk-governance/l2-contracts/lib/flexible-voting/lib/solmate/src/ -zk-governance/=lib/zk-governance/ -cybercorps-contracts/=lib/cybercorps-contracts/src/ +zk-governance/=lib/zk-governance/ \ No newline at end of file diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 9dbe870..c3c1054 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -9,6 +9,7 @@ pragma solidity ^0.8.24; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {ICyberAgreementRegistry} from "cybercorps-contracts/src/interfaces/ICyberAgreementRegistry.sol"; import "./BaseAllocation.sol"; import "./RestrictedTokenAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; @@ -50,6 +51,7 @@ contract metavestController is SafeTransferLib { address public authority; address public dao; + address public registry; address public vestingFactory; // address public tokenOptionFactory; // address public restrictedTokenFactory; @@ -83,7 +85,6 @@ contract metavestController is SafeTransferLib { struct SignedAgreementData { bytes32 id; - string agreementUri; metavestType _metavestType; address grantee; address recipient; @@ -226,12 +227,14 @@ contract metavestController is SafeTransferLib { constructor( address _authority, address _dao, + address _registry, address _vestingFactory // address _tokenOptionFactory, // address _restrictedTokenFactory ) { if (_authority == address(0)) revert MetaVesTController_ZeroAddress(); authority = _authority; + registry = _registry; vestingFactory = _vestingFactory; // tokenOptionFactory = _tokenOptionFactory; // restrictedTokenFactory = _restrictedTokenFactory; @@ -303,39 +306,39 @@ contract metavestController is SafeTransferLib { emit MetaVesTController_ConditionUpdated(_condition, _functionSig); } - function createSignedContract( + function proposeAndSignDeal( uint256 salt, + bytes32 templateId, metavestType _metavestType, address grantee, address recipient, BaseAllocation.Allocation calldata allocation, BaseAllocation.Milestone[] calldata milestones, - string calldata agreementUri, - bytes calldata signature + string[] memory globalValues, + string[] memory partyValues, + bytes calldata signature, + uint256 expiry ) external onlyAuthority returns (bytes32) { - bytes32 contractId = computeContractId(salt, agreementUri, grantee, recipient, allocation, milestones); - - // Verify signature - if (!_verifySignature( - grantee, - SignedAgreementData({ - id: contractId, - agreementUri: agreementUri, - _metavestType: _metavestType, - grantee: grantee, - recipient: recipient, - allocation: allocation, - milestones: milestones - }), - signature - )) { - revert MetaVesTController_SignatureVerificationFailed(); - } + address[] memory allParties = new address[](1); + allParties[0] = grantee; + string[][] memory allPartyValues = new string[][](1); + allPartyValues[0] = partyValues; + + bytes32 agreementId = ICyberAgreementRegistry(registry).createContract( + templateId, + salt, + globalValues, + allParties, + allPartyValues, + bytes32(0), // TODO WIP + address(this), + expiry + ); - agreements[contractId] = AgreementData({ + // TODO revise needed + agreements[agreementId] = AgreementData({ signedData: SignedAgreementData({ - id: contractId, - agreementUri: agreementUri, + id: agreementId, _metavestType: _metavestType, grantee: grantee, recipient: recipient, @@ -344,8 +347,11 @@ contract metavestController is SafeTransferLib { }), pending: true }); - emit MetaVesTController_ContractCreated(contractId, grantee, recipient, _metavestType, allocation, milestones); - return contractId; + + ICyberAgreementRegistry(registry).signContractFor(grantee, agreementId, allPartyValues[0], signature, false, ""); + + emit MetaVesTController_ContractCreated(agreementId, grantee, recipient, _metavestType, allocation, milestones); + return agreementId; } function createMetavest(bytes32 contractId) external conditionCheck returns (address) @@ -959,91 +965,91 @@ contract metavestController is SafeTransferLib { (delegation.expiry == 0 || delegation.expiry > block.timestamp); } - function _verifySignature( - address signer, - SignedAgreementData memory data, - bytes memory signature - ) internal view returns (bool) { - // Hash the data (AgreementData) according to EIP-712 - bytes32 digest = _hashTypedDataV4(data); - - // Recover the signer address - address recoveredSigner = digest.recover(signature); - - // Check direct signature - if (recoveredSigner == signer) { - return true; - } - - // Check delegation signature - Delegation storage delegation = delegations[signer]; - if (delegation.delegate == recoveredSigner && - (delegation.expiry == 0 || delegation.expiry > block.timestamp)) { - return true; - } - - return false; - } - - function _hashTypedDataV4(metavestController.SignedAgreementData memory data) internal view returns(bytes32) { - return keccak256(abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - keccak256(abi.encode( - SIGNED_AGREEMENT_DATA_TYPEHASH, - data.id, - keccak256(bytes(data.agreementUri)), - data._metavestType, - data.grantee, - data.recipient, - _hashAllocaiton(data.allocation), - _hashMilestones(data.milestones) - )) - )); - } - - function _hashSignedAgreementData(metavestController.SignedAgreementData memory data) internal view returns (bytes32) { - return keccak256(abi.encode( - SIGNED_AGREEMENT_DATA_TYPEHASH, - data.id, - keccak256(bytes(data.agreementUri)), - data._metavestType, - data.grantee, - data.recipient, - _hashAllocaiton(data.allocation), - _hashMilestones(data.milestones) - )); - } - - function _hashAllocaiton(BaseAllocation.Allocation memory allocation) internal view returns (bytes32) { - return keccak256(abi.encode( - ALLOCATION_TYPEHASH, - allocation.tokenContract, - allocation.tokenStreamTotal, - allocation.vestingCliffCredit, - allocation.unlockingCliffCredit, - allocation.vestingRate, - allocation.vestingStartTime, - allocation.unlockRate, - allocation.unlockStartTime - )); - } - - function _hashMilestones(BaseAllocation.Milestone[] memory milestones) internal view returns (bytes32) { - bytes32[] memory hashes = new bytes32[](milestones.length); - for (uint256 i = 0; i < milestones.length; i++) { - hashes[i] = _hashMilestone(milestones[i]); - } - return keccak256(abi.encodePacked(hashes)); - } - - function _hashMilestone(BaseAllocation.Milestone memory milestone) internal view returns (bytes32) { - return keccak256(abi.encode( - MILESTONE_TYPEHASH, - milestone.milestoneAward, - milestone.unlockOnCompletion, - milestone.complete, - keccak256(abi.encodePacked(milestone.conditionContracts)) - )); - } +// function _verifySignature( +// address signer, +// SignedAgreementData memory data, +// bytes memory signature +// ) internal view returns (bool) { +// // Hash the data (AgreementData) according to EIP-712 +// bytes32 digest = _hashTypedDataV4(data); +// +// // Recover the signer address +// address recoveredSigner = digest.recover(signature); +// +// // Check direct signature +// if (recoveredSigner == signer) { +// return true; +// } +// +// // Check delegation signature +// Delegation storage delegation = delegations[signer]; +// if (delegation.delegate == recoveredSigner && +// (delegation.expiry == 0 || delegation.expiry > block.timestamp)) { +// return true; +// } +// +// return false; +// } +// +// function _hashTypedDataV4(metavestController.SignedAgreementData memory data) internal view returns(bytes32) { +// return keccak256(abi.encodePacked( +// "\x19\x01", +// DOMAIN_SEPARATOR, +// keccak256(abi.encode( +// SIGNED_AGREEMENT_DATA_TYPEHASH, +// data.id, +// keccak256(bytes(data.agreementUri)), +// data._metavestType, +// data.grantee, +// data.recipient, +// _hashAllocaiton(data.allocation), +// _hashMilestones(data.milestones) +// )) +// )); +// } +// +// function _hashSignedAgreementData(metavestController.SignedAgreementData memory data) internal view returns (bytes32) { +// return keccak256(abi.encode( +// SIGNED_AGREEMENT_DATA_TYPEHASH, +// data.id, +// keccak256(bytes(data.agreementUri)), +// data._metavestType, +// data.grantee, +// data.recipient, +// _hashAllocaiton(data.allocation), +// _hashMilestones(data.milestones) +// )); +// } +// +// function _hashAllocaiton(BaseAllocation.Allocation memory allocation) internal view returns (bytes32) { +// return keccak256(abi.encode( +// ALLOCATION_TYPEHASH, +// allocation.tokenContract, +// allocation.tokenStreamTotal, +// allocation.vestingCliffCredit, +// allocation.unlockingCliffCredit, +// allocation.vestingRate, +// allocation.vestingStartTime, +// allocation.unlockRate, +// allocation.unlockStartTime +// )); +// } +// +// function _hashMilestones(BaseAllocation.Milestone[] memory milestones) internal view returns (bytes32) { +// bytes32[] memory hashes = new bytes32[](milestones.length); +// for (uint256 i = 0; i < milestones.length; i++) { +// hashes[i] = _hashMilestone(milestones[i]); +// } +// return keccak256(abi.encodePacked(hashes)); +// } +// +// function _hashMilestone(BaseAllocation.Milestone memory milestone) internal view returns (bytes32) { +// return keccak256(abi.encode( +// MILESTONE_TYPEHASH, +// milestone.milestoneAward, +// milestone.unlockOnCompletion, +// milestone.complete, +// keccak256(abi.encodePacked(milestone.conditionContracts)) +// )); +// } } diff --git a/test/ZkGuardianCompensation.t.sol b/test/ZkGuardianCompensation.t.sol index 1cc0dfb..80df0e9 100644 --- a/test/ZkGuardianCompensation.t.sol +++ b/test/ZkGuardianCompensation.t.sol @@ -5,14 +5,14 @@ import "../src/MetaVesTController.sol"; import "../src/VestingAllocationFactory.sol"; import "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; -import "./lib/MetaVesTUtils.sol"; import "./lib/MetaVesTControllerTestBase.sol"; -import {CyberAgreementRegistry} from "cybercorps-contracts/CyberAgreementRegistry.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; // Test by forge test --zksync --via-ir contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { // Parameters + bytes32 templateId = bytes32(uint256(123)); bytes32 salt = keccak256("MetaLexZkSyncTest"); uint256 cap = 1e6 ether; // 1M ZK uint48 cappedMinterStartTime = 1756684800; // 2025/9/1 UTC @@ -24,7 +24,8 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { vm.startPrank(deployer); - // Deploy CyberAgreementRegistry + // Deploy CyberAgreementRegistry and prepare templates + // TODO who should be the owner of auth? auth = new BorgAuth{salt: salt}(deployer); registry = CyberAgreementRegistry(address(new ERC1967Proxy{salt: salt}( @@ -35,6 +36,35 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { ) ))); + globalFields = new string[](13); + globalFields[0] = "governingJurisdiction"; // TODO do we need this? + globalFields[1] = "disputeResolution"; // TODO do we need this? + globalFields[2] = "metavestType"; + globalFields[3] = "grantee"; + globalFields[4] = "recipient"; + globalFields[5] = "tokenContract"; + globalFields[6] = "tokenStreamTotal"; + globalFields[7] = "vestingCliffCredit"; + globalFields[8] = "unlockingCliffCredit"; + globalFields[9] = "vestingRate"; + globalFields[10] = "vestingStartTime"; + globalFields[11] = "unlockRate"; + globalFields[12] = "unlockStartTime"; + partyFields = new string[](5); + partyFields[0] = "name"; + partyFields[1] = "evmAddress"; + partyFields[2] = "contactDetails"; // TODO do we need this? + partyFields[3] = "granteeType"; // TODO do we need this? + partyFields[4] = "granteeJurisdiction"; // TODO do we need this? + + registry.createTemplate( + templateId, + "ZkSyncGuardianCompensation", + agreementUri, + globalFields, + partyFields + ); + // Deploy MetaVesT controller vestingAllocationFactory = new VestingAllocationFactory(); @@ -42,6 +72,7 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { controller = new metavestController{salt: salt}( guardianSafe, guardianSafe, + address(registry), address(vestingAllocationFactory) ); @@ -101,178 +132,235 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { _granteeWithdrawAndAsserts(vestingAllocationBob, 10e3 ether, "Bob full"); } - function test_AdminToolingCompensation() public { - (bytes32 contractIdAlice, bytes32 contractIdBob) = _guardiansSignAndTppPass(); - - // Vesting starts and a month has passed - vm.warp(cappedMinterStartTime + 30 days); - - // Alice creates vesting contract and start withdrawal - VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); - _granteeWithdrawAndAsserts(vestingAllocationAlice, uint256(50e3 ether) + uint160(50e3 ether) / 365 days * 30 days, "Alice cliff + first month"); - - // Second month - skip(30 days); - - // Add new grantee for admin/tooling compensation - bytes32 contractIdChad = _signAndCreateContract( - guardianSafe, - chad, - chadPrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 10k ZK total in one cliff - tokenStreamTotal: 10e3 ether, - vestingCliffCredit: 10e3 ether, - unlockingCliffCredit: 10e3 ether, - vestingRate: 0, - vestingStartTime: 0, - unlockRate: 0, - unlockStartTime: 0 - }), - new BaseAllocation.Milestone[](0) - ); - VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); - _granteeWithdrawAndAsserts(vestingAllocationChad, 10e3 ether, "Chad cliff"); - } - - function test_RevertIf_NotAuthority() public { - // Non Guardian SAFE should not be able to accept agreement and create contract - _signAndCreateContract( - deployer, // Not authority - alice, - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 10e18, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert - ); - } - - function test_RevertIf_IncorrectAgreementSignature() public { - // Register Alice with someone else's signature should fail - _signAndCreateContract( - guardianSafe, - alice, - bobPrivateKey, // Use someone else to sign - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 10e18, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert - ); - } - - function test_DelegateSignature() public { - // Alice to delegate to Bob - vm.prank(alice); - controller.setDelegation(bob, block.timestamp + 60); - assertTrue(controller.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); - - // Bob should be able to sign for Alice now - bytes32 contractId = _signAndCreateContract( - guardianSafe, - alice, - bobPrivateKey, // Use Bob to sign - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 10e18, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0) - ); - metavestController.AgreementData memory agreement = controller.getAgreement(contractId); - assertEq(agreement.signedData.grantee, alice, "Alice should be the grantee"); - - // Wait until expiry - skip(61); - - // Bob should no longer be able to sign for Alice - assertFalse(controller.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); - _signAndCreateContract( - guardianSafe, - alice, - bobPrivateKey, // Use Bob to sign - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 10e18, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert - ); - } + // TODO test +// function test_AdminToolingCompensation() public { +// (bytes32 contractIdAlice, bytes32 contractIdBob) = _guardiansSignAndTppPass(); +// +// // Vesting starts and a month has passed +// vm.warp(cappedMinterStartTime + 30 days); +// +// // Alice creates vesting contract and start withdrawal +// VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); +// _granteeWithdrawAndAsserts(vestingAllocationAlice, uint256(50e3 ether) + uint160(50e3 ether) / 365 days * 30 days, "Alice cliff + first month"); +// +// // Second month +// skip(30 days); +// +// // Add new grantee for admin/tooling compensation +// bytes32 contractIdChad = _proposeAndSignDeal( +// templateId, +// guardianSafe, +// chad, +// chadPrivateKey, +// BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// // 10k ZK total in one cliff +// tokenStreamTotal: 10e3 ether, +// vestingCliffCredit: 10e3 ether, +// unlockingCliffCredit: 10e3 ether, +// vestingRate: 0, +// vestingStartTime: 0, +// unlockRate: 0, +// unlockStartTime: 0 +// }), +// new BaseAllocation.Milestone[](0) +// ); +// VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); +// _granteeWithdrawAndAsserts(vestingAllocationChad, 10e3 ether, "Chad cliff"); +// } +// +// function test_RevertIf_NotAuthority() public { +// // Non Guardian SAFE should not be able to accept agreement and create contract +// _proposeAndSignDeal( +// templateId, +// deployer, // Not authority +// alice, +// alicePrivateKey, +// BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter +// unlockRate: 10e18, +// unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter +// }), +// new BaseAllocation.Milestone[](0), +// abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert +// ); +// } +// +// function test_RevertIf_IncorrectAgreementSignature() public { +// // Register Alice with someone else's signature should fail +// _proposeAndSignDeal( +// templateId, +// guardianSafe, +// alice, +// bobPrivateKey, // Use someone else to sign +// BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter +// unlockRate: 10e18, +// unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter +// }), +// new BaseAllocation.Milestone[](0), +// abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert +// ); +// } +// +// function test_DelegateSignature() public { +// // Alice to delegate to Bob +// vm.prank(alice); +// controller.setDelegation(bob, block.timestamp + 60); +// assertTrue(controller.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); +// +// // Bob should be able to sign for Alice now +// bytes32 contractId = _proposeAndSignDeal( +// templateId, +// guardianSafe, +// alice, +// bobPrivateKey, // Use Bob to sign +// BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter +// unlockRate: 10e18, +// unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter +// }), +// new BaseAllocation.Milestone[](0) +// ); +// metavestController.AgreementData memory agreement = controller.getAgreement(contractId); +// assertEq(agreement.signedData.grantee, alice, "Alice should be the grantee"); +// +// // Wait until expiry +// skip(61); +// +// // Bob should no longer be able to sign for Alice +// assertFalse(controller.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); +// _proposeAndSignDeal( +// templateId, +// guardianSafe, +// alice, +// bobPrivateKey, // Use Bob to sign +// BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter +// unlockRate: 10e18, +// unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter +// }), +// new BaseAllocation.Milestone[](0), +// abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert +// ); +// } function _guardiansSignAndTppPass() internal returns(bytes32, bytes32) { // Guardians to sign agreements and register on MetaVesTController - - bytes32 contractIdAlice = _signAndCreateContract( - guardianSafe, - alice, - alicePrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: 100e3 ether, - vestingCliffCredit: 50e3 ether, - unlockingCliffCredit: 50e3 ether, - vestingRate: uint160(50e3 ether) / 365 days, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: uint160(50e3 ether) / 365 days, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0) - ); - - bytes32 contractIdBob = _signAndCreateContract( - guardianSafe, - bob, - bobPrivateKey, - "ipfs.io/ipfs/[cid]", - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 80k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: 80e3 ether, - vestingCliffCredit: 40e3 ether, - unlockingCliffCredit: 40e3 ether, - vestingRate: uint160(40e3 ether) / 365 days, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: uint160(40e3 ether) / 365 days, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0) - ); + bytes32 contractIdAlice; + bytes32 contractIdBob; + + { + string[] memory globalValues = new string[](13); + globalValues[0] = "test governingJurisdiction"; // TODO do we need this? + globalValues[1] = "test disputeResolution"; // TODO do we need this? + globalValues[2] = "0"; // metavestType: Vesting + globalValues[3] = vm.toString(alice); // grantee + globalValues[4] = vm.toString(alice); // recipient + globalValues[5] = vm.toString(address(zkToken)); // tokenContract + globalValues[6] = vm.toString(uint256(100e3)); //tokenStreamTotal + globalValues[7] = vm.toString(uint256(50e3)); // vestingCliffCredit + globalValues[8] = vm.toString(uint256(50e3)); // unlockingCliffCredit + globalValues[9] = vm.toString(uint160(50e3 ether) / 365 days); // vestingRate + globalValues[10] = vm.toString(zkCappedMinter.START_TIME()); // vestingStartTime + globalValues[11] = vm.toString(uint160(50e3 ether) / 365 days); // unlockRate + globalValues[12] = vm.toString(zkCappedMinter.START_TIME()); // unlockStartTime + + string[] memory partyValues = new string[](5); + partyValues[0] = "Alice"; + partyValues[1] = vm.toString(alice); // evmAddress + partyValues[2] = "alice@email.com"; // TODO do we need this? + partyValues[3] = "individual"; // TODO do we need this? + partyValues[4] = "test granteeJurisdiction"; // TODO do we need this? + + contractIdAlice = _proposeAndSignDeal( + templateId, + guardianSafe, + alice, + alicePrivateKey, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 100e3 ether, + vestingCliffCredit: 50e3 ether, + unlockingCliffCredit: 50e3 ether, + vestingRate: uint160(50e3 ether) / 365 days, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: uint160(50e3 ether) / 365 days, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + globalValues, + partyValues, + block.timestamp + 3600 + ); + } + + { + string[] memory globalValues = new string[](13); + globalValues[0] = "test governingJurisdiction"; // TODO do we need this? + globalValues[1] = "test disputeResolution"; // TODO do we need this? + globalValues[2] = "0"; // metavestType: Vesting + globalValues[3] = vm.toString(bob); // grantee + globalValues[4] = vm.toString(bob); // recipient + globalValues[5] = vm.toString(address(zkToken)); // tokenContract + globalValues[6] = vm.toString(uint256(80e3)); //tokenStreamTotal + globalValues[7] = vm.toString(uint256(40e3)); // vestingCliffCredit + globalValues[8] = vm.toString(uint256(40e3)); // unlockingCliffCredit + globalValues[9] = vm.toString(uint160(40e3 ether) / 365 days); // vestingRate + globalValues[10] = vm.toString(zkCappedMinter.START_TIME()); // vestingStartTime + globalValues[11] = vm.toString(uint160(40e3 ether) / 365 days); // unlockRate + globalValues[12] = vm.toString(zkCappedMinter.START_TIME()); // unlockStartTime + + string[] memory partyValues = new string[](5); + partyValues[0] = "Bob"; + partyValues[1] = vm.toString(bob); // evmAddress + partyValues[2] = "bob@email.com"; // TODO do we need this? + partyValues[3] = "individual"; // TODO do we need this? + partyValues[4] = "test granteeJurisdiction"; // TODO do we need this? + + contractIdBob = _proposeAndSignDeal( + templateId, + guardianSafe, + bob, + bobPrivateKey, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 80k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 80e3 ether, + vestingCliffCredit: 40e3 ether, + unlockingCliffCredit: 40e3 ether, + vestingRate: uint160(40e3 ether) / 365 days, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: uint160(40e3 ether) / 365 days, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + globalValues, + partyValues, + block.timestamp + 3600 + ); + } // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index 2f06f77..628c600 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -5,9 +5,9 @@ import "forge-std/Test.sol"; import "../../src/MetaVesTController.sol"; import "../../src/VestingAllocationFactory.sol"; import "../../src/interfaces/zk-governance/IZkTokenV1.sol"; -import "./MetaVesTUtils.sol"; -import {BorgAuth} from "cybercorps-contracts/libs/auth.sol"; -import {CyberAgreementRegistry} from "cybercorps-contracts/CyberAgreementRegistry.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreementUtils.sol"; contract MetaVesTControllerTestBase is Test { // // zkSync Era Sepolia @ 5576300 @@ -31,6 +31,10 @@ contract MetaVesTControllerTestBase is Test { uint256 chadPrivateKey = 3; address chad = vm.addr(chadPrivateKey); + string agreementUri = "ipfs.io/ipfs/[cid]"; + string[] globalFields; + string[] partyFields; + BorgAuth auth; CyberAgreementRegistry registry; @@ -47,43 +51,58 @@ contract MetaVesTControllerTestBase is Test { assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); } - function _signAndCreateContract( + function _proposeAndSignDeal( + bytes32 templateId, address authority, address grantee, uint256 granteePrivateKey, - string memory agreementUri, BaseAllocation.Allocation memory allocation, - BaseAllocation.Milestone[] memory milestones + BaseAllocation.Milestone[] memory milestones, + string[] memory globalValues, + string[] memory partyValues, + uint256 expiry ) internal returns(bytes32) { - return _signAndCreateContract( - authority, grantee, granteePrivateKey, agreementUri, allocation, milestones, + return _proposeAndSignDeal( + templateId, authority, grantee, granteePrivateKey, allocation, milestones, globalValues, partyValues, expiry, "" // Not expecting revert ); } - function _signAndCreateContract( + function _proposeAndSignDeal( + bytes32 templateId, address authority, address grantee, uint256 granteePrivateKey, - string memory agreementUri, BaseAllocation.Allocation memory allocation, BaseAllocation.Milestone[] memory milestones, + string[] memory globalValues, + string[] memory partyValues, + uint256 expiry, bytes memory expectRevertData ) internal returns(bytes32) { uint256 contractSalt = block.timestamp; - bytes32 expectedContractId = controller.computeContractId(contractSalt, agreementUri, grantee, grantee, allocation, milestones); - bytes memory signature = MetaVesTUtils.signAgreementTypedData( + + address[] memory allParties = new address[](1); + allParties[0] = grantee; + bytes32 expectedContractId = keccak256( + abi.encode( + templateId, + contractSalt, + globalValues, + allParties + ) + ); + + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, - controller, - metavestController.SignedAgreementData({ - id: expectedContractId, - agreementUri: agreementUri, - _metavestType: metavestController.metavestType.Vesting, - grantee: grantee, - recipient: grantee, - allocation: allocation, - milestones: milestones - }), + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + expectedContractId, + agreementUri, + globalFields, + partyFields, + globalValues, + partyValues, granteePrivateKey ); @@ -91,15 +110,18 @@ contract MetaVesTControllerTestBase is Test { vm.expectRevert(expectRevertData); } vm.prank(authority); - bytes32 contractId = controller.createSignedContract( + bytes32 contractId = controller.proposeAndSignDeal( contractSalt, + templateId, metavestController.metavestType.Vesting, grantee, grantee, allocation, milestones, - agreementUri, - signature + globalValues, + partyValues, + signature, + expiry ); if (expectRevertData.length == 0) { diff --git a/test/lib/MetaVesTUtils.sol b/test/lib/MetaVesTUtils.sol deleted file mode 100644 index cd48983..0000000 --- a/test/lib/MetaVesTUtils.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.24; - -import "../../src/MetaVesTController.sol"; -import {Vm} from "forge-std/Test.sol"; - -library MetaVesTUtils { - function signAgreementTypedData( - Vm vm, - metavestController controller, - metavestController.SignedAgreementData memory data, - uint256 privKey - ) internal view returns (bytes memory signature) { - bytes32 digest = keccak256( - abi.encodePacked( - "\x19\x01", - controller.DOMAIN_SEPARATOR(), - _hashSignedAgreementData(controller, data) - ) - ); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, digest); - signature = abi.encodePacked(r, s, v); - return signature; - } - - function _hashSignedAgreementData( - metavestController controller, - metavestController.SignedAgreementData memory data - ) internal view returns (bytes32) { - return keccak256(abi.encode( - controller.SIGNED_AGREEMENT_DATA_TYPEHASH(), - data.id, - keccak256(bytes(data.agreementUri)), - data._metavestType, - data.grantee, - data.recipient, - _hashAllocaiton(controller, data.allocation), - _hashMilestones(controller, data.milestones) - )); - } - - function _hashAllocaiton( - metavestController controller, - BaseAllocation.Allocation memory allocation - ) internal view returns (bytes32) { - return keccak256(abi.encode( - controller.ALLOCATION_TYPEHASH(), - allocation.tokenContract, - allocation.tokenStreamTotal, - allocation.vestingCliffCredit, - allocation.unlockingCliffCredit, - allocation.vestingRate, - allocation.vestingStartTime, - allocation.unlockRate, - allocation.unlockStartTime - )); - } - - function _hashMilestones( - metavestController controller, - BaseAllocation.Milestone[] memory milestones - ) internal view returns (bytes32) { - bytes32[] memory hashes = new bytes32[](milestones.length); - for (uint256 i = 0; i < milestones.length; i++) { - hashes[i] = _hashMilestone(controller, milestones[i]); - } - return keccak256(abi.encodePacked(hashes)); - } - - function _hashMilestone( - metavestController controller, - BaseAllocation.Milestone memory milestone - ) internal view returns (bytes32) { - return keccak256(abi.encode( - controller.MILESTONE_TYPEHASH(), - milestone.milestoneAward, - milestone.unlockOnCompletion, - milestone.complete, - keccak256(abi.encodePacked(milestone.conditionContracts)) - )); - } -} From 520bbddadd0640510d70063ef4ffd09edffce54c Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 6 Aug 2025 15:25:25 -0700 Subject: [PATCH 20/68] feat: refactor deal structures --- src/MetaVesTController.sol | 97 +++++++++++++++---------- test/ZkGuardianCompensation.t.sol | 2 + test/lib/MetaVesTControllerTestBase.sol | 5 +- 3 files changed, 63 insertions(+), 41 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index c3c1054..e2673e7 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -78,13 +78,8 @@ contract metavestController is SafeTransferLib { mapping(address => uint256) voterPower; } - struct AgreementData { - SignedAgreementData signedData; - bool pending; // Pending MeteVesT deployment - } - - struct SignedAgreementData { - bytes32 id; + struct DealData { + bytes32 agreementId; metavestType _metavestType; address grantee; address recipient; @@ -114,7 +109,7 @@ contract metavestController is SafeTransferLib { mapping(bytes32 => bool) public setMajorityVoteActive; /// @notice granteeId => granteeData - mapping(bytes32 => AgreementData) public agreements; + mapping(bytes32 => DealData) public deals; mapping(address => Delegation) public delegations; @@ -133,9 +128,22 @@ contract metavestController is SafeTransferLib { event MetaVesTController_SetRemoved(string indexed set); event MetaVesTController_AddressAddedToSet(string set, address indexed grantee); event MetaVesTController_AddressRemovedFromSet(string set, address indexed grantee); - event MetaVesTController_MetaVestCreated(address indexed metavest, bytes32 contractId); + event MetaVesTController_MetaVestCreated(address indexed metavest, bytes32 agreementId); event MetaVesTController_ZkCappedMinterUpdated(address zkCappedMinter); - event MetaVesTController_ContractCreated(bytes32 indexed contractId, address indexed grantee, address indexed recipient, metavestType metavestType, BaseAllocation.Allocation allocation, BaseAllocation.Milestone[] milestones); + event MetaVesTController_DealProposed( + bytes32 indexed agreementId, + address indexed grantee, + address indexed recipient, + metavestType metavestType, + BaseAllocation.Allocation allocation, + BaseAllocation.Milestone[] milestones, + bool hasSecret, + address registry + ); + event MetaVesTController_DealFinalizedAndMetaVestCreated( + bytes32 indexed agreementId, + address metavest + ); event MetaVesTController_DelegationSet(address indexed delegator, address indexed delegate, uint256 expiry); event MetaVesTController_DelegationRevoked(address indexed delegator, address indexed delegate); @@ -173,7 +181,7 @@ contract metavestController is SafeTransferLib { error MetaVesTController_SetAlreadyExists(); error MetaVesTController_StringTooLong(); error MetaVesTController_TypeNotSupported(metavestType _type); - error MetaVesTController_AgreementAlreadyProcessed(); + error MetaVesTController_DealAlreadyFinalized(); error MetaVesTController_SignatureVerificationFailed(); /// @@ -317,6 +325,7 @@ contract metavestController is SafeTransferLib { string[] memory globalValues, string[] memory partyValues, bytes calldata signature, + bytes32 secretHash, uint256 expiry ) external onlyAuthority returns (bytes32) { address[] memory allParties = new address[](1); @@ -330,54 +339,60 @@ contract metavestController is SafeTransferLib { globalValues, allParties, allPartyValues, - bytes32(0), // TODO WIP + secretHash, address(this), expiry ); - // TODO revise needed - agreements[agreementId] = AgreementData({ - signedData: SignedAgreementData({ - id: agreementId, - _metavestType: _metavestType, - grantee: grantee, - recipient: recipient, - allocation: allocation, - milestones: milestones - }), - pending: true + deals[agreementId] = DealData({ + agreementId: agreementId, + _metavestType: _metavestType, + grantee: grantee, + recipient: recipient, + allocation: allocation, + milestones: milestones }); - ICyberAgreementRegistry(registry).signContractFor(grantee, agreementId, allPartyValues[0], signature, false, ""); + ICyberAgreementRegistry(registry).signContractFor( + grantee, + agreementId, + allPartyValues[0], + signature, + false, // Not meant for anyone else other than the signer + "" // Signer == proposer, no secret needed + ); - emit MetaVesTController_ContractCreated(agreementId, grantee, recipient, _metavestType, allocation, milestones); + emit MetaVesTController_DealProposed( + agreementId, grantee, recipient, _metavestType, allocation, milestones, + secretHash > 0, + registry + ); return agreementId; } - function createMetavest(bytes32 contractId) external conditionCheck returns (address) + function createMetavest(bytes32 agreementId) external conditionCheck returns (address) { - AgreementData storage agreement = agreements[contractId]; - - if (!agreement.pending) { - revert MetaVesTController_AgreementAlreadyProcessed(); + if (ICyberAgreementRegistry(registry).isFinalized(agreementId)) { + revert MetaVesTController_DealAlreadyFinalized(); } - agreement.pending = false; + + DealData storage deal = deals[agreementId]; address newMetavest; - if(agreement.signedData._metavestType == metavestType.Vesting) + if(deal._metavestType == metavestType.Vesting) { // TODO WIP: must support recipient - newMetavest = createVestingAllocation(agreement.signedData.grantee, agreement.signedData.allocation, agreement.signedData.milestones); + newMetavest = createVestingAllocation(deal.grantee, deal.allocation, deal.milestones); } - else if(agreement.signedData._metavestType == metavestType.TokenOption) + else if(deal._metavestType == metavestType.TokenOption) { // TODO will be supported in the next stage - revert MetaVesTController_TypeNotSupported(agreement.signedData._metavestType); + revert MetaVesTController_TypeNotSupported(deal._metavestType); } - else if(agreement.signedData._metavestType == metavestType.RestrictedTokenAward) + else if(deal._metavestType == metavestType.RestrictedTokenAward) { // TODO will be supported in the next stage - revert MetaVesTController_TypeNotSupported(agreement.signedData._metavestType); + revert MetaVesTController_TypeNotSupported(deal._metavestType); } else { @@ -390,7 +405,9 @@ contract metavestController is SafeTransferLib { ); BaseAllocation(newMetavest).setZkCappedMinterAddress(address(zkCappedMinter)); - emit MetaVesTController_MetaVestCreated(newMetavest, contractId); + ICyberAgreementRegistry(registry).finalizeContract(agreementId); + + emit MetaVesTController_DealFinalizedAndMetaVestCreated(agreementId, newMetavest); return newMetavest; } @@ -909,8 +926,8 @@ contract metavestController is SafeTransferLib { return keccak256(abi.encode(salt, agreementUri, grantee, recipient, allocation, milestones)); } - function getAgreement(bytes32 contractId) public view returns (AgreementData memory) { - return agreements[contractId]; + function getDeal(bytes32 agreementId) public view returns (DealData memory) { + return deals[agreementId]; } function setDelegation(address delegate, uint256 expiry) external { diff --git a/test/ZkGuardianCompensation.t.sol b/test/ZkGuardianCompensation.t.sol index 80df0e9..e169e3c 100644 --- a/test/ZkGuardianCompensation.t.sol +++ b/test/ZkGuardianCompensation.t.sol @@ -312,6 +312,7 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { new BaseAllocation.Milestone[](0), globalValues, partyValues, + bytes32(0), // no secrets block.timestamp + 3600 ); } @@ -358,6 +359,7 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { new BaseAllocation.Milestone[](0), globalValues, partyValues, + bytes32(0), // no secrets block.timestamp + 3600 ); } diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index 628c600..f3c9ee5 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -60,10 +60,11 @@ contract MetaVesTControllerTestBase is Test { BaseAllocation.Milestone[] memory milestones, string[] memory globalValues, string[] memory partyValues, + bytes32 secretHash, uint256 expiry ) internal returns(bytes32) { return _proposeAndSignDeal( - templateId, authority, grantee, granteePrivateKey, allocation, milestones, globalValues, partyValues, expiry, + templateId, authority, grantee, granteePrivateKey, allocation, milestones, globalValues, partyValues, secretHash, expiry, "" // Not expecting revert ); } @@ -77,6 +78,7 @@ contract MetaVesTControllerTestBase is Test { BaseAllocation.Milestone[] memory milestones, string[] memory globalValues, string[] memory partyValues, + bytes32 secretHash, uint256 expiry, bytes memory expectRevertData ) internal returns(bytes32) { @@ -121,6 +123,7 @@ contract MetaVesTControllerTestBase is Test { globalValues, partyValues, signature, + secretHash, expiry ); From 00b8a070b92e7c8e166279c9e35ba90534325b78 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 6 Aug 2025 16:11:33 -0700 Subject: [PATCH 21/68] wip: test: update all to specs --- src/MetaVesTController.sol | 143 --------- test/ZkGuardianCompensation.t.sol | 411 +++++++++++------------- test/lib/MetaVesTControllerTestBase.sol | 34 +- 3 files changed, 209 insertions(+), 379 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index e2673e7..dd06a18 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -111,8 +111,6 @@ contract metavestController is SafeTransferLib { /// @notice granteeId => granteeData mapping(bytes32 => DealData) public deals; - mapping(address => Delegation) public delegations; - /// /// EVENTS /// @@ -182,7 +180,6 @@ contract metavestController is SafeTransferLib { error MetaVesTController_StringTooLong(); error MetaVesTController_TypeNotSupported(metavestType _type); error MetaVesTController_DealAlreadyFinalized(); - error MetaVesTController_SignatureVerificationFailed(); /// /// FUNCTIONS @@ -929,144 +926,4 @@ contract metavestController is SafeTransferLib { function getDeal(bytes32 agreementId) public view returns (DealData memory) { return deals[agreementId]; } - - function setDelegation(address delegate, uint256 expiry) external { - if (delegate == address(0)) revert("Cannot delegate to zero address"); - if (delegate == msg.sender) revert("Cannot delegate to self"); - if (expiry != 0 && expiry <= block.timestamp) revert("Expiry must be in the future"); - - delegations[msg.sender] = Delegation({delegate: delegate, expiry: expiry}); - emit MetaVesTController_DelegationSet(msg.sender, delegate, expiry); - } - - /** - * @dev Revoke delegation for the caller - */ - function revokeDelegation() external { - address delegate = delegations[msg.sender].delegate; - delete delegations[msg.sender]; - emit MetaVesTController_DelegationRevoked(msg.sender, delegate); - } - - /** - * @dev Get delegation info for a given address - * @param delegator The address to check for delegation - * @return delegate The delegate address - * @return expiry The expiry timestamp (0 if no expiry) - */ - function getDelegation(address delegator) external view returns (address delegate, uint256 expiry) { - Delegation storage delegation = delegations[delegator]; - return (delegation.delegate, delegation.expiry); - } - - /** - * @dev Check if a delegation is valid (not expired) - * @param delegator The delegator address - * @return True if delegation exists and is not expired - */ - function isValidDelegation(address delegator) external view returns (bool) { - Delegation storage delegation = delegations[delegator]; - return delegation.delegate != address(0) && - (delegation.expiry == 0 || delegation.expiry > block.timestamp); - } - - /** - * @dev Check if an address is a valid delegate for a given delegator - * @param delegator The delegator address - * @param delegate The delegate address to check - * @return True if the delegate is valid and not expired - */ - function isValidDelegate(address delegator, address delegate) external view returns (bool) { - Delegation storage delegation = delegations[delegator]; - return delegation.delegate == delegate && - (delegation.expiry == 0 || delegation.expiry > block.timestamp); - } - -// function _verifySignature( -// address signer, -// SignedAgreementData memory data, -// bytes memory signature -// ) internal view returns (bool) { -// // Hash the data (AgreementData) according to EIP-712 -// bytes32 digest = _hashTypedDataV4(data); -// -// // Recover the signer address -// address recoveredSigner = digest.recover(signature); -// -// // Check direct signature -// if (recoveredSigner == signer) { -// return true; -// } -// -// // Check delegation signature -// Delegation storage delegation = delegations[signer]; -// if (delegation.delegate == recoveredSigner && -// (delegation.expiry == 0 || delegation.expiry > block.timestamp)) { -// return true; -// } -// -// return false; -// } -// -// function _hashTypedDataV4(metavestController.SignedAgreementData memory data) internal view returns(bytes32) { -// return keccak256(abi.encodePacked( -// "\x19\x01", -// DOMAIN_SEPARATOR, -// keccak256(abi.encode( -// SIGNED_AGREEMENT_DATA_TYPEHASH, -// data.id, -// keccak256(bytes(data.agreementUri)), -// data._metavestType, -// data.grantee, -// data.recipient, -// _hashAllocaiton(data.allocation), -// _hashMilestones(data.milestones) -// )) -// )); -// } -// -// function _hashSignedAgreementData(metavestController.SignedAgreementData memory data) internal view returns (bytes32) { -// return keccak256(abi.encode( -// SIGNED_AGREEMENT_DATA_TYPEHASH, -// data.id, -// keccak256(bytes(data.agreementUri)), -// data._metavestType, -// data.grantee, -// data.recipient, -// _hashAllocaiton(data.allocation), -// _hashMilestones(data.milestones) -// )); -// } -// -// function _hashAllocaiton(BaseAllocation.Allocation memory allocation) internal view returns (bytes32) { -// return keccak256(abi.encode( -// ALLOCATION_TYPEHASH, -// allocation.tokenContract, -// allocation.tokenStreamTotal, -// allocation.vestingCliffCredit, -// allocation.unlockingCliffCredit, -// allocation.vestingRate, -// allocation.vestingStartTime, -// allocation.unlockRate, -// allocation.unlockStartTime -// )); -// } -// -// function _hashMilestones(BaseAllocation.Milestone[] memory milestones) internal view returns (bytes32) { -// bytes32[] memory hashes = new bytes32[](milestones.length); -// for (uint256 i = 0; i < milestones.length; i++) { -// hashes[i] = _hashMilestone(milestones[i]); -// } -// return keccak256(abi.encodePacked(hashes)); -// } -// -// function _hashMilestone(BaseAllocation.Milestone memory milestone) internal view returns (bytes32) { -// return keccak256(abi.encode( -// MILESTONE_TYPEHASH, -// milestone.milestoneAward, -// milestone.unlockOnCompletion, -// milestone.complete, -// keccak256(abi.encodePacked(milestone.conditionContracts)) -// )); -// } } diff --git a/test/ZkGuardianCompensation.t.sol b/test/ZkGuardianCompensation.t.sol index e169e3c..fcbf0c6 100644 --- a/test/ZkGuardianCompensation.t.sol +++ b/test/ZkGuardianCompensation.t.sol @@ -131,238 +131,193 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { _granteeWithdrawAndAsserts(vestingAllocationBob, 10e3 ether, "Bob full"); } + + function test_AdminToolingCompensation() public { + (bytes32 contractIdAlice, bytes32 contractIdBob) = _guardiansSignAndTppPass(); + + // Vesting starts and a month has passed + vm.warp(cappedMinterStartTime + 30 days); + + // Alice creates vesting contract and start withdrawal + VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); + _granteeWithdrawAndAsserts(vestingAllocationAlice, uint256(50e3 ether) + uint160(50e3 ether) / 365 days * 30 days, "Alice cliff + first month"); + + // Second month + skip(30 days); + + // Add new grantee for admin/tooling compensation + bytes32 contractIdChad = _proposeAndSignDeal( + templateId, + guardianSafe, + chad, + chadPrivateKey, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 10k ZK total in one cliff + tokenStreamTotal: 10e3 ether, + vestingCliffCredit: 10e3 ether, + unlockingCliffCredit: 10e3 ether, + vestingRate: 0, + vestingStartTime: 0, + unlockRate: 0, + unlockStartTime: 0 + }), + new BaseAllocation.Milestone[](0), + "Chad", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + ); + VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); + _granteeWithdrawAndAsserts(vestingAllocationChad, 10e3 ether, "Chad cliff"); + } + + function test_RevertIf_NotAuthority() public { + // Non Guardian SAFE should not be able to accept agreement and create contract + _proposeAndSignDeal( + templateId, + deployer, // Not authority + alice, + alicePrivateKey, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 10e18, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert + ); + } + + function test_RevertIf_IncorrectAgreementSignature() public { + // Register Alice with someone else's signature should fail + _proposeAndSignDeal( + templateId, + guardianSafe, + alice, + bobPrivateKey, // Use someone else to sign + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 10e18, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert + ); + } - // TODO test -// function test_AdminToolingCompensation() public { -// (bytes32 contractIdAlice, bytes32 contractIdBob) = _guardiansSignAndTppPass(); -// -// // Vesting starts and a month has passed -// vm.warp(cappedMinterStartTime + 30 days); -// -// // Alice creates vesting contract and start withdrawal -// VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); -// _granteeWithdrawAndAsserts(vestingAllocationAlice, uint256(50e3 ether) + uint160(50e3 ether) / 365 days * 30 days, "Alice cliff + first month"); -// -// // Second month -// skip(30 days); -// -// // Add new grantee for admin/tooling compensation -// bytes32 contractIdChad = _proposeAndSignDeal( -// templateId, -// guardianSafe, -// chad, -// chadPrivateKey, -// BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// // 10k ZK total in one cliff -// tokenStreamTotal: 10e3 ether, -// vestingCliffCredit: 10e3 ether, -// unlockingCliffCredit: 10e3 ether, -// vestingRate: 0, -// vestingStartTime: 0, -// unlockRate: 0, -// unlockStartTime: 0 -// }), -// new BaseAllocation.Milestone[](0) -// ); -// VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); -// _granteeWithdrawAndAsserts(vestingAllocationChad, 10e3 ether, "Chad cliff"); -// } -// -// function test_RevertIf_NotAuthority() public { -// // Non Guardian SAFE should not be able to accept agreement and create contract -// _proposeAndSignDeal( -// templateId, -// deployer, // Not authority -// alice, -// alicePrivateKey, -// BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter -// unlockRate: 10e18, -// unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter -// }), -// new BaseAllocation.Milestone[](0), -// abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert -// ); -// } -// -// function test_RevertIf_IncorrectAgreementSignature() public { -// // Register Alice with someone else's signature should fail -// _proposeAndSignDeal( -// templateId, -// guardianSafe, -// alice, -// bobPrivateKey, // Use someone else to sign -// BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter -// unlockRate: 10e18, -// unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter -// }), -// new BaseAllocation.Milestone[](0), -// abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert -// ); -// } -// -// function test_DelegateSignature() public { -// // Alice to delegate to Bob -// vm.prank(alice); -// controller.setDelegation(bob, block.timestamp + 60); -// assertTrue(controller.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); -// -// // Bob should be able to sign for Alice now -// bytes32 contractId = _proposeAndSignDeal( -// templateId, -// guardianSafe, -// alice, -// bobPrivateKey, // Use Bob to sign -// BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter -// unlockRate: 10e18, -// unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter -// }), -// new BaseAllocation.Milestone[](0) -// ); -// metavestController.AgreementData memory agreement = controller.getAgreement(contractId); -// assertEq(agreement.signedData.grantee, alice, "Alice should be the grantee"); -// -// // Wait until expiry -// skip(61); -// -// // Bob should no longer be able to sign for Alice -// assertFalse(controller.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); -// _proposeAndSignDeal( -// templateId, -// guardianSafe, -// alice, -// bobPrivateKey, // Use Bob to sign -// BaseAllocation.Allocation({ -// tokenContract: address(zkToken), -// tokenStreamTotal: 1000e18, -// vestingCliffCredit: 100e18, -// unlockingCliffCredit: 100e18, -// vestingRate: 10e18, -// vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter -// unlockRate: 10e18, -// unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter -// }), -// new BaseAllocation.Milestone[](0), -// abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert -// ); -// } + function test_DelegateSignature() public { + // Alice to delegate to Bob + vm.prank(alice); + registry.setDelegation(bob, block.timestamp + 60); + assertTrue(registry.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); + + // Bob should be able to sign for Alice now + bytes32 agreementId = _proposeAndSignDeal( + templateId, + guardianSafe, + alice, + bobPrivateKey, // Use Bob to sign + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 10e18, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + ); + metavestController.DealData memory deal = controller.getDeal(agreementId); + assertEq(deal.grantee, alice, "Alice should be the grantee"); + + // Wait until expiry + skip(61); + + // Bob should no longer be able to sign for Alice + assertFalse(registry.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); + _proposeAndSignDeal( + templateId, + guardianSafe, + alice, + bobPrivateKey, // Use Bob to sign + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000e18, + vestingCliffCredit: 100e18, + unlockingCliffCredit: 100e18, + vestingRate: 10e18, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 10e18, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert + ); + } function _guardiansSignAndTppPass() internal returns(bytes32, bytes32) { // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice; - bytes32 contractIdBob; - - { - string[] memory globalValues = new string[](13); - globalValues[0] = "test governingJurisdiction"; // TODO do we need this? - globalValues[1] = "test disputeResolution"; // TODO do we need this? - globalValues[2] = "0"; // metavestType: Vesting - globalValues[3] = vm.toString(alice); // grantee - globalValues[4] = vm.toString(alice); // recipient - globalValues[5] = vm.toString(address(zkToken)); // tokenContract - globalValues[6] = vm.toString(uint256(100e3)); //tokenStreamTotal - globalValues[7] = vm.toString(uint256(50e3)); // vestingCliffCredit - globalValues[8] = vm.toString(uint256(50e3)); // unlockingCliffCredit - globalValues[9] = vm.toString(uint160(50e3 ether) / 365 days); // vestingRate - globalValues[10] = vm.toString(zkCappedMinter.START_TIME()); // vestingStartTime - globalValues[11] = vm.toString(uint160(50e3 ether) / 365 days); // unlockRate - globalValues[12] = vm.toString(zkCappedMinter.START_TIME()); // unlockStartTime - - string[] memory partyValues = new string[](5); - partyValues[0] = "Alice"; - partyValues[1] = vm.toString(alice); // evmAddress - partyValues[2] = "alice@email.com"; // TODO do we need this? - partyValues[3] = "individual"; // TODO do we need this? - partyValues[4] = "test granteeJurisdiction"; // TODO do we need this? - - contractIdAlice = _proposeAndSignDeal( - templateId, - guardianSafe, - alice, - alicePrivateKey, - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: 100e3 ether, - vestingCliffCredit: 50e3 ether, - unlockingCliffCredit: 50e3 ether, - vestingRate: uint160(50e3 ether) / 365 days, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: uint160(50e3 ether) / 365 days, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - globalValues, - partyValues, - bytes32(0), // no secrets - block.timestamp + 3600 - ); - } - - { - string[] memory globalValues = new string[](13); - globalValues[0] = "test governingJurisdiction"; // TODO do we need this? - globalValues[1] = "test disputeResolution"; // TODO do we need this? - globalValues[2] = "0"; // metavestType: Vesting - globalValues[3] = vm.toString(bob); // grantee - globalValues[4] = vm.toString(bob); // recipient - globalValues[5] = vm.toString(address(zkToken)); // tokenContract - globalValues[6] = vm.toString(uint256(80e3)); //tokenStreamTotal - globalValues[7] = vm.toString(uint256(40e3)); // vestingCliffCredit - globalValues[8] = vm.toString(uint256(40e3)); // unlockingCliffCredit - globalValues[9] = vm.toString(uint160(40e3 ether) / 365 days); // vestingRate - globalValues[10] = vm.toString(zkCappedMinter.START_TIME()); // vestingStartTime - globalValues[11] = vm.toString(uint160(40e3 ether) / 365 days); // unlockRate - globalValues[12] = vm.toString(zkCappedMinter.START_TIME()); // unlockStartTime - - string[] memory partyValues = new string[](5); - partyValues[0] = "Bob"; - partyValues[1] = vm.toString(bob); // evmAddress - partyValues[2] = "bob@email.com"; // TODO do we need this? - partyValues[3] = "individual"; // TODO do we need this? - partyValues[4] = "test granteeJurisdiction"; // TODO do we need this? - - contractIdBob = _proposeAndSignDeal( - templateId, - guardianSafe, - bob, - bobPrivateKey, - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 80k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: 80e3 ether, - vestingCliffCredit: 40e3 ether, - unlockingCliffCredit: 40e3 ether, - vestingRate: uint160(40e3 ether) / 365 days, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: uint160(40e3 ether) / 365 days, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - globalValues, - partyValues, - bytes32(0), // no secrets - block.timestamp + 3600 - ); - } + + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + guardianSafe, + alice, + alicePrivateKey, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 100e3 ether, + vestingCliffCredit: 50e3 ether, + unlockingCliffCredit: 50e3 ether, + vestingRate: uint160(50e3 ether) / 365 days, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: uint160(50e3 ether) / 365 days, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + ); + + bytes32 contractIdBob = _proposeAndSignDeal( + templateId, + guardianSafe, + bob, + bobPrivateKey, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 80k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 80e3 ether, + vestingCliffCredit: 40e3 ether, + unlockingCliffCredit: 40e3 ether, + vestingRate: uint160(40e3 ether) / 365 days, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: uint160(40e3 ether) / 365 days, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Bob", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + ); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index f3c9ee5..f01b687 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -58,13 +58,11 @@ contract MetaVesTControllerTestBase is Test { uint256 granteePrivateKey, BaseAllocation.Allocation memory allocation, BaseAllocation.Milestone[] memory milestones, - string[] memory globalValues, - string[] memory partyValues, - bytes32 secretHash, + string memory partyName, uint256 expiry ) internal returns(bytes32) { return _proposeAndSignDeal( - templateId, authority, grantee, granteePrivateKey, allocation, milestones, globalValues, partyValues, secretHash, expiry, + templateId, authority, grantee, granteePrivateKey, allocation, milestones, partyName, expiry, "" // Not expecting revert ); } @@ -76,14 +74,34 @@ contract MetaVesTControllerTestBase is Test { uint256 granteePrivateKey, BaseAllocation.Allocation memory allocation, BaseAllocation.Milestone[] memory milestones, - string[] memory globalValues, - string[] memory partyValues, - bytes32 secretHash, + string memory partyName, uint256 expiry, bytes memory expectRevertData ) internal returns(bytes32) { uint256 contractSalt = block.timestamp; + string[] memory globalValues = new string[](13); + globalValues[0] = "test governingJurisdiction"; // TODO do we need this? + globalValues[1] = "test disputeResolution"; // TODO do we need this? + globalValues[2] = "0"; // metavestType: Vesting + globalValues[3] = vm.toString(grantee); // grantee + globalValues[4] = vm.toString(grantee); // recipient + globalValues[5] = vm.toString(allocation.tokenContract); // tokenContract + globalValues[6] = vm.toString(allocation.tokenStreamTotal); //tokenStreamTotal + globalValues[7] = vm.toString(allocation.vestingCliffCredit); // vestingCliffCredit + globalValues[8] = vm.toString(allocation.unlockingCliffCredit); // unlockingCliffCredit + globalValues[9] = vm.toString(allocation.vestingRate); // vestingRate + globalValues[10] = vm.toString(allocation.vestingStartTime); // vestingStartTime + globalValues[11] = vm.toString(allocation.unlockRate); // unlockRate + globalValues[12] = vm.toString(allocation.unlockStartTime); // unlockStartTime + + string[] memory partyValues = new string[](5); + partyValues[0] = partyName; + partyValues[1] = vm.toString(grantee); // evmAddress + partyValues[2] = "email@company.com"; // TODO do we need this? + partyValues[3] = "individual"; // TODO do we need this? + partyValues[4] = "test granteeJurisdiction"; // TODO do we need this? + address[] memory allParties = new address[](1); allParties[0] = grantee; bytes32 expectedContractId = keccak256( @@ -123,7 +141,7 @@ contract MetaVesTControllerTestBase is Test { globalValues, partyValues, signature, - secretHash, + bytes32(0), // no secrets expiry ); From fb0b3508c33f83a65e260026b6549f9ff6ce1b21 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 6 Aug 2025 16:11:44 -0700 Subject: [PATCH 22/68] Revert "wip: test: temporarily remove breaking tests" This reverts commit 0d97fbf29137d5a207f6497253fe6684ee3e4ea8. --- test/AuditBaseA.t.sol | 59 ++ test/AuditBaseA2.t.sol | 220 ++++++ test/AuditBaseC.t.sol | 61 ++ test/AuditBaseC3.t.sol | 88 +++ test/amendement.t.sol | 917 +++++++++++++++++++++++++ test/controller.t.sol | 1470 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 2815 insertions(+) create mode 100644 test/AuditBaseA.t.sol create mode 100644 test/AuditBaseA2.t.sol create mode 100644 test/AuditBaseC.t.sol create mode 100644 test/AuditBaseC3.t.sol create mode 100644 test/amendement.t.sol create mode 100644 test/controller.t.sol diff --git a/test/AuditBaseA.t.sol b/test/AuditBaseA.t.sol new file mode 100644 index 0000000..1846b52 --- /dev/null +++ b/test/AuditBaseA.t.sol @@ -0,0 +1,59 @@ +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; + +import "../test/amendement.t.sol"; + +contract EvilGrant { + + function grantee () public view returns (address) { + return address(0x31337); + } + function getGoverningPower() public view returns (uint256) { + return 99999999999999999999999999999; + } +} + +contract Audit is MetaVestControllerTest { + function test_RevertIf_AuditArbitraryVote() public { + // template from testVoteOnMetavestAmendment + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + address attacker = address(0x31337); + address evil_grant = address(new EvilGrant()); + + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); + + (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + console.log("attacker made vote and power is" , currentVotingPower); + } + + function test_RevertIf_AuditRemoveConfirmedMilestone() public { + // template from testRemoveMilestone + address vestingAllocation = createDummyVestingAllocation(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); + vm.prank(authority); + controller.removeMetavestMilestone(vestingAllocation, 0); + } + +} \ No newline at end of file diff --git a/test/AuditBaseA2.t.sol b/test/AuditBaseA2.t.sol new file mode 100644 index 0000000..6a4288e --- /dev/null +++ b/test/AuditBaseA2.t.sol @@ -0,0 +1,220 @@ +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; + +import "../test/amendement.t.sol"; + +contract EvilGrant { + + function grantee () public view returns (address) { + return address(0x31337); + } + function getGoverningPower() public view returns (uint256) { + return 99999999999999999999999999999; + } +} + +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract Audit is MetaVestControllerTest { + function test_RevertIf_AuditArbitraryVote() public { + // template from testVoteOnMetavestAmendment + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + address attacker = address(0x31337); + address evil_grant = address(new EvilGrant()); + + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); + + (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + console.log("attacker made vote and power is" , currentVotingPower); + } + + function test_RevertIf_AuditRemoveConfirmedMilestone() public { + // template from testRemoveMilestone + address vestingAllocation = createDummyVestingAllocation(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); + vm.prank(authority); + controller.removeMetavestMilestone(vestingAllocation, 0); + } + + function testAuditProposeMajorityMetavestAmendmentExpire() public { + // template from testProposeMajorityMetavestAmendment + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + // proposal expired + uint256 AMENDMENT_TIME_LIMIT = 604800; + vm.warp(block.timestamp + AMENDMENT_TIME_LIMIT + 1); + + // MetaVesTController_AmendmentAlreadyPending even expired + vm.prank(authority); + vm.expectRevert(); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + } + +// function testAuditModifiedCalldataProposal() public { +// // template from testCreateSetWithThreeTokenOptionsAndChangeExercisePrice +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// vm.expectRevert(); +// controller.updateExerciseOrRepurchasePrice(allocation1, 999999999999999999999e18); +// +// // Bypass MetaVesTController_AmendmentNeitherMutualNorMajorityConsented +// vm.prank(authority); +// bytes memory p = abi.encodeWithSelector(msgSig, allocation1, 999999999999999999999e18, 2e18); +// address(controller).call(p); +// +// console.log('Modified excercise price: ', TokenOptionAllocation(allocation1).exercisePrice()); +// } + + function testAuditConsentToMetavestAmendmentInFlavor() public { + // template from testRemoveMilestone + address vestingAllocation = createDummyVestingAllocation(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, false); + // emit MetaVesTController_AmendmentConsentUpdated(msgSig: 0x75b89e4f00000000000000000000000000000000000000000000000000000000, grantee: ECAdd: [0x0000000000000000000000000000000000000006], inFavor: false) + console.log("expected inFavor: false"); + (,,bool inFavor) = controller.functionToGranteeToAmendmentPending(selector, vestingAllocation); + console.log("output: ", inFavor); + assertEq(inFavor, false); + + } + + function test_RevertIf_AuditProposeMajorityMetavestAmendmentNewGranteeDuringProposal() public { + // template from testProposeMajorityMetavestAmendment + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + address mockAllocation4 = createDummyVestingAllocation(); + address mockAllocation5 = createDummyVestingAllocation(); + address mockAllocation6 = createDummyVestingAllocation(); + address mockAllocation7 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.startPrank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation4); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation5); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation6); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + controller.addMetaVestToSet("testSet", mockAllocation7); + vm.stopPrank(); + + vm.startPrank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation5, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation6, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(mockAllocation7, "testSet", msgSig, true); + vm.stopPrank(); + + (uint256 totalVotingPower, uint256 currentVotingPower,,,) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + console.log("totalVotingPower: ", totalVotingPower); + console.log("currentVotingPower: ", currentVotingPower); + } + + function testCreateSetAddVestingThenRemoveSet() public { + + // template from testCreateSetAddVestingThenRemoveSet + address allocation1 = createDummyVestingAllocation(); + address allocation2 = createDummyVestingAllocation(); + address allocation3 = createDummyVestingAllocation(); + + vm.startPrank(authority); + controller.createSet("testSetB"); + controller.addMetaVestToSet("testSetB", allocation1); + controller.addMetaVestToSet("testSetB", allocation2); + controller.addMetaVestToSet("testSetB", allocation3); + controller.removeSet("testSetB"); + controller.createSet("testSetB"); + vm.stopPrank(); + + } + +} \ No newline at end of file diff --git a/test/AuditBaseC.t.sol b/test/AuditBaseC.t.sol new file mode 100644 index 0000000..b466e23 --- /dev/null +++ b/test/AuditBaseC.t.sol @@ -0,0 +1,61 @@ +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; + +import "../test/controller.t.sol"; + +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract Audit is MetaVestControllerTest { + + function testAuditTerminateFailAfterWithdrawFixCheck() public { + // template from testTerminateVestAndRecoverSlowUnlock + address vestingAllocation = createDummyVestingAllocationSlowUnlock(); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.startPrank(grantee); + skip(25 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 1000e18 + 100e18 + 25 * 5e18, "Unexpected amount after cliff and milestone"); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + skip(25 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 25 * 5e18, "Unexpected amount after the second vesting period"); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + skip(1200 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), (10e18 - 5e18) * 50, "Unexpected amount after termination"); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + +// function testAuditTerminateFailAfterWithdrawFixCheckOptions() public { +// // template from testTerminateVestAndRecoverSlowUnlock +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// VestingAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 5 seconds); +// vm.startPrank(grantee); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.warp(block.timestamp + 5 seconds); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// vm.warp(block.timestamp + 5 seconds); +// controller.terminateMetavestVesting(vestingAllocation); +// +// vm.startPrank(grantee); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// vm.warp(block.timestamp + 365 days); +// +// vm.prank(authority); +// TokenOptionAllocation(vestingAllocation).recoverForfeitTokens(); +// //check balance of the vesting contract +// assertEq(token.balanceOf(vestingAllocation), 0); +// } +} \ No newline at end of file diff --git a/test/AuditBaseC3.t.sol b/test/AuditBaseC3.t.sol new file mode 100644 index 0000000..31f356e --- /dev/null +++ b/test/AuditBaseC3.t.sol @@ -0,0 +1,88 @@ +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; + +import "../test/controller.t.sol"; + +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract Audit is MetaVestControllerTest { + + function testAuditTerminateVestAndRecovers() public { + // template from testTerminateVestAndRecovers + address vestingAllocation = createDummyVestingAllocation(); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000e18, + unlockOnCompletion: false, + complete: false, + conditionContracts: new address[](0) + }); + vm.prank(authority); + controller.addMetavestMilestone(vestingAllocation, milestones[0]); + VestingAllocation(vestingAllocation).confirmMilestone(1); + + skip(50 seconds); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + assertEq(VestingAllocation(vestingAllocation).getVestedTokenAmount(), 100e18 + 1000e18 + 10e18 * 50, "Unexpected vested amount after termination"); + assertEq(VestingAllocation(vestingAllocation).getUnlockedTokenAmount(), 100e18 + 5e18 * 50 + 5e18 * 50, "Unexpected unlocked amount after termination"); + + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + // assertEq(token.balanceOf(vestingAllocation), 0); + } + +// function test_RevertIf_AuditRounding() public { +// // template from testConfirmingMilestoneTokenOption +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// +// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e6)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e11)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(9.99e11)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e12)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1.1e12)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e13)); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1.1e12); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// +// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } +// +// function testAuditExcercisePrice() public { +// // template from testConfirmingMilestoneTokenOption +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// +// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e18); +// +// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } +} diff --git a/test/amendement.t.sol b/test/amendement.t.sol new file mode 100644 index 0000000..2edf871 --- /dev/null +++ b/test/amendement.t.sol @@ -0,0 +1,917 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/BaseAllocation.sol"; +import "../src/RestrictedTokenAllocation.sol"; +import "../src/interfaces/IAllocationFactory.sol"; +import "../src/VestingAllocationFactory.sol"; +import "../src/TokenOptionFactory.sol"; +import "../src/RestrictedTokenFactory.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "./lib/MetaVesTControllerTestBase.sol"; + +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract MetaVestControllerTest is MetaVesTControllerTestBase { + address public authority = guardianSafe; + address public dao = guardianSafe; + address public grantee = alice; + + bytes32 salt = keccak256("MetaVestControllerTest"); + uint256 cap = 2000 ether; + uint48 cappedMinterStartTime = uint48(block.timestamp); // Minter start now + uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 1600); // Minter expired 1600 seconds after start + + address public vestingAllocation; + + function setUp() public { + vm.startPrank(deployer); + + // Deploy MetaVesT controller + + vestingAllocationFactory = new VestingAllocationFactory(); + + controller = new metavestController{salt: salt}( + guardianSafe, + guardianSafe, + address(vestingAllocationFactory) + ); + + // Deploy ZK Capped Minter v2 + + zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + cap, + cappedMinterStartTime, + cappedMinterExpirationTime, + uint256(salt) + )); + + vm.stopPrank(); + + vm.startPrank(guardianSafe); + controller.setZkCappedMinter(address(zkCappedMinter)); + controller.createSet("testSet"); + vm.stopPrank(); + + vestingAllocation = createDummyVestingAllocation(); + } + + function testProposeMetavestAmendment() public { + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); + + vm.prank(authority); + controller.proposeMetavestAmendment(address(vestingAllocation), msgSig, callData); + + (bool isPending, bytes32 dataHash, bool inFavor) = controller.functionToGranteeToAmendmentPending(msgSig, address(vestingAllocation)); + + assertTrue(isPending); + assertEq(dataHash, keccak256(callData)); + assertFalse(inFavor); + } + + function test_RevertIf_ProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + address mockAllocation4 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation4); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + //log the current withdrawable + console.log(VestingAllocation(mockAllocation2).getAmountWithdrawable()); + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(mockAllocation2, true); + } + + function testQuickProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + address mockAllocation4 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation4); + vm.warp(block.timestamp + 15 seconds); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.startPrank(grantee); + //log the current withdrawable + console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); + + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); + vm.stopPrank(); + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true); + } + + +// function testMajorityPowerMetavestAmendment() public { +// address mockAllocation2 = createDummyTokenOptionAllocation(); +// address mockAllocation3 = createDummyTokenOptionAllocation(); +// address mockAllocation4 = createDummyTokenOptionAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// controller.addMetaVestToSet("testSet", mockAllocation4); +// vm.warp(block.timestamp + 1 days); +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +// vm.stopPrank(); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } + +// function test_RevertIf_MajorityPowerMetavestAmendment() public { +// address mockAllocation2 = createDummyTokenOptionAllocation(); +// address mockAllocation3 = createDummyTokenOptionAllocation(); +// address mockAllocation4 = createDummyTokenOptionAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// controller.addMetaVestToSet("testSet", mockAllocation4); +// vm.warp(block.timestamp + 1 days); +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +// vm.stopPrank(); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } + + function testProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation3, true); + } + + function testProposeMajorityMetavestAmendmentReAdd() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation3, true); + + vm.prank(authority); + controller.removeMetaVestFromSet("testSet", mockAllocation3); + // vm.prank(authority); + // controller.updateMetavestTransferability(mockAllocation3, true); + vm.warp(block.timestamp + 90 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation3, true); + } + + function test_RevertIf_NoPassProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + address mockAllocation3 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation3); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(mockAllocation2, true); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(mockAllocation3, true); + } + + function testVoteOnMetavestAmendment() public { + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); + + (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); + + } + + function test_RevertIf_VoteOnMetavestAmendmentTwice() public { + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.startPrank(grantee); + controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AlreadyVoted.selector)); + controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); + vm.stopPrank(); + } + + function testSetManagement() public { + vm.startPrank(authority); + + // Test creating a new set + controller.createSet("newSet"); + + // Test adding a MetaVest to a set + controller.addMetaVestToSet("newSet", address(vestingAllocation)); + + + // Test removing a MetaVest from a set + controller.removeMetaVestFromSet("newSet", address(vestingAllocation)); + + + // Test removing a set + controller.removeSet("newSet"); + + + vm.stopPrank(); + } + + function test_RevertIf_CreateDuplicateSet() public { + vm.startPrank(authority); + controller.createSet("duplicateSet"); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetAlreadyExists.selector)); + controller.createSet("duplicateSet"); + vm.stopPrank(); + } + + function test_RevertIf_NonAuthorityCreateSet() public { + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); + controller.createSet("unauthorizedSet"); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation() internal returns (address) { + return createDummyVestingAllocation(""); // Expect no reverts + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation(bytes memory expectRevertData) internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 100 ether, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + return controller.createMetavest(contractIdAlice); + } + +// function createDummyTokenOptionAllocation() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 1100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// ); +// } +// +// function createDummyRestrictedTokenAward() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 1100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// +// ); +// } + + //write a test for every consentcheck function in metavest controller + function testConsentCheck() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(allocation, true); + } + + function test_RevertIf_ConsentCheck() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(allocation, true); + } + + function test_RevertIf_ConsentCheckNoProposal() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); + } + + function test_RevertIf_ConsentCheckNoVote() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.updateMetavestTransferability(allocation, true); + } + + function test_RevertIf_ConsentCheckNoUpdate() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); + } + + function test_RevertIf_ConsentCheckNoVoteUpdate() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); + controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); + } + +// function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +// +// // Check that the exercise price was updated +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +// } + +// function test_RevertIf_CreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// //vm.prank(grantee); +// // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// // vm.prank(grantee); +// // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +// +// // Check that the exercise price was updated +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +// } + + function test_RevertIf_consentToNoPendingAmendment() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_NoPendingAmendment.selector, msgSig, allocation)); + controller.consentToMetavestAmendment(allocation, msgSig, true); + } + +// function testEveryUpdateAmendmentFunction() public { +// address allocation = createDummyTokenOptionAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(allocation, true); +// +// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); +// +// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 0); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.removeMetavestMilestone(allocation, 0); +// +// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestUnlockRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); +// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); +// } + +// function testEveryUpdateAmendmentFunctionRestricted() public { +// address allocation = createDummyRestrictedTokenAward(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(allocation, true); +// +// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); +// +// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 0); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.removeMetavestMilestone(allocation, 0); +// +// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestUnlockRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); +// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); +// } + + function testEveryUpdateAmendmentFunctionVesting() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(allocation, true); + + msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + callData = abi.encodeWithSelector(msgSig, allocation, 0); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.removeMetavestMilestone(allocation, 0); + + msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestUnlockRate(allocation, 20e18); + + msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(allocation, 20e18); + + msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); + callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); + } + + function test_RevertIf_EveryUpdateAmendmentFunctionVesting() public { + address allocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(allocation, true); + + msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + callData = abi.encodeWithSelector(msgSig, allocation, 0); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.removeMetavestMilestone(allocation, 0); + + msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestUnlockRate(allocation, 20e18); + + msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + callData = abi.encodeWithSelector(msgSig, allocation, 20e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(allocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(allocation, 20e18); + + msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); + callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); + + vm.prank(authority); + controller.proposeMetavestAmendment(allocation, msgSig, callData); + + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); + controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); + } +} diff --git a/test/controller.t.sol b/test/controller.t.sol new file mode 100644 index 0000000..bbcc8e6 --- /dev/null +++ b/test/controller.t.sol @@ -0,0 +1,1470 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import "../src/RestrictedTokenAllocation.sol"; +import "../src/RestrictedTokenFactory.sol"; +import "../src/TokenOptionAllocation.sol"; +import "../src/TokenOptionFactory.sol"; +import "../src/VestingAllocation.sol"; +import "../src/VestingAllocationFactory.sol"; +import "../src/interfaces/IAllocationFactory.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "./lib/MetaVesTControllerTestBase.sol"; +import "./mocks/MockCondition.sol"; + +contract MetaVestControllerTest is MetaVesTControllerTestBase { + address authority = guardianSafe; + address dao = guardianSafe; + address grantee = alice; + address transferee = address(0x101); + + // Parameters + bytes32 salt = keccak256("MetaVesTControllerTest"); + uint256 cap = 2000 ether; + uint48 cappedMinterStartTime = uint48(block.timestamp); // Minter start now + uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 1600); // Minter expired 1600 seconds after start + + function setUp() public { + vm.startPrank(deployer); + + // Deploy MetaVesT controller + + vestingAllocationFactory = new VestingAllocationFactory(); + + controller = new metavestController{salt: salt}( + guardianSafe, + guardianSafe, + address(vestingAllocationFactory) + ); + + // Deploy ZK Capped Minter v2 + + zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + cap, + cappedMinterStartTime, + cappedMinterExpirationTime, + uint256(salt) + )); + + vm.stopPrank(); + + vm.startPrank(guardianSafe); + controller.setZkCappedMinter(address(zkCappedMinter)); + controller.createSet("testSet"); + vm.stopPrank(); + } + + function testCreateVestingAllocation() public { + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 60 ether, + vestingCliffCredit: 30 ether, + unlockingCliffCredit: 30 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0) + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + // Anyone can create MetaVesT (per agreements) to start vesting + VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); + + // Grantees should be able to withdraw all remaining tokens after sufficient time passed + skip(61); + _granteeWithdrawAndAsserts(vestingAllocationAlice, 60 ether, "Alice full"); + } + +// function testCreateTokenOptionAllocation() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// address tokenOptionAllocation = controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// ); +// +// assertEq(zkToken.balanceOf(address(tokenOptionAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// //assertEq(controller.tokenOptionAllocations(grantee, 0), tokenOptionAllocation); +// } + +// function testCreateRestrictedTokenAward() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// address restrictedTokenAward = controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// +// ); +// +// assertEq(zkToken.balanceOf(address(restrictedTokenAward)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); +// } + + function testUpdateTransferability() public { + uint256 startTimestamp = block.timestamp; + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + //compute msg.data for updateMetavestTransferability(vestingAllocation, true) + bytes4 selector = controller.updateMetavestTransferability.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, true); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, true); + vm.prank(authority); + controller.updateMetavestTransferability(vestingAllocation, true); + vm.prank(grantee); + VestingAllocation(vestingAllocation).transferRights(transferee); + vm.prank(transferee); + VestingAllocation(vestingAllocation).confirmTransfer(); + uint256 newTimestamp = startTimestamp + 100; // 101 + vm.warp(newTimestamp); + skip(10); + vm.prank(transferee); + uint256 balance = VestingAllocation(vestingAllocation).getAmountWithdrawable(); + + + //warp ahead 100 blocks + + vm.prank(transferee); + VestingAllocation(vestingAllocation).withdraw(balance); + + // assertTrue(BaseAllocation(vestingAllocation).transferable()); + } + + function testGetGovPower() public { + address vestingAllocation = createDummyVestingAllocation(); + BaseAllocation(vestingAllocation).getGoverningPower(); + } + + function testProposeMajorityMetavestAmendment() public { + address vestingAllocation = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", vestingAllocation); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + vm.prank(grantee); + controller.voteOnMetavestAmendment(vestingAllocation, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(vestingAllocation, true); + } + + + function test_RevertIf_ReProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + vm.warp(block.timestamp + 30 days); + /* + vm.prank(grantee); + controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + + vm.prank(authority); + controller.updateMetavestTransferability(mockAllocation2, true);*/ + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + } + + function testReProposeMajorityMetavestAmendment() public { + address mockAllocation2 = createDummyVestingAllocation(); + bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); + bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + + vm.prank(authority); + controller.addMetaVestToSet("testSet", mockAllocation2); + vm.warp(block.timestamp + 1 days); + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + vm.warp(block.timestamp + 30 days); + + vm.prank(authority); + controller.cancelExpiredMajorityMetavestAmendment("testSet", msgSig); + + vm.prank(authority); + controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); + + } + + function test_RevertIf_RemoveNonExistantMetaVestFromSet() public { + address mockAllocation2 = createDummyVestingAllocation(); + vm.startPrank(authority); + // controller.createSet("testSet"); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_MetaVestNotInSet.selector)); + controller.removeMetaVestFromSet("testSet", mockAllocation2); + } + + +// function testUpdateExercisePrice() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// +// //compute msg.data for updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18) +// bytes4 selector = controller.updateExerciseOrRepurchasePrice.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, tokenOptionAllocation, 2e18); +// +// controller.proposeMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, msgData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, true); +// +// controller.updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18); +// +// assertEq(TokenOptionAllocation(tokenOptionAllocation).exercisePrice(), 2e18); +// } + + function testRemoveMilestone() public { + address vestingAllocation = createDummyVestingAllocation(); + //create array of addresses and include vestingAllocation address + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); + vm.prank(grantee); + //consent to amendment for the removemetavestmilestone method sig function consentToMetavestAmendment(address _metavest, bytes4 _msgSig, bool _inFavor) external { + controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.prank(authority); + controller.removeMetavestMilestone(vestingAllocation, 0); + + //BaseAllocation.Milestone memory milestone = BaseAllocation(vestingAllocation).milestones(0); + //assertEq(milestone.milestoneAward, 0); + } + + function testAddMilestone() public { + address vestingAllocation = createDummyVestingAllocation(); + + BaseAllocation.Milestone memory newMilestone = BaseAllocation.Milestone({ + milestoneAward: 50e18, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + vm.prank(authority); + controller.addMetavestMilestone(vestingAllocation, newMilestone); + + // BaseAllocation.Milestone memory addedMilestone = BaseAllocation(vestingAllocation).milestones[0]; + // assertEq(addedMilestone.milestoneAward, 50e18); + } + + function testUpdateUnlockRate() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(authority); + controller.updateMetavestUnlockRate(vestingAllocation, 20e18); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 20e18); + } + + function testUpdateUnlockRateZeroEmergency() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(authority); + controller.updateMetavestUnlockRate(vestingAllocation, 0); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 0); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.prank(authority); + controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); + updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 1e20); + } + + function test_RevertIf_UpdateUnlockRateZeroEmergency() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); + vm.prank(authority); + controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 10e18); + } + + function test_RevertIf_UpdateUnlockRateZeroEmergencyTerminated() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestUnlockRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); + vm.prank(authority); + controller.updateMetavestUnlockRate(vestingAllocation, 0); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 0); + + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); + vm.prank(authority); + controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); + updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 0); + } + + function testUpdateVestingRate() public { + address vestingAllocation = createDummyVestingAllocation(); + address[] memory addresses = new address[](1); + addresses[0] = vestingAllocation; + bytes4 selector = controller.updateMetavestVestingRate.selector; + bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, msgData); + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, true); + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 20e18); + + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.vestingRate, 20e18); + } + +// function testUpdateStopTimes() public { +// +// address vestingAllocation = createDummyRestrictedTokenAward(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, uint48(block.timestamp + 500 days)); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, msgData); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, true); +// uint48 newShortStopTime = uint48(block.timestamp + 500 days); +// +// controller.updateMetavestStopTimes(vestingAllocation, newShortStopTime); +// } + + function testTerminateVesting() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + + assertTrue(BaseAllocation(vestingAllocation).terminated()); + } + +// function testRepurchaseTokens() public { +// uint256 startingBalance = paymentToken.balanceOf(grantee); +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 repurchaseAmount = 5e18; +// uint256 snapshot = token.balanceOf(authority); +// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); +// controller.terminateMetavestVesting(restrictedTokenAward); +// paymentToken.approve(address(restrictedTokenAward), payment); +// vm.warp(block.timestamp + 20 days); +// vm.prank(authority); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +// +// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +// +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +// } + +// function testRepurchaseTokensFuture() public { +// uint256 startingBalance = paymentToken.balanceOf(grantee); +// address restrictedTokenAward = createDummyRestrictedTokenAwardFuture(); +// +// uint256 snapshot = token.balanceOf(authority); +// +// controller.terminateMetavestVesting(restrictedTokenAward); +// uint256 repurchaseAmount = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); +// paymentToken.approve(address(restrictedTokenAward), payment); +// vm.warp(block.timestamp + 20 days); +// vm.prank(authority); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +// +// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +// +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); +// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +// +// } + + function testTerminateTokensFuture() public { + address vestingAllocation = createDummyVestingAllocationLargeFuture(); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + } + + function testUpdateAuthority() public { + address newAuthority = address(0x4); + vm.prank(authority); + controller.initiateAuthorityUpdate(newAuthority); + + vm.prank(newAuthority); + controller.acceptAuthorityRole(); + + assertEq(controller.authority(), newAuthority); + } + + function testUpdateDao() public { + address newDao = address(0x5); + + vm.prank(dao); + controller.initiateDaoUpdate(newDao); + + vm.prank(newDao); + controller.acceptDaoRole(); + + assertEq(controller.dao(), newDao); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation() internal returns (address) { + return createDummyVestingAllocation(""); // Expect no reverts + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation(bytes memory expectRevertData) internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000 ether, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + return controller.createMetavest(contractIdAlice); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationNoUnlock() internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000 ether, + unlockOnCompletion: false, + complete: false, + conditionContracts: new address[](0) + }); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + return controller.createMetavest(contractIdAlice); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationSlowUnlock() internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 1000 ether, + unlockOnCompletion: true, + complete: false, + conditionContracts: new address[](0) + }); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 5 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + return controller.createMetavest(contractIdAlice); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationLarge() internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 0 ether, + unlockingCliffCredit: 0 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + return controller.createMetavest(contractIdAlice); + } + + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationLargeFuture() internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 0 ether, + unlockingCliffCredit: 0 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp + 2000), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp + 2000) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + return controller.createMetavest(contractIdAlice); + } + +// function createDummyTokenOptionAllocation() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 1000e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 2000e18); +// +// return controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 5e17, +// address(paymentToken), +// 1 days, +// 0 +// ); +// } + + +// function createDummyRestrictedTokenAward() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 1000e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 2100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 1 days, +// 0 +// +// ); +// } +// +// function createDummyRestrictedTokenAwardFuture() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp+1000), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp+1000) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 1000e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 2100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 1 days, +// 0 +// +// ); +// } + + + function testGetMetaVestType() public { + address vestingAllocation = createDummyVestingAllocation(); +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// address restrictedTokenAward = createDummyRestrictedTokenAward(); + + assertEq(controller.getMetaVestType(vestingAllocation), 1); +// assertEq(controller.getMetaVestType(tokenOptionAllocation), 2); +// assertEq(controller.getMetaVestType(restrictedTokenAward), 3); + } + +// function testWithdrawFromController() public { +// uint256 amount = 100e18; +// token.transfer(address(controller), amount); +// +// uint256 initialBalance = token.balanceOf(authority); +// controller.withdrawFromController(address(token)); +// uint256 finalBalance = token.balanceOf(authority); +// +// assertEq(finalBalance - initialBalance, amount); +// assertEq(token.balanceOf(address(controller)), 0); +// } + + function test_RevertIf_CreateMetavestWithZeroAddress() public { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); + + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _signAndCreateContract( + guardianSafe, + alice, // = grantee + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(0), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ZeroAddress.selector)); + controller.createMetavest(contractIdAlice); + } + + function testTerminateVestAndRecovers() public { + address vestingAllocation = createDummyVestingAllocation(); + uint256 snapshot = zkToken.balanceOf(authority); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.warp(block.timestamp + 50 seconds); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(authority), 0); + } + + function testTerminateVestAndRecoverSlowUnlock() public { + address vestingAllocation = createDummyVestingAllocationSlowUnlock(); + uint256 snapshot = zkToken.balanceOf(authority); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.warp(block.timestamp + 25 seconds); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.warp(block.timestamp + 25 seconds); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(vestingAllocation), 0); + } + + function testTerminateRecoverAll() public { + address vestingAllocation = createDummyVestingAllocationLarge(); + uint256 snapshot = zkToken.balanceOf(authority); + vm.warp(block.timestamp + 25 seconds); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(authority), 0); + } + + function testTerminateRecoverChunksBefore() public { + address vestingAllocation = createDummyVestingAllocationLarge(); + uint256 snapshot = zkToken.balanceOf(authority); + vm.warp(block.timestamp + 25 seconds); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + vm.warp(block.timestamp + 25 seconds); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + assertEq(zkToken.balanceOf(authority), 0); + } + +// function testConfirmingMilestoneRestrictedTokenAllocation() public { +// address vestingAllocation = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// RestrictedTokenAward(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } +// +// function testConfirmingMilestoneTokenOption() public { +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } + + function testUnlockMilestoneNotUnlocked() public { + address vestingAllocation = createDummyVestingAllocationNoUnlock(); + uint256 snapshot = zkToken.balanceOf(authority); + VestingAllocation(vestingAllocation).confirmMilestone(0); + vm.warp(block.timestamp + 50 seconds); + vm.startPrank(grantee); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.warp(block.timestamp + 1050 seconds); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + +// function testTerminateTokenOptionAndRecover() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// vm.prank(grantee); +// ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); +// vm.prank(grantee); +// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18); +// controller.terminateMetavestVesting(tokenOptionAllocation); +// vm.startPrank(grantee); +// vm.warp(block.timestamp + 1 days + 25 seconds); +// assertEq(TokenOptionAllocation(tokenOptionAllocation).getAmountExercisable(), 0); +// TokenOptionAllocation(tokenOptionAllocation).withdraw(TokenOptionAllocation(tokenOptionAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// assertEq(token.balanceOf(tokenOptionAllocation), 0); +// vm.warp(block.timestamp + 365 days); +// vm.prank(authority); +// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +// } + +// function testTerminateEarlyTokenOptionAndRecover() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 5 seconds); +// // vm.prank(grantee); +// /* ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); +// vm.prank(grantee); +// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18);*/ +// controller.terminateMetavestVesting(tokenOptionAllocation); +// vm.warp(block.timestamp + 365 days); +// vm.prank(authority); +// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +// } + + +// function testTerminateRestrictedTokenAwardAndRecover() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// vm.warp(block.timestamp + 20 days); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// assertEq(token.balanceOf(restrictedTokenAward), 0); +// assertEq(paymentToken.balanceOf(restrictedTokenAward), 0); +// } + +// function testChangeVestingAndUnlockingRate() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// +// bytes4 msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestUnlockRate(restrictedTokenAward, 50e18); +// +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(restrictedTokenAward, 50e18); +// +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// +// } + +// function testZeroReclaim() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// vm.warp(block.timestamp + 15 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// //create call data to propose setting vesting to 0 +// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(restrictedTokenAward, 0); +// +// vm.startPrank(authority); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.warp(block.timestamp + 155 days); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// vm.stopPrank(); +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); +// } + + function testZeroReclaimVesting() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.warp(block.timestamp + 15 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + //create call data to propose setting vesting to 0 + bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 0); + + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 0); + + vm.startPrank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.stopPrank(); + } + + function testSlightReduc() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + //create call data to propose setting vesting to 0 + bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 80e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 80e18); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.stopPrank(); + vm.warp(block.timestamp + 155 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + + function testLargeReduc() public { + address vestingAllocation = createDummyVestingAllocation(); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + //create call data to propose setting vesting to 0 + bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 10e18); + + vm.prank(authority); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); + + vm.prank(grantee); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); + + vm.prank(authority); + controller.updateMetavestVestingRate(vestingAllocation, 10e18); + vm.warp(block.timestamp + 5 seconds); + vm.startPrank(authority); + controller.terminateMetavestVesting(vestingAllocation); + vm.stopPrank(); + vm.warp(block.timestamp + 155 seconds); + vm.startPrank(grantee); + RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + vm.stopPrank(); + } + +// function testLargeReducOption() public { +// address restrictedTokenAward = createDummyTokenOptionAllocation(); +// vm.warp(block.timestamp + 5 seconds); +// vm.startPrank(grantee); +// //approve amount to exercise by getting amount to exercise and price +// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); +// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// //create call data to propose setting vesting to 0 +// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); +// vm.warp(block.timestamp + 5 seconds); +// vm.startPrank(authority); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.stopPrank(); +// vm.warp(block.timestamp + 155 seconds); +// vm.startPrank(grantee); +// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); +// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// console.log(token.balanceOf(restrictedTokenAward)); +// } + + + +// function testReclaim() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// vm.warp(block.timestamp + 15 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// +// vm.startPrank(authority); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.warp(block.timestamp + 155 days); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// vm.stopPrank(); +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); +// } + + + +// function test_RevertIf_UpdateExercisePriceForVesting() public { +// address vestingAllocation = createDummyVestingAllocation(); +// controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); +// } + +// function test_RevertIf_RepurchaseTokensAfterExpiry() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// +// // Fast forward time to after the short stop date +// vm.warp(block.timestamp + 366 days); +// +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +// } + +// function test_RevertIf_RepurchaseTokensInsufficientAllowance() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// +// // Not approving any tokens +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +// } + + function test_RevertIf_InitiateAuthorityUpdateNonAuthority() public { + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); + controller.initiateAuthorityUpdate(address(0x5678)); + } + + function test_RevertIf_AcceptAuthorityRoleNonPendingAuthority() public { + vm.prank(authority); + controller.initiateAuthorityUpdate(address(0x5678)); + + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingAuthority.selector)); + controller.acceptAuthorityRole(); + } + + function test_RevertIf_InitiateDaoUpdateNonDao() public { + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); + controller.initiateDaoUpdate(address(0x5678)); + } + + function test_RevertIf_AcceptDaoRoleNonPendingDao() public { + vm.prank(dao); + controller.initiateDaoUpdate(address(0x5678)); + + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingDao.selector)); + controller.acceptDaoRole(); + } + + function testUpdateFunctionCondition() public { + bytes4 functionSig = bytes4(keccak256("testFunction()")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + + assertEq(controller.functionToConditions(functionSig, 0), address(condition)); + } + + function test_RevertIf_UpdateFunctionConditionNonDao() public { + bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); + address condition = address(0x1234); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); + controller.updateFunctionCondition(condition, functionSig); + } + + + function testRemoveFunctionCondition() public { + bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + assert(controller.functionToConditions(functionSig, 0) == address(condition)); + vm.prank(dao); + controller.removeFunctionCondition(address(condition), functionSig); + } + + function test_RevertIf_CheckFunctionCondition() public { + bytes4 functionSig = bytes4(keccak256("createMetavest(bytes32)")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + assert(controller.functionToConditions(functionSig, 0) == address(condition)); + // create a dummy metavest + createDummyVestingAllocation( + abi.encodeWithSelector(metavestController.MetaVesTController_ConditionNotSatisfied.selector, condition) // Expected revert + ); + } + + function test_RevertIf_AddDuplicateCondition() public { + bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); + /* constructor( + address[] memory _signers, + uint256 _threshold, + Logic _logic + ) */ + address[] memory signers = new address[](2); + signers[0] = address(0x1); + signers[1] = address(0x2); + SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); + + vm.prank(dao); + controller.updateFunctionCondition(address(condition), functionSig); + assert(controller.functionToConditions(functionSig, 0) == address(condition)); + vm.prank(dao); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_DuplicateCondition.selector)); + controller.updateFunctionCondition(address(condition), functionSig); + } + + function test_RevertIf_ExceedCap() public { + // Add a large grant that exceeds the cap + bytes32 contractIdChad = _signAndCreateContract( + guardianSafe, + chad, + chadPrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 2001 ether, + vestingCliffCredit: 2001 ether, + unlockingCliffCredit: 2001 ether, + vestingRate: 0, + vestingStartTime: 0, + unlockRate: 0, + unlockStartTime: 0 + }), + new BaseAllocation.Milestone[](0) + ); + VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); + + vm.prank(chad); + vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(vestingAllocationChad), 2001 ether)); + vestingAllocationChad.withdraw(2001 ether); + } + + function test_RevertIf_NotAuthority() public { + // Non Guardian SAFE should not be able to accept agreement and create contract + _signAndCreateContract( + deployer, // Not authority + alice, + alicePrivateKey, + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert + ); + } + + function test_RevertIf_IncorrectAgreementSignature() public { + // Register Alice with someone else's signature should fail + _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use someone else to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert + ); + } + + function test_DelegateSignature() public { + // Alice to delegate to Bob + vm.prank(alice); + controller.setDelegation(bob, block.timestamp + 60); + assertTrue(controller.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); + + // Bob should be able to sign for Alice now + bytes32 contractId = _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use Bob to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0) + ); + metavestController.AgreementData memory agreement = controller.getAgreement(contractId); + assertEq(agreement.signedData.grantee, alice, "Alice should be the grantee"); + + // Wait until expiry + skip(61); + + // Bob should no longer be able to sign for Alice + assertFalse(controller.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); + _signAndCreateContract( + guardianSafe, + alice, + bobPrivateKey, // Use Bob to sign + "ipfs.io/ipfs/[cid]", + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert + ); + } +} From aa4a72eeb5697f0995ee743b1cbb04463df78099 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 6 Aug 2025 16:59:53 -0700 Subject: [PATCH 23/68] test: update all to specs --- src/MetaVesTController.sol | 2 +- test/ZkGuardianCompensation.t.sol | 55 ++--------- test/amendement.t.sol | 17 +++- test/controller.t.sol | 124 ++++++++++++++++-------- test/lib/MetaVesTControllerTestBase.sol | 64 +++++++++++- 5 files changed, 163 insertions(+), 99 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index dd06a18..19efb4a 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -312,8 +312,8 @@ contract metavestController is SafeTransferLib { } function proposeAndSignDeal( - uint256 salt, bytes32 templateId, + uint256 salt, metavestType _metavestType, address grantee, address recipient, diff --git a/test/ZkGuardianCompensation.t.sol b/test/ZkGuardianCompensation.t.sol index fcbf0c6..ab3b29c 100644 --- a/test/ZkGuardianCompensation.t.sol +++ b/test/ZkGuardianCompensation.t.sol @@ -7,63 +7,21 @@ import "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; import "./lib/MetaVesTControllerTestBase.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; // Test by forge test --zksync --via-ir contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { // Parameters - bytes32 templateId = bytes32(uint256(123)); - bytes32 salt = keccak256("MetaLexZkSyncTest"); uint256 cap = 1e6 ether; // 1M ZK uint48 cappedMinterStartTime = 1756684800; // 2025/9/1 UTC uint48 cappedMinterExpirationTime = cappedMinterStartTime + 365 days * 2; // Expect to vest over an year with a margin of an extra year for withdrawal - function setUp() public { + function setUp() public override { // Assume deployment 7 days before the vesting starts vm.warp(cappedMinterStartTime - 7 days); - vm.startPrank(deployer); + MetaVesTControllerTestBase.setUp(); - // Deploy CyberAgreementRegistry and prepare templates - - // TODO who should be the owner of auth? - auth = new BorgAuth{salt: salt}(deployer); - registry = CyberAgreementRegistry(address(new ERC1967Proxy{salt: salt}( - address(new CyberAgreementRegistry{salt: salt}()), - abi.encodeWithSelector( - CyberAgreementRegistry.initialize.selector, - address(auth) - ) - ))); - - globalFields = new string[](13); - globalFields[0] = "governingJurisdiction"; // TODO do we need this? - globalFields[1] = "disputeResolution"; // TODO do we need this? - globalFields[2] = "metavestType"; - globalFields[3] = "grantee"; - globalFields[4] = "recipient"; - globalFields[5] = "tokenContract"; - globalFields[6] = "tokenStreamTotal"; - globalFields[7] = "vestingCliffCredit"; - globalFields[8] = "unlockingCliffCredit"; - globalFields[9] = "vestingRate"; - globalFields[10] = "vestingStartTime"; - globalFields[11] = "unlockRate"; - globalFields[12] = "unlockStartTime"; - partyFields = new string[](5); - partyFields[0] = "name"; - partyFields[1] = "evmAddress"; - partyFields[2] = "contactDetails"; // TODO do we need this? - partyFields[3] = "granteeType"; // TODO do we need this? - partyFields[4] = "granteeJurisdiction"; // TODO do we need this? - - registry.createTemplate( - templateId, - "ZkSyncGuardianCompensation", - agreementUri, - globalFields, - partyFields - ); + vm.startPrank(deployer); // Deploy MetaVesT controller @@ -148,6 +106,7 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { // Add new grantee for admin/tooling compensation bytes32 contractIdChad = _proposeAndSignDeal( templateId, + block.timestamp, // salt guardianSafe, chad, chadPrivateKey, @@ -174,6 +133,7 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { // Non Guardian SAFE should not be able to accept agreement and create contract _proposeAndSignDeal( templateId, + block.timestamp, // salt deployer, // Not authority alice, alicePrivateKey, @@ -198,6 +158,7 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { // Register Alice with someone else's signature should fail _proposeAndSignDeal( templateId, + block.timestamp, // salt guardianSafe, alice, bobPrivateKey, // Use someone else to sign @@ -227,6 +188,7 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { // Bob should be able to sign for Alice now bytes32 agreementId = _proposeAndSignDeal( templateId, + block.timestamp, // salt guardianSafe, alice, bobPrivateKey, // Use Bob to sign @@ -254,6 +216,7 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { assertFalse(registry.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); _proposeAndSignDeal( templateId, + block.timestamp, // salt guardianSafe, alice, bobPrivateKey, // Use Bob to sign @@ -279,6 +242,7 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { bytes32 contractIdAlice = _proposeAndSignDeal( templateId, + block.timestamp, // salt guardianSafe, alice, alicePrivateKey, @@ -300,6 +264,7 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { bytes32 contractIdBob = _proposeAndSignDeal( templateId, + block.timestamp, // salt guardianSafe, bob, bobPrivateKey, diff --git a/test/amendement.t.sol b/test/amendement.t.sol index 2edf871..89dd3b7 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -17,14 +17,17 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { address public dao = guardianSafe; address public grantee = alice; - bytes32 salt = keccak256("MetaVestControllerTest"); uint256 cap = 2000 ether; uint48 cappedMinterStartTime = uint48(block.timestamp); // Minter start now uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 1600); // Minter expired 1600 seconds after start address public vestingAllocation; - function setUp() public { + uint256 agreementSaltCounter = 0; + + function setUp() public override { + MetaVesTControllerTestBase.setUp(); + vm.startPrank(deployer); // Deploy MetaVesT controller @@ -34,6 +37,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { controller = new metavestController{salt: salt}( guardianSafe, guardianSafe, + address(registry), address(vestingAllocationFactory) ); @@ -360,11 +364,12 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { }); // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + agreementSaltCounter++, guardianSafe, alice, // = grantee alicePrivateKey, - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -375,7 +380,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockRate: 10 ether, unlockStartTime: uint48(block.timestamp) }), - milestones + milestones, + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions diff --git a/test/controller.t.sol b/test/controller.t.sol index bbcc8e6..e46c255 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -19,12 +19,13 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { address transferee = address(0x101); // Parameters - bytes32 salt = keccak256("MetaVesTControllerTest"); uint256 cap = 2000 ether; uint48 cappedMinterStartTime = uint48(block.timestamp); // Minter start now uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 1600); // Minter expired 1600 seconds after start - function setUp() public { + function setUp() public override { + MetaVesTControllerTestBase.setUp(); + vm.startPrank(deployer); // Deploy MetaVesT controller @@ -34,6 +35,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { controller = new metavestController{salt: salt}( guardianSafe, guardianSafe, + address(registry), address(vestingAllocationFactory) ); @@ -57,14 +59,15 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { } function testCreateVestingAllocation() public { - bytes32 contractIdAlice = _signAndCreateContract( + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, alice, alicePrivateKey, - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), - // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year tokenStreamTotal: 60 ether, vestingCliffCredit: 30 ether, unlockingCliffCredit: 30 ether, @@ -73,7 +76,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockRate: 1 ether, unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter }), - new BaseAllocation.Milestone[](0) + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions @@ -534,11 +539,12 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { }); // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, alice, // = grantee alicePrivateKey, - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -549,7 +555,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockRate: 10 ether, unlockStartTime: uint48(block.timestamp) }), - milestones + milestones, + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions @@ -574,11 +582,12 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { }); // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, alice, // = grantee alicePrivateKey, - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -589,7 +598,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockRate: 10 ether, unlockStartTime: uint48(block.timestamp) }), - milestones + milestones, + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions @@ -611,11 +622,12 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { }); // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, alice, // = grantee alicePrivateKey, - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -626,7 +638,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockRate: 5 ether, unlockStartTime: uint48(block.timestamp) }), - milestones + milestones, + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions @@ -642,11 +656,12 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, alice, // = grantee alicePrivateKey, - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -657,7 +672,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockRate: 10 ether, unlockStartTime: uint48(block.timestamp) }), - milestones + milestones, + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions @@ -673,11 +690,12 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, alice, // = grantee alicePrivateKey, - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -688,7 +706,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockRate: 10 ether, unlockStartTime: uint48(block.timestamp + 2000) }), - milestones + milestones, + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions @@ -831,11 +851,12 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); // Guardians to sign agreements and register on MetaVesTController - bytes32 contractIdAlice = _signAndCreateContract( + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, alice, // = grantee alicePrivateKey, - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(0), tokenStreamTotal: 1000 ether, @@ -846,7 +867,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockRate: 10 ether, unlockStartTime: uint48(block.timestamp) }), - milestones + milestones, + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions @@ -1348,11 +1371,12 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { function test_RevertIf_ExceedCap() public { // Add a large grant that exceeds the cap - bytes32 contractIdChad = _signAndCreateContract( + bytes32 contractIdChad = _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, chad, chadPrivateKey, - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 2001 ether, @@ -1363,7 +1387,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockRate: 0, unlockStartTime: 0 }), - new BaseAllocation.Milestone[](0) + new BaseAllocation.Milestone[](0), + "Chad", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); @@ -1374,11 +1400,12 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { function test_RevertIf_NotAuthority() public { // Non Guardian SAFE should not be able to accept agreement and create contract - _signAndCreateContract( + _proposeAndSignDeal( + templateId, + block.timestamp, // salt deployer, // Not authority alice, alicePrivateKey, - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 100 ether, @@ -1390,17 +1417,20 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter }), new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert ); } function test_RevertIf_IncorrectAgreementSignature() public { // Register Alice with someone else's signature should fail - _signAndCreateContract( + _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, alice, bobPrivateKey, // Use someone else to sign - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 100 ether, @@ -1412,22 +1442,25 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter }), new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert + "Alice", + cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert ); } function test_DelegateSignature() public { // Alice to delegate to Bob vm.prank(alice); - controller.setDelegation(bob, block.timestamp + 60); - assertTrue(controller.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); + registry.setDelegation(bob, block.timestamp + 60); + assertTrue(registry.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); // Bob should be able to sign for Alice now - bytes32 contractId = _signAndCreateContract( + bytes32 contractId = _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, alice, bobPrivateKey, // Use Bob to sign - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 100 ether, @@ -1438,21 +1471,24 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockRate: 1 ether, unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter }), - new BaseAllocation.Milestone[](0) + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - metavestController.AgreementData memory agreement = controller.getAgreement(contractId); - assertEq(agreement.signedData.grantee, alice, "Alice should be the grantee"); + metavestController.DealData memory deal = controller.getDeal(contractId); + assertEq(deal.grantee, alice, "Alice should be the grantee"); // Wait until expiry skip(61); // Bob should no longer be able to sign for Alice - assertFalse(controller.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); - _signAndCreateContract( + assertFalse(registry.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); + _proposeAndSignDeal( + templateId, + block.timestamp, // salt guardianSafe, alice, bobPrivateKey, // Use Bob to sign - "ipfs.io/ipfs/[cid]", BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 100 ether, @@ -1464,7 +1500,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter }), new BaseAllocation.Milestone[](0), - abi.encodeWithSelector(metavestController.MetaVesTController_SignatureVerificationFailed.selector) // Expected revert + "Alice", + cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert ); } } diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index f01b687..6df1c7b 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -5,6 +5,7 @@ import "forge-std/Test.sol"; import "../../src/MetaVesTController.sol"; import "../../src/VestingAllocationFactory.sol"; import "../../src/interfaces/zk-governance/IZkTokenV1.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreementUtils.sol"; @@ -31,6 +32,9 @@ contract MetaVesTControllerTestBase is Test { uint256 chadPrivateKey = 3; address chad = vm.addr(chadPrivateKey); + bytes32 salt = keccak256("MetaVesTControllerTestBase"); + + bytes32 templateId = bytes32(uint256(123)); string agreementUri = "ipfs.io/ipfs/[cid]"; string[] globalFields; string[] partyFields; @@ -42,6 +46,54 @@ contract MetaVesTControllerTestBase is Test { metavestController controller; + function setUp() public virtual { + vm.startPrank(deployer); + + // Deploy CyberAgreementRegistry and prepare templates + + // TODO who should be the owner of auth? + auth = new BorgAuth{salt: salt}(deployer); + registry = CyberAgreementRegistry(address(new ERC1967Proxy{salt: salt}( + address(new CyberAgreementRegistry{salt: salt}()), + abi.encodeWithSelector( + CyberAgreementRegistry.initialize.selector, + address(auth) + ) + ))); + + globalFields = new string[](13); + globalFields[0] = "governingJurisdiction"; // TODO do we need this? + globalFields[1] = "disputeResolution"; // TODO do we need this? + globalFields[2] = "metavestType"; + globalFields[3] = "grantee"; + globalFields[4] = "recipient"; + globalFields[5] = "tokenContract"; + globalFields[6] = "tokenStreamTotal"; + globalFields[7] = "vestingCliffCredit"; + globalFields[8] = "unlockingCliffCredit"; + globalFields[9] = "vestingRate"; + globalFields[10] = "vestingStartTime"; + globalFields[11] = "unlockRate"; + globalFields[12] = "unlockStartTime"; + + partyFields = new string[](5); + partyFields[0] = "name"; + partyFields[1] = "evmAddress"; + partyFields[2] = "contactDetails"; // TODO do we need this? + partyFields[3] = "granteeType"; // TODO do we need this? + partyFields[4] = "granteeJurisdiction"; // TODO do we need this? + + registry.createTemplate( + templateId, + "ZkSyncGuardianCompensation", + agreementUri, + globalFields, + partyFields + ); + + vm.stopPrank(); + } + function _granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) internal { address grantee = vestingAllocation.grantee(); uint256 balanceBefore = zkToken.balanceOf(grantee); @@ -53,6 +105,7 @@ contract MetaVesTControllerTestBase is Test { function _proposeAndSignDeal( bytes32 templateId, + uint256 agreementSalt, address authority, address grantee, uint256 granteePrivateKey, @@ -62,13 +115,14 @@ contract MetaVesTControllerTestBase is Test { uint256 expiry ) internal returns(bytes32) { return _proposeAndSignDeal( - templateId, authority, grantee, granteePrivateKey, allocation, milestones, partyName, expiry, + templateId, agreementSalt, authority, grantee, granteePrivateKey, allocation, milestones, partyName, expiry, "" // Not expecting revert ); } function _proposeAndSignDeal( bytes32 templateId, + uint256 agreementSalt, address authority, address grantee, uint256 granteePrivateKey, @@ -78,8 +132,6 @@ contract MetaVesTControllerTestBase is Test { uint256 expiry, bytes memory expectRevertData ) internal returns(bytes32) { - uint256 contractSalt = block.timestamp; - string[] memory globalValues = new string[](13); globalValues[0] = "test governingJurisdiction"; // TODO do we need this? globalValues[1] = "test disputeResolution"; // TODO do we need this? @@ -95,6 +147,8 @@ contract MetaVesTControllerTestBase is Test { globalValues[11] = vm.toString(allocation.unlockRate); // unlockRate globalValues[12] = vm.toString(allocation.unlockStartTime); // unlockStartTime + // TODO what to do with milestones, which could be of dynamic lengths + string[] memory partyValues = new string[](5); partyValues[0] = partyName; partyValues[1] = vm.toString(grantee); // evmAddress @@ -107,7 +161,7 @@ contract MetaVesTControllerTestBase is Test { bytes32 expectedContractId = keccak256( abi.encode( templateId, - contractSalt, + agreementSalt, globalValues, allParties ) @@ -131,8 +185,8 @@ contract MetaVesTControllerTestBase is Test { } vm.prank(authority); bytes32 contractId = controller.proposeAndSignDeal( - contractSalt, templateId, + agreementSalt, metavestController.metavestType.Vesting, grantee, grantee, From 2b5a6e1b781cb0612f14d0926101f3878b9ed62d Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 7 Aug 2025 10:32:06 -0700 Subject: [PATCH 24/68] feat: add recipient --- lib/zk-governance | 2 +- src/BaseAllocation.sol | 21 +- src/MetaVesTController.sol | 11 +- src/RestrictedTokenAllocation.sol | 463 +++++++++++++------------- src/RestrictedTokenFactory.sol | 51 +-- src/TokenOptionAllocation.sol | 445 +++++++++++++------------ src/TokenOptionFactory.sol | 51 +-- src/VestingAllocation.sol | 8 +- src/VestingAllocationFactory.sol | 3 +- src/interfaces/IAllocationFactory.sol | 1 + test/VestingAllocation.t.sol | 119 +++++++ test/amendement.t.sol | 2 +- test/controller.t.sol | 14 +- 13 files changed, 667 insertions(+), 524 deletions(-) create mode 100644 test/VestingAllocation.t.sol diff --git a/lib/zk-governance b/lib/zk-governance index 663418c..36f7d4e 160000 --- a/lib/zk-governance +++ b/lib/zk-governance @@ -1 +1 @@ -Subproject commit 663418c83bd6e0190976bad3c37374213d8c004f +Subproject commit 36f7d4e19e586fb539f4c8723e28e52b5864fb8e diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index 9d03056..d047695 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -136,9 +136,10 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ event MetaVesT_TransferabilityUpdated(address indexed grantee, bool isTransferable); event MetaVest_TransferRightsPending(address indexed grantee, address indexed pendingGrantee); event MetaVesT_TransferredRights(address indexed grantee, address transferee); + event MetaVesT_UpdatedRecipient(address indexed grantee, address newRecipient); event MetaVesT_UnlockRateUpdated(address indexed grantee, uint208 unlockRate); event MetaVesT_VestingRateUpdated(address indexed grantee, uint208 vestingRate); - event MetaVesT_Withdrawn(address indexed grantee, address indexed tokenAddress, uint256 amount); + event MetaVesT_Withdrawn(address indexed grantee, address indexed recipient, address indexed tokenAddress, uint256 amount); event MetaVesT_PriceUpdated(address indexed grantee, uint256 exercisePrice); event MetaVesT_RepurchaseAndWithdrawal(address indexed grantee, address indexed tokenAddress, uint256 withdrawalAmount, uint256 repurchaseAmount); event MetaVesT_Terminated(address indexed grantee, uint256 tokensRecovered); @@ -160,6 +161,7 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ address public grantee; // grantee of the tokens address public pendingGrantee; // address of the pending grantee + address public recipient; // recipient of the tokens bool transferable; // whether grantee can transfer their MetaVesT in whole Milestone[] public milestones; // array of Milestone structs Allocation public allocation; // struct containing vesting and unlocking details @@ -174,10 +176,12 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ /// @notice BaseAllocation constructor /// @param _grantee: address of the grantee, cannot be a zero address /// @param _controller: address of the MetaVesTController contract - constructor(address _grantee, address _controller) { + constructor(address _grantee, address _recipient, address _controller) { // Controller can be 0 for an immuatable version, but grantee cannot if (_grantee == address(0)) revert MetaVesT_ZeroAddress(); + if (_recipient == address(0)) revert MetaVesT_ZeroAddress(); grantee = _grantee; + recipient = _recipient; controller = _controller; govType = GovType.vested; } @@ -306,6 +310,15 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ pendingGrantee = address(0); } + /// @notice update the recipient to a new address + /// @dev onlyGrantee -- must be called by the grantee + /// @param _newRecipient - the address of the new recipient + function updateRecipient(address _newRecipient) external onlyGrantee { + if(_newRecipient == address(0)) revert MetaVesT_ZeroAddress(); + emit MetaVesT_UpdatedRecipient(grantee, _newRecipient); + recipient = _newRecipient; + } + /// @notice withdraws tokens from the VestingAllocation /// @dev onlyGrantee -- must be called by the grantee /// @param _amount - the amount of tokens to withdraw @@ -313,8 +326,8 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ if (_amount == 0) revert MetaVesT_ZeroAmount(); if (_amount > getAmountWithdrawable()) revert MetaVesT_MoreThanAvailable(); tokensWithdrawn += _amount; - IZkCappedMinterV2(ZkCappedMinterAddress).mint(msg.sender, _amount); - emit MetaVesT_Withdrawn(msg.sender, allocation.tokenContract, _amount); + IZkCappedMinterV2(ZkCappedMinterAddress).mint(recipient, _amount); + emit MetaVesT_Withdrawn(msg.sender, recipient, allocation.tokenContract, _amount); } /// @notice gets the details of the vest diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 19efb4a..740a30e 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -378,8 +378,7 @@ contract metavestController is SafeTransferLib { address newMetavest; if(deal._metavestType == metavestType.Vesting) { - // TODO WIP: must support recipient - newMetavest = createVestingAllocation(deal.grantee, deal.allocation, deal.milestones); + newMetavest = createVestingAllocation(deal.grantee, deal.recipient, deal.allocation, deal.milestones); } else if(deal._metavestType == metavestType.TokenOption) { @@ -411,11 +410,12 @@ contract metavestController is SafeTransferLib { function validateInputParameters( address _grantee, + address _recipient, address _paymentToken, uint256 _exercisePrice, VestingAllocation.Allocation memory _allocation ) internal pure { - if (_grantee == address(0) || _allocation.tokenContract == address(0) || _paymentToken == address(0) || _exercisePrice == 0) + if (_grantee == address(0) || _recipient == address(0) || _allocation.tokenContract == address(0) || _paymentToken == address(0) || _exercisePrice == 0) revert MetaVesTController_ZeroAddress(); } @@ -488,9 +488,9 @@ contract metavestController is SafeTransferLib { // } - function createVestingAllocation(address _grantee, VestingAllocation.Allocation memory _allocation, VestingAllocation.Milestone[] memory _milestones) internal returns (address){ + function createVestingAllocation(address _grantee, address _recipient, VestingAllocation.Allocation memory _allocation, VestingAllocation.Milestone[] memory _milestones) internal returns (address){ //hard code values not to trigger the failure for the 2 parameters that don't matter for this type of allocation - validateInputParameters(_grantee, address(this), 1, _allocation); + validateInputParameters(_grantee, _recipient, address(this), 1, _allocation); validateAllocation(_allocation); uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); @@ -501,6 +501,7 @@ contract metavestController is SafeTransferLib { address vestingAllocation = IAllocationFactory(vestingFactory).createAllocation( IAllocationFactory.AllocationType.Vesting, _grantee, + _recipient, address(this), _allocation, _milestones, diff --git a/src/RestrictedTokenAllocation.sol b/src/RestrictedTokenAllocation.sol index ba9e988..f527e3b 100644 --- a/src/RestrictedTokenAllocation.sol +++ b/src/RestrictedTokenAllocation.sol @@ -1,231 +1,232 @@ -// SPDX-License-Identifier: AGPL-3.0-only -import "./BaseAllocation.sol"; - -pragma solidity ^0.8.24; - -contract RestrictedTokenAward is BaseAllocation { - - /// @notice address of payment token used for token option exercises or restricted token repurchases - address public immutable paymentToken; - uint256 public shortStopDuration; - uint256 public shortStopDate; - uint256 public repurchasePrice; - uint256 public tokensRepurchased; - uint256 public tokensRepurchasedWithdrawn; - - /// @notice Constructor to deploy a new RestrictedTokenAward - /// @param _grantee - address of the grantee - /// @param _controller - address of the controller - /// @param _paymentToken - address of the payment token - /// @param _repurchasePrice - price at which the restricted tokens can be repurchased in vesting token decimals but only up to payment decimal precision - /// @param _shortStopDuration - duration after termination during which restricted tokens can be repurchased - /// @param _allocation - allocation details as an Allocation struct - /// @param _milestones - milestones with their conditions and awards - constructor ( - address _grantee, - address _controller, - address _paymentToken, - uint256 _repurchasePrice, - uint256 _shortStopDuration, - Allocation memory _allocation, - Milestone[] memory _milestones - ) BaseAllocation( - _grantee, - _controller - ) { - //perform input validation - if (_allocation.tokenContract == address(0)) revert MetaVesT_ZeroAddress(); - if (_allocation.tokenStreamTotal == 0) revert MetaVesT_ZeroAmount(); - if (_allocation.vestingRate > 1000*1e18 || _allocation.unlockRate > 1000*1e18) revert MetaVesT_RateTooHigh(); - - //set vesting allocation variables - allocation.tokenContract = _allocation.tokenContract; - allocation.tokenStreamTotal = _allocation.tokenStreamTotal; - allocation.vestingCliffCredit = _allocation.vestingCliffCredit; - allocation.unlockingCliffCredit = _allocation.unlockingCliffCredit; - allocation.vestingRate = _allocation.vestingRate; - allocation.vestingStartTime = _allocation.vestingStartTime; - allocation.unlockRate = _allocation.unlockRate; - allocation.unlockStartTime = _allocation.unlockStartTime; - - // set token option variables - repurchasePrice = _repurchasePrice; - shortStopDuration = _shortStopDuration; - - paymentToken = _paymentToken; - - // manually copy milestones - for (uint256 i; i < _milestones.length; ++i) { - milestones.push(_milestones[i]); - } - } - - /// @notice returns the vesting type for RestrictedTokenAward - /// @return uint256 type 3 - function getVestingType() external pure override returns (uint256) { - return 3; - } - - /// @notice returns the governing power for RestrictedTokenAward based on the govType - /// @return uint256 governingPower for this RestrictedTokenAward contract - function getGoverningPower() external view override returns (uint256) { - uint256 governingPower; - if(govType==GovType.all) - { - uint256 totalMilestoneAward = 0; - for(uint256 i; i < milestones.length; ++i) - { - totalMilestoneAward += milestones[i].milestoneAward; - } - governingPower = (allocation.tokenStreamTotal + totalMilestoneAward) - tokensWithdrawn; - } - else if(govType==GovType.vested) - governingPower = getVestedTokenAmount() - tokensWithdrawn; - else - governingPower = _min(getVestedTokenAmount(), getUnlockedTokenAmount()) - tokensWithdrawn; - - return governingPower; - } - - /// @notice updates the short stop time of the vesting contract - /// @dev onlyController -- must be called from the metavest controller - /// @param _shortStopTime - new short stop time to be set in seconds - function updateStopTimes(uint48 _shortStopTime) external override onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - shortStopDuration = _shortStopTime; - emit MetaVesT_StopTimesUpdated(grantee, _shortStopTime); - } - - /// @notice updates the exercise price - /// @dev onlyController -- must be called from the metavest controller - /// @param _newPrice - the new exercise price in vesting token decimals but only up to payment decimal precision - function updatePrice(uint256 _newPrice) external onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - repurchasePrice = _newPrice; - emit MetaVesT_PriceUpdated(grantee, _newPrice); - } - - /// @notice gets the payment amount for a given amount of tokens - /// @param _amount - the amount of tokens to calculate the payment amount in the vesting token decimals - /// @return paymentAmount - the payment amount for the given token amount in the payment token decimals - function getPaymentAmount(uint256 _amount) public view returns (uint256) { - uint8 paymentDecimals = IERC20M(paymentToken).decimals(); - uint8 repurchaseTokenDecimals = IERC20M(allocation.tokenContract).decimals(); - - // Calculate paymentAmount - uint256 paymentAmount; - paymentAmount = _amount * repurchasePrice / (10**repurchaseTokenDecimals); - - //scale paymentAmount to payment token decimals - if(paymentDecimals getAmountRepurchasable()) revert MetaVesT_MoreThanAvailable(); - if(block.timestampIERC20M(allocation.tokenContract).balanceOf(address(this))) - repurchaseAmount = IERC20M(allocation.tokenContract).balanceOf(address(this)); - return repurchaseAmount; - } - - /// @notice returns the amount of tokens that are vested - /// @return uint256 amount of tokens that are vested - function getVestedTokenAmount() public view returns (uint256) { - if(block.timestampallocation.tokenStreamTotal) - _tokensVested = allocation.tokenStreamTotal; - return _tokensVested += milestoneAwardTotal; - } - - /// @notice returns the amount of tokens that are unlocked - /// @return uint256 amount of tokens that are unlocked - function getUnlockedTokenAmount() public view returns (uint256) { - if(block.timestampallocation.tokenStreamTotal + milestoneAwardTotal) - _tokensUnlocked = allocation.tokenStreamTotal + milestoneAwardTotal; - - return _tokensUnlocked += milestoneUnlockedTotal; - } - - /// @notice returns the amount of tokens that can be withdrawn - /// @return uint256 amount of tokens that can be withdrawn - function getAmountWithdrawable() public view override returns (uint256) { - uint256 _tokensVested = getVestedTokenAmount(); - uint256 _tokensUnlocked = getUnlockedTokenAmount(); - uint256 withdrawableAmount = _min(_tokensVested, _tokensUnlocked); - if(withdrawableAmount>tokensWithdrawn) - return withdrawableAmount - tokensWithdrawn; - else - return 0; - } - -} +// TODO WIP +//// SPDX-License-Identifier: AGPL-3.0-only +//import "./BaseAllocation.sol"; +// +//pragma solidity ^0.8.24; +// +//contract RestrictedTokenAward is BaseAllocation { +// +// /// @notice address of payment token used for token option exercises or restricted token repurchases +// address public immutable paymentToken; +// uint256 public shortStopDuration; +// uint256 public shortStopDate; +// uint256 public repurchasePrice; +// uint256 public tokensRepurchased; +// uint256 public tokensRepurchasedWithdrawn; +// +// /// @notice Constructor to deploy a new RestrictedTokenAward +// /// @param _grantee - address of the grantee +// /// @param _controller - address of the controller +// /// @param _paymentToken - address of the payment token +// /// @param _repurchasePrice - price at which the restricted tokens can be repurchased in vesting token decimals but only up to payment decimal precision +// /// @param _shortStopDuration - duration after termination during which restricted tokens can be repurchased +// /// @param _allocation - allocation details as an Allocation struct +// /// @param _milestones - milestones with their conditions and awards +// constructor ( +// address _grantee, +// address _controller, +// address _paymentToken, +// uint256 _repurchasePrice, +// uint256 _shortStopDuration, +// Allocation memory _allocation, +// Milestone[] memory _milestones +// ) BaseAllocation( +// _grantee, +// _controller +// ) { +// //perform input validation +// if (_allocation.tokenContract == address(0)) revert MetaVesT_ZeroAddress(); +// if (_allocation.tokenStreamTotal == 0) revert MetaVesT_ZeroAmount(); +// if (_allocation.vestingRate > 1000*1e18 || _allocation.unlockRate > 1000*1e18) revert MetaVesT_RateTooHigh(); +// +// //set vesting allocation variables +// allocation.tokenContract = _allocation.tokenContract; +// allocation.tokenStreamTotal = _allocation.tokenStreamTotal; +// allocation.vestingCliffCredit = _allocation.vestingCliffCredit; +// allocation.unlockingCliffCredit = _allocation.unlockingCliffCredit; +// allocation.vestingRate = _allocation.vestingRate; +// allocation.vestingStartTime = _allocation.vestingStartTime; +// allocation.unlockRate = _allocation.unlockRate; +// allocation.unlockStartTime = _allocation.unlockStartTime; +// +// // set token option variables +// repurchasePrice = _repurchasePrice; +// shortStopDuration = _shortStopDuration; +// +// paymentToken = _paymentToken; +// +// // manually copy milestones +// for (uint256 i; i < _milestones.length; ++i) { +// milestones.push(_milestones[i]); +// } +// } +// +// /// @notice returns the vesting type for RestrictedTokenAward +// /// @return uint256 type 3 +// function getVestingType() external pure override returns (uint256) { +// return 3; +// } +// +// /// @notice returns the governing power for RestrictedTokenAward based on the govType +// /// @return uint256 governingPower for this RestrictedTokenAward contract +// function getGoverningPower() external view override returns (uint256) { +// uint256 governingPower; +// if(govType==GovType.all) +// { +// uint256 totalMilestoneAward = 0; +// for(uint256 i; i < milestones.length; ++i) +// { +// totalMilestoneAward += milestones[i].milestoneAward; +// } +// governingPower = (allocation.tokenStreamTotal + totalMilestoneAward) - tokensWithdrawn; +// } +// else if(govType==GovType.vested) +// governingPower = getVestedTokenAmount() - tokensWithdrawn; +// else +// governingPower = _min(getVestedTokenAmount(), getUnlockedTokenAmount()) - tokensWithdrawn; +// +// return governingPower; +// } +// +// /// @notice updates the short stop time of the vesting contract +// /// @dev onlyController -- must be called from the metavest controller +// /// @param _shortStopTime - new short stop time to be set in seconds +// function updateStopTimes(uint48 _shortStopTime) external override onlyController { +// if(terminated) revert MetaVesT_AlreadyTerminated(); +// shortStopDuration = _shortStopTime; +// emit MetaVesT_StopTimesUpdated(grantee, _shortStopTime); +// } +// +// /// @notice updates the exercise price +// /// @dev onlyController -- must be called from the metavest controller +// /// @param _newPrice - the new exercise price in vesting token decimals but only up to payment decimal precision +// function updatePrice(uint256 _newPrice) external onlyController { +// if(terminated) revert MetaVesT_AlreadyTerminated(); +// repurchasePrice = _newPrice; +// emit MetaVesT_PriceUpdated(grantee, _newPrice); +// } +// +// /// @notice gets the payment amount for a given amount of tokens +// /// @param _amount - the amount of tokens to calculate the payment amount in the vesting token decimals +// /// @return paymentAmount - the payment amount for the given token amount in the payment token decimals +// function getPaymentAmount(uint256 _amount) public view returns (uint256) { +// uint8 paymentDecimals = IERC20M(paymentToken).decimals(); +// uint8 repurchaseTokenDecimals = IERC20M(allocation.tokenContract).decimals(); +// +// // Calculate paymentAmount +// uint256 paymentAmount; +// paymentAmount = _amount * repurchasePrice / (10**repurchaseTokenDecimals); +// +// //scale paymentAmount to payment token decimals +// if(paymentDecimals getAmountRepurchasable()) revert MetaVesT_MoreThanAvailable(); +// if(block.timestampIERC20M(allocation.tokenContract).balanceOf(address(this))) +// repurchaseAmount = IERC20M(allocation.tokenContract).balanceOf(address(this)); +// return repurchaseAmount; +// } +// +// /// @notice returns the amount of tokens that are vested +// /// @return uint256 amount of tokens that are vested +// function getVestedTokenAmount() public view returns (uint256) { +// if(block.timestampallocation.tokenStreamTotal) +// _tokensVested = allocation.tokenStreamTotal; +// return _tokensVested += milestoneAwardTotal; +// } +// +// /// @notice returns the amount of tokens that are unlocked +// /// @return uint256 amount of tokens that are unlocked +// function getUnlockedTokenAmount() public view returns (uint256) { +// if(block.timestampallocation.tokenStreamTotal + milestoneAwardTotal) +// _tokensUnlocked = allocation.tokenStreamTotal + milestoneAwardTotal; +// +// return _tokensUnlocked += milestoneUnlockedTotal; +// } +// +// /// @notice returns the amount of tokens that can be withdrawn +// /// @return uint256 amount of tokens that can be withdrawn +// function getAmountWithdrawable() public view override returns (uint256) { +// uint256 _tokensVested = getVestedTokenAmount(); +// uint256 _tokensUnlocked = getUnlockedTokenAmount(); +// uint256 withdrawableAmount = _min(_tokensVested, _tokensUnlocked); +// if(withdrawableAmount>tokensWithdrawn) +// return withdrawableAmount - tokensWithdrawn; +// else +// return 0; +// } +// +//} diff --git a/src/RestrictedTokenFactory.sol b/src/RestrictedTokenFactory.sol index 6497f53..20c7f21 100644 --- a/src/RestrictedTokenFactory.sol +++ b/src/RestrictedTokenFactory.sol @@ -1,25 +1,26 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.24; - -import "./RestrictedTokenAllocation.sol"; -import "./interfaces/IAllocationFactory.sol"; - -contract RestrictedTokenFactory is IAllocationFactory { - - function createAllocation( - AllocationType _allocationType, - address _grantee, - address _controller, - RestrictedTokenAward.Allocation memory _allocation, - RestrictedTokenAward.Milestone[] memory _milestones, - address _paymentToken, - uint256 _exercisePrice, - uint256 _shortStopDuration - ) external returns (address) { - if (_allocationType == AllocationType.RestrictedToken) { - return address(new RestrictedTokenAward(_grantee, _controller, _paymentToken, _exercisePrice, _shortStopDuration, _allocation, _milestones)); - } else { - revert("AllocationFactory: invalid allocation type"); - } - } -} +// TODO WIP +//// SPDX-License-Identifier: GPL-3.0 +//pragma solidity ^0.8.24; +// +//import "./RestrictedTokenAllocation.sol"; +//import "./interfaces/IAllocationFactory.sol"; +// +//contract RestrictedTokenFactory is IAllocationFactory { +// +// function createAllocation( +// AllocationType _allocationType, +// address _grantee, +// address _controller, +// RestrictedTokenAward.Allocation memory _allocation, +// RestrictedTokenAward.Milestone[] memory _milestones, +// address _paymentToken, +// uint256 _exercisePrice, +// uint256 _shortStopDuration +// ) external returns (address) { +// if (_allocationType == AllocationType.RestrictedToken) { +// return address(new RestrictedTokenAward(_grantee, _controller, _paymentToken, _exercisePrice, _shortStopDuration, _allocation, _milestones)); +// } else { +// revert("AllocationFactory: invalid allocation type"); +// } +// } +//} diff --git a/src/TokenOptionAllocation.sol b/src/TokenOptionAllocation.sol index f5c2947..520918b 100644 --- a/src/TokenOptionAllocation.sol +++ b/src/TokenOptionAllocation.sol @@ -1,222 +1,223 @@ -// SPDX-License-Identifier: AGPL-3.0-only -import "./BaseAllocation.sol"; - -pragma solidity ^0.8.24; - -contract TokenOptionAllocation is BaseAllocation { - - /// @notice address of payment token used for token option exercises or restricted token repurchases - address public immutable paymentToken; - uint256 public tokensExercised; - uint256 public exercisePrice; - uint256 public shortStopDuration; - uint256 public shortStopTime; - - event MetaVesT_TokenOptionExercised(address indexed _grantee, uint256 _tokensToExercise, uint256 _paymentAmount); - - /// @notice Constructor to create a TokenOptionAllocation - /// @param _grantee - address of the grantee - /// @param _controller - address of the controller - /// @param _paymentToken - address of the payment token - /// @param _exercisePrice - price of the token option exercise in vesting token decimals but only up to payment decimal precision - /// @param _shortStopDuration - duration of the short stop - /// @param _allocation - allocation details as an Allocation struct - /// @param _milestones - milestones with conditions and awards - constructor ( - address _grantee, - address _controller, - address _paymentToken, - uint256 _exercisePrice, - uint256 _shortStopDuration, - Allocation memory _allocation, - Milestone[] memory _milestones - ) BaseAllocation( - _grantee, - _controller - ) { - //perform input validation - if (_allocation.tokenContract == address(0)) revert MetaVesT_ZeroAddress(); - if (_allocation.tokenStreamTotal == 0) revert MetaVesT_ZeroAmount(); - if (_allocation.vestingRate > 1000*1e18 || _allocation.unlockRate > 1000*1e18) revert MetaVesT_RateTooHigh(); - - //set vesting allocation variables - allocation.tokenContract = _allocation.tokenContract; - allocation.tokenStreamTotal = _allocation.tokenStreamTotal; - allocation.vestingCliffCredit = _allocation.vestingCliffCredit; - allocation.unlockingCliffCredit = _allocation.unlockingCliffCredit; - allocation.vestingRate = _allocation.vestingRate; - allocation.vestingStartTime = _allocation.vestingStartTime; - allocation.unlockRate = _allocation.unlockRate; - allocation.unlockStartTime = _allocation.unlockStartTime; - - // set token option variables - exercisePrice = _exercisePrice; - shortStopDuration = _shortStopDuration; - - paymentToken = _paymentToken; - - // manually copy milestones - for (uint256 i; i < _milestones.length; ++i) { - milestones.push(_milestones[i]); - } - } - - /// @notice returns the contract vesting type 2 for TokenOptionAllocation - /// @return 2 - function getVestingType() external pure override returns (uint256) { - return 2; - } - - /// @notice returns the governing power of the TokenOptionAllocation - /// @return governingPower - the governing power of the TokenOptionAllocation based on the governance setting - function getGoverningPower() external view override returns (uint256) { - uint256 governingPower; - if(govType==GovType.all) - { - uint256 totalMilestoneAward = 0; - for(uint256 i; i < milestones.length; ++i) - { - totalMilestoneAward += milestones[i].milestoneAward; - } - governingPower = (allocation.tokenStreamTotal + totalMilestoneAward) - tokensWithdrawn; - } - else if(govType==GovType.vested) - governingPower = tokensExercised - tokensWithdrawn; - else - governingPower = _min(tokensExercised, getUnlockedTokenAmount()) - tokensWithdrawn; - - return governingPower; - } - - /// @notice updates the short stop time of the TokenOptionAllocation - /// @dev onlyController -- must be called from the metavest controller - /// @param _shortStopTime - the new short stop time - function updateStopTimes(uint48 _shortStopTime) external override onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - shortStopDuration = _shortStopTime; - emit MetaVesT_StopTimesUpdated(grantee, _shortStopTime); - } - - /// @notice updates the exercise price - /// @dev onlyController -- must be called from the metavest controller - /// @param _newPrice - the new exercise price in vesting token decimals but only up to payment decimal precision - function updatePrice(uint256 _newPrice) external onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - exercisePrice = _newPrice; - emit MetaVesT_PriceUpdated(grantee, _newPrice); - } - - /// @notice gets the payment amount for a given amount of tokens - /// @param _amount - the amount of tokens to calculate the payment amount in the vesting token decimals - /// @return paymentAmount - the payment amount for the given token amount in the payment token decimals - function getPaymentAmount(uint256 _amount) public view returns (uint256) { - uint8 paymentDecimals = IERC20M(paymentToken).decimals(); - uint8 exerciseTokenDecimals = IERC20M(allocation.tokenContract).decimals(); - - // Calculate paymentAmount - uint256 paymentAmount; - paymentAmount = _amount * exercisePrice / (10**exerciseTokenDecimals); - - //scale paymentAmount to payment token decimals - if(paymentDecimalsshortStopTime && terminated) revert MetaVest_ShortStopDatePassed(); - if (_tokensToExercise == 0) revert MetaVesT_ZeroAmount(); - if (_tokensToExercise > getAmountExercisable()) revert MetaVesT_MoreThanAvailable(); - - // Calculate paymentAmount - uint256 paymentAmount = getPaymentAmount(_tokensToExercise); - if(paymentAmount == 0) revert MetaVesT_TooSmallAmount(); - - safeTransferFrom(paymentToken, msg.sender, getAuthority(), paymentAmount); - tokensExercised += _tokensToExercise; - emit MetaVesT_TokenOptionExercised(msg.sender, _tokensToExercise, paymentAmount); - } - - /// @notice Allows the controller to terminate the TokenOptionAllocation - /// @dev onlyController -- must be called from the metavest controller - function terminate() external override onlyController nonReentrant { - if(terminated) revert MetaVesT_AlreadyTerminated(); - - uint256 milestonesAllocation = 0; - for (uint256 i; i < milestones.length; ++i) { - milestonesAllocation += milestones[i].milestoneAward; - } - uint256 tokensToRecover = allocation.tokenStreamTotal + milestonesAllocation - getAmountExercisable() - tokensExercised; - terminationTime = block.timestamp; - shortStopTime = block.timestamp + shortStopDuration; - safeTransfer(allocation.tokenContract, getAuthority(), tokensToRecover); - terminated = true; - emit MetaVesT_Terminated(grantee, tokensToRecover); - } - - /// @notice recovers any forfeited tokens after the short stop time - /// @dev onlyAuthority -- must be called from the authority - function recoverForfeitTokens() external onlyAuthority nonReentrant { - if(block.timestamp tokensExercised - tokensWithdrawn) - tokensToRecover = IERC20M(allocation.tokenContract).balanceOf(address(this)) + tokensWithdrawn - tokensExercised; - safeTransfer(allocation.tokenContract, getAuthority(), tokensToRecover); - } - - /// @notice gets the amount of tokens available for a grantee to exercise - /// @return uint256 amount of tokens available for the grantee to exercise - function getAmountExercisable() public view returns (uint256) { - if(block.timestampshortStopTime && shortStopTime>0)) - return 0; - - uint256 _timeElapsedSinceVest = block.timestamp - allocation.vestingStartTime; - if(terminated) - _timeElapsedSinceVest = terminationTime - allocation.vestingStartTime; - - uint256 _tokensVested = (_timeElapsedSinceVest * allocation.vestingRate) + allocation.vestingCliffCredit; - - if(_tokensVested>allocation.tokenStreamTotal) - _tokensVested = allocation.tokenStreamTotal; - uint256 _tokensExercisable = _tokensVested + milestoneAwardTotal; - if(_tokensExercisable>tokensExercised) - return _tokensExercisable - tokensExercised; - else - return 0; - } - - /// @notice gets the amount of tokens unlocked for a grantee - /// @return uint256 amount of tokens unlocked for the grantee - function getUnlockedTokenAmount() public view returns (uint256) { - if(block.timestampallocation.tokenStreamTotal + milestoneAwardTotal) - _tokensUnlocked = allocation.tokenStreamTotal + milestoneAwardTotal; - - return _tokensUnlocked + milestoneUnlockedTotal; - } - - /// @notice gets the amount of tokens available for a grantee to withdraw - /// @return uint256 amount of tokens available for the grantee to withdraw - function getAmountWithdrawable() public view override returns (uint256) { - uint256 _tokensUnlocked = getUnlockedTokenAmount(); - - uint256 withdrawableAmount = _min(tokensExercised, _tokensUnlocked); - if(withdrawableAmount>tokensWithdrawn) - return withdrawableAmount - tokensWithdrawn; - else - return 0; - } - -} +// TODO WIP +//// SPDX-License-Identifier: AGPL-3.0-only +//import "./BaseAllocation.sol"; +// +//pragma solidity ^0.8.24; +// +//contract TokenOptionAllocation is BaseAllocation { +// +// /// @notice address of payment token used for token option exercises or restricted token repurchases +// address public immutable paymentToken; +// uint256 public tokensExercised; +// uint256 public exercisePrice; +// uint256 public shortStopDuration; +// uint256 public shortStopTime; +// +// event MetaVesT_TokenOptionExercised(address indexed _grantee, uint256 _tokensToExercise, uint256 _paymentAmount); +// +// /// @notice Constructor to create a TokenOptionAllocation +// /// @param _grantee - address of the grantee +// /// @param _controller - address of the controller +// /// @param _paymentToken - address of the payment token +// /// @param _exercisePrice - price of the token option exercise in vesting token decimals but only up to payment decimal precision +// /// @param _shortStopDuration - duration of the short stop +// /// @param _allocation - allocation details as an Allocation struct +// /// @param _milestones - milestones with conditions and awards +// constructor ( +// address _grantee, +// address _controller, +// address _paymentToken, +// uint256 _exercisePrice, +// uint256 _shortStopDuration, +// Allocation memory _allocation, +// Milestone[] memory _milestones +// ) BaseAllocation( +// _grantee, +// _controller +// ) { +// //perform input validation +// if (_allocation.tokenContract == address(0)) revert MetaVesT_ZeroAddress(); +// if (_allocation.tokenStreamTotal == 0) revert MetaVesT_ZeroAmount(); +// if (_allocation.vestingRate > 1000*1e18 || _allocation.unlockRate > 1000*1e18) revert MetaVesT_RateTooHigh(); +// +// //set vesting allocation variables +// allocation.tokenContract = _allocation.tokenContract; +// allocation.tokenStreamTotal = _allocation.tokenStreamTotal; +// allocation.vestingCliffCredit = _allocation.vestingCliffCredit; +// allocation.unlockingCliffCredit = _allocation.unlockingCliffCredit; +// allocation.vestingRate = _allocation.vestingRate; +// allocation.vestingStartTime = _allocation.vestingStartTime; +// allocation.unlockRate = _allocation.unlockRate; +// allocation.unlockStartTime = _allocation.unlockStartTime; +// +// // set token option variables +// exercisePrice = _exercisePrice; +// shortStopDuration = _shortStopDuration; +// +// paymentToken = _paymentToken; +// +// // manually copy milestones +// for (uint256 i; i < _milestones.length; ++i) { +// milestones.push(_milestones[i]); +// } +// } +// +// /// @notice returns the contract vesting type 2 for TokenOptionAllocation +// /// @return 2 +// function getVestingType() external pure override returns (uint256) { +// return 2; +// } +// +// /// @notice returns the governing power of the TokenOptionAllocation +// /// @return governingPower - the governing power of the TokenOptionAllocation based on the governance setting +// function getGoverningPower() external view override returns (uint256) { +// uint256 governingPower; +// if(govType==GovType.all) +// { +// uint256 totalMilestoneAward = 0; +// for(uint256 i; i < milestones.length; ++i) +// { +// totalMilestoneAward += milestones[i].milestoneAward; +// } +// governingPower = (allocation.tokenStreamTotal + totalMilestoneAward) - tokensWithdrawn; +// } +// else if(govType==GovType.vested) +// governingPower = tokensExercised - tokensWithdrawn; +// else +// governingPower = _min(tokensExercised, getUnlockedTokenAmount()) - tokensWithdrawn; +// +// return governingPower; +// } +// +// /// @notice updates the short stop time of the TokenOptionAllocation +// /// @dev onlyController -- must be called from the metavest controller +// /// @param _shortStopTime - the new short stop time +// function updateStopTimes(uint48 _shortStopTime) external override onlyController { +// if(terminated) revert MetaVesT_AlreadyTerminated(); +// shortStopDuration = _shortStopTime; +// emit MetaVesT_StopTimesUpdated(grantee, _shortStopTime); +// } +// +// /// @notice updates the exercise price +// /// @dev onlyController -- must be called from the metavest controller +// /// @param _newPrice - the new exercise price in vesting token decimals but only up to payment decimal precision +// function updatePrice(uint256 _newPrice) external onlyController { +// if(terminated) revert MetaVesT_AlreadyTerminated(); +// exercisePrice = _newPrice; +// emit MetaVesT_PriceUpdated(grantee, _newPrice); +// } +// +// /// @notice gets the payment amount for a given amount of tokens +// /// @param _amount - the amount of tokens to calculate the payment amount in the vesting token decimals +// /// @return paymentAmount - the payment amount for the given token amount in the payment token decimals +// function getPaymentAmount(uint256 _amount) public view returns (uint256) { +// uint8 paymentDecimals = IERC20M(paymentToken).decimals(); +// uint8 exerciseTokenDecimals = IERC20M(allocation.tokenContract).decimals(); +// +// // Calculate paymentAmount +// uint256 paymentAmount; +// paymentAmount = _amount * exercisePrice / (10**exerciseTokenDecimals); +// +// //scale paymentAmount to payment token decimals +// if(paymentDecimalsshortStopTime && terminated) revert MetaVest_ShortStopDatePassed(); +// if (_tokensToExercise == 0) revert MetaVesT_ZeroAmount(); +// if (_tokensToExercise > getAmountExercisable()) revert MetaVesT_MoreThanAvailable(); +// +// // Calculate paymentAmount +// uint256 paymentAmount = getPaymentAmount(_tokensToExercise); +// if(paymentAmount == 0) revert MetaVesT_TooSmallAmount(); +// +// safeTransferFrom(paymentToken, msg.sender, getAuthority(), paymentAmount); +// tokensExercised += _tokensToExercise; +// emit MetaVesT_TokenOptionExercised(msg.sender, _tokensToExercise, paymentAmount); +// } +// +// /// @notice Allows the controller to terminate the TokenOptionAllocation +// /// @dev onlyController -- must be called from the metavest controller +// function terminate() external override onlyController nonReentrant { +// if(terminated) revert MetaVesT_AlreadyTerminated(); +// +// uint256 milestonesAllocation = 0; +// for (uint256 i; i < milestones.length; ++i) { +// milestonesAllocation += milestones[i].milestoneAward; +// } +// uint256 tokensToRecover = allocation.tokenStreamTotal + milestonesAllocation - getAmountExercisable() - tokensExercised; +// terminationTime = block.timestamp; +// shortStopTime = block.timestamp + shortStopDuration; +// safeTransfer(allocation.tokenContract, getAuthority(), tokensToRecover); +// terminated = true; +// emit MetaVesT_Terminated(grantee, tokensToRecover); +// } +// +// /// @notice recovers any forfeited tokens after the short stop time +// /// @dev onlyAuthority -- must be called from the authority +// function recoverForfeitTokens() external onlyAuthority nonReentrant { +// if(block.timestamp tokensExercised - tokensWithdrawn) +// tokensToRecover = IERC20M(allocation.tokenContract).balanceOf(address(this)) + tokensWithdrawn - tokensExercised; +// safeTransfer(allocation.tokenContract, getAuthority(), tokensToRecover); +// } +// +// /// @notice gets the amount of tokens available for a grantee to exercise +// /// @return uint256 amount of tokens available for the grantee to exercise +// function getAmountExercisable() public view returns (uint256) { +// if(block.timestampshortStopTime && shortStopTime>0)) +// return 0; +// +// uint256 _timeElapsedSinceVest = block.timestamp - allocation.vestingStartTime; +// if(terminated) +// _timeElapsedSinceVest = terminationTime - allocation.vestingStartTime; +// +// uint256 _tokensVested = (_timeElapsedSinceVest * allocation.vestingRate) + allocation.vestingCliffCredit; +// +// if(_tokensVested>allocation.tokenStreamTotal) +// _tokensVested = allocation.tokenStreamTotal; +// uint256 _tokensExercisable = _tokensVested + milestoneAwardTotal; +// if(_tokensExercisable>tokensExercised) +// return _tokensExercisable - tokensExercised; +// else +// return 0; +// } +// +// /// @notice gets the amount of tokens unlocked for a grantee +// /// @return uint256 amount of tokens unlocked for the grantee +// function getUnlockedTokenAmount() public view returns (uint256) { +// if(block.timestampallocation.tokenStreamTotal + milestoneAwardTotal) +// _tokensUnlocked = allocation.tokenStreamTotal + milestoneAwardTotal; +// +// return _tokensUnlocked + milestoneUnlockedTotal; +// } +// +// /// @notice gets the amount of tokens available for a grantee to withdraw +// /// @return uint256 amount of tokens available for the grantee to withdraw +// function getAmountWithdrawable() public view override returns (uint256) { +// uint256 _tokensUnlocked = getUnlockedTokenAmount(); +// +// uint256 withdrawableAmount = _min(tokensExercised, _tokensUnlocked); +// if(withdrawableAmount>tokensWithdrawn) +// return withdrawableAmount - tokensWithdrawn; +// else +// return 0; +// } +// +//} diff --git a/src/TokenOptionFactory.sol b/src/TokenOptionFactory.sol index 3237c5d..c708a5f 100644 --- a/src/TokenOptionFactory.sol +++ b/src/TokenOptionFactory.sol @@ -1,25 +1,26 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.24; - -import "./TokenOptionAllocation.sol"; -import "./interfaces/IAllocationFactory.sol"; - -contract TokenOptionFactory is IAllocationFactory { - - function createAllocation( - AllocationType _allocationType, - address _grantee, - address _controller, - TokenOptionAllocation.Allocation memory _allocation, - TokenOptionAllocation.Milestone[] memory _milestones, - address _paymentToken, - uint256 _exercisePrice, - uint256 _shortStopDuration - ) external returns (address) { - if (_allocationType == AllocationType.TokenOption) { - return address(new TokenOptionAllocation(_grantee, _controller, _paymentToken, _exercisePrice, _shortStopDuration, _allocation, _milestones)); - } else { - revert("AllocationFactory: invalid allocation type"); - } - } -} +// TODO WIP +//// SPDX-License-Identifier: GPL-3.0 +//pragma solidity ^0.8.24; +// +//import "./TokenOptionAllocation.sol"; +//import "./interfaces/IAllocationFactory.sol"; +// +//contract TokenOptionFactory is IAllocationFactory { +// +// function createAllocation( +// AllocationType _allocationType, +// address _grantee, +// address _controller, +// TokenOptionAllocation.Allocation memory _allocation, +// TokenOptionAllocation.Milestone[] memory _milestones, +// address _paymentToken, +// uint256 _exercisePrice, +// uint256 _shortStopDuration +// ) external returns (address) { +// if (_allocationType == AllocationType.TokenOption) { +// return address(new TokenOptionAllocation(_grantee, _controller, _paymentToken, _exercisePrice, _shortStopDuration, _allocation, _milestones)); +// } else { +// revert("AllocationFactory: invalid allocation type"); +// } +// } +//} diff --git a/src/VestingAllocation.sol b/src/VestingAllocation.sol index 80feea9..f63bcdb 100644 --- a/src/VestingAllocation.sol +++ b/src/VestingAllocation.sol @@ -7,22 +7,26 @@ contract VestingAllocation is BaseAllocation { /// @notice constructor for VestingAllocation /// @param _grantee address of the grantee + /// @param _recipient address of the fund recipient /// @param _controller address of the controller /// @param _allocation Allocation struct containing token contract /// @param _milestones array of Milestone structs with conditions and awards constructor ( address _grantee, + address _recipient, address _controller, Allocation memory _allocation, Milestone[] memory _milestones ) BaseAllocation( - _grantee, - _controller + _grantee, + _recipient, + _controller ) { //perform input validation if (_allocation.tokenContract == address(0)) revert MetaVesT_ZeroAddress(); if (_allocation.tokenStreamTotal == 0) revert MetaVesT_ZeroAmount(); if (_grantee == address(0)) revert MetaVesT_ZeroAddress(); + if (_recipient == address(0)) revert MetaVesT_ZeroAddress(); if (_allocation.vestingRate > 1000*1e18 || _allocation.unlockRate > 1000*1e18) revert MetaVesT_RateTooHigh(); //set vesting allocation variables diff --git a/src/VestingAllocationFactory.sol b/src/VestingAllocationFactory.sol index 693064f..f01baaa 100644 --- a/src/VestingAllocationFactory.sol +++ b/src/VestingAllocationFactory.sol @@ -9,6 +9,7 @@ contract VestingAllocationFactory is IAllocationFactory { function createAllocation( AllocationType _allocationType, address _grantee, + address _recipient, address _controller, VestingAllocation.Allocation memory _allocation, VestingAllocation.Milestone[] memory _milestones, @@ -17,7 +18,7 @@ contract VestingAllocationFactory is IAllocationFactory { uint256 _shortStopDuration ) external returns (address) { if (_allocationType == AllocationType.Vesting) { - return address(new VestingAllocation(_grantee, _controller, _allocation, _milestones)); + return address(new VestingAllocation(_grantee, _recipient, _controller, _allocation, _milestones)); } else { revert("AllocationFactory: invalid allocation type"); } diff --git a/src/interfaces/IAllocationFactory.sol b/src/interfaces/IAllocationFactory.sol index 7f0fd1a..7bf0370 100644 --- a/src/interfaces/IAllocationFactory.sol +++ b/src/interfaces/IAllocationFactory.sol @@ -14,6 +14,7 @@ interface IAllocationFactory { function createAllocation( AllocationType _allocationType, address _grantee, + address _recipient, address _controller, VestingAllocation.Allocation memory _allocation, VestingAllocation.Milestone[] memory _milestones, diff --git a/test/VestingAllocation.t.sol b/test/VestingAllocation.t.sol new file mode 100644 index 0000000..965ef09 --- /dev/null +++ b/test/VestingAllocation.t.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +//import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import {ZkCappedMinterV2, IMintable} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV1} from "zk-governance/l2-contracts/src/ZkTokenV1.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {VestingAllocation} from "../src/VestingAllocation.sol"; + +//contract TestToken is ERC20 { +// uint8 _decimals; +// +// constructor(uint256 initialSupply, uint8 __decimals) ERC20("Test Token", "TestUSDC") { +// _decimals = __decimals; +// _mint(msg.sender, initialSupply); +// } +// +// function decimals() public view override returns (uint8) { +// return _decimals; +// } +//} + +contract VestingAllocationTest is Test { + + address grantee = address(0xa); + address recipient = address(0xb); + address newRecipient = address(0xc); + + ZkTokenV1 zkToken; + + VestingAllocation vestingAllocation; + + function setUp() public { + zkToken = new ZkTokenV1(); + zkToken.initialize(address(this), address(this), 0 ether); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 2000 ether, + unlockOnCompletion: false, + complete: false, + conditionContracts: new address[](0) + }); + + vestingAllocation = new VestingAllocation( + grantee, + recipient, + address(this), + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + + // Deploy ZK Capped Minter v2 + ZkCappedMinterV2 zkCappedMinter = new ZkCappedMinterV2( + IMintable(address(zkToken)), + address(this), + 10000 ether, + uint48(block.timestamp), + uint48(block.timestamp + 365 days * 10) + ); + + // Grant ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + // Grant MetaVesT minter privilege + zkCappedMinter.grantRole( + zkCappedMinter.MINTER_ROLE(), + address(vestingAllocation) + ); + vestingAllocation.setZkCappedMinterAddress(address(zkCappedMinter)); + } + + function test_metadata() public { + assertEq(vestingAllocation.grantee(), grantee, "Unexpected grantee"); + assertEq(vestingAllocation.recipient(), recipient, "Unexpected recipient"); + } + + function test_withdraw() public { + // Should withdraw to recipient by default + uint256 balanceBefore = zkToken.balanceOf(recipient); + + vm.expectEmit(true, true, true, true); + emit BaseAllocation.MetaVesT_Withdrawn(grantee, recipient, address(zkToken), 100 ether); + vm.prank(grantee); + VestingAllocation(vestingAllocation).withdraw(100 ether); + + assertEq(zkToken.balanceOf(recipient), balanceBefore + 100 ether); + } + + function test_UpdateRecipient() public { + // Grantee should be able to update recipient + vm.prank(grantee); + vm.expectEmit(true, true, true, true); + emit BaseAllocation.MetaVesT_UpdatedRecipient(grantee, newRecipient); + VestingAllocation(vestingAllocation).updateRecipient(newRecipient); + + // Should withdraw to new recipient now + uint256 balanceBefore = zkToken.balanceOf(newRecipient); + vm.prank(grantee); + VestingAllocation(vestingAllocation).withdraw(100 ether); + assertEq(zkToken.balanceOf(newRecipient), balanceBefore + 100 ether); + } + + function test_RevertIf_NonGranteeUpdateRecipient() public { + vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_OnlyGrantee.selector)); + VestingAllocation(vestingAllocation).updateRecipient(newRecipient); + } +} diff --git a/test/amendement.t.sol b/test/amendement.t.sol index 89dd3b7..09b47d8 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -122,7 +122,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.startPrank(grantee); //log the current withdrawable - console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); + console.log(VestingAllocation(mockAllocation2).getAmountWithdrawable()); controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); diff --git a/test/controller.t.sol b/test/controller.t.sol index e46c255..451b252 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -940,10 +940,10 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { // function testConfirmingMilestoneRestrictedTokenAllocation() public { // address vestingAllocation = createDummyRestrictedTokenAward(); // uint256 snapshot = token.balanceOf(authority); -// RestrictedTokenAward(vestingAllocation).confirmMilestone(0); +// VestingAllocation(vestingAllocation).confirmMilestone(0); // vm.warp(block.timestamp + 50 seconds); // vm.startPrank(grantee); -// RestrictedTokenAward(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); // vm.stopPrank(); // } // @@ -1098,7 +1098,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { address vestingAllocation = createDummyVestingAllocation(); vm.warp(block.timestamp + 15 seconds); vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); //create call data to propose setting vesting to 0 bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); @@ -1122,7 +1122,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { address vestingAllocation = createDummyVestingAllocation(); vm.warp(block.timestamp + 5 seconds); vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); //create call data to propose setting vesting to 0 bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); @@ -1142,7 +1142,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.stopPrank(); vm.warp(block.timestamp + 155 seconds); vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); } @@ -1150,7 +1150,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { address vestingAllocation = createDummyVestingAllocation(); vm.warp(block.timestamp + 5 seconds); vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); //create call data to propose setting vesting to 0 bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); @@ -1170,7 +1170,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.stopPrank(); vm.warp(block.timestamp + 155 seconds); vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(RestrictedTokenAward(vestingAllocation).getAmountWithdrawable()); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); } From e30640310c97e5850f7b8911a5c7a8764309219e Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 7 Aug 2025 15:27:37 -0700 Subject: [PATCH 25/68] feat: pause/close minting --- src/MetaVesTController.sol | 12 +++++ .../zk-governance/IZkCappedMinterV2.sol | 9 ++++ test/VestingAllocation.t.sol | 50 ++++++++++++------ test/controller.t.sol | 52 +++++++++++++++++++ 4 files changed, 106 insertions(+), 17 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 740a30e..3ea6d91 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -913,6 +913,18 @@ contract metavestController is SafeTransferLib { emit MetaVesTController_ZkCappedMinterUpdated(zkCappedMinter); } + function pauseZkCappedMinter() external onlyAuthority { + IZkCappedMinterV2(zkCappedMinter).pause(); + } + + function unpauseZkCappedMinter() external onlyAuthority { + IZkCappedMinterV2(zkCappedMinter).unpause(); + } + + function closeZkCappedMinter() external onlyAuthority { + IZkCappedMinterV2(zkCappedMinter).close(); + } + function computeContractId( uint256 salt, string memory agreementUri, diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2.sol b/src/interfaces/zk-governance/IZkCappedMinterV2.sol index d8b2e2d..31f220c 100644 --- a/src/interfaces/zk-governance/IZkCappedMinterV2.sol +++ b/src/interfaces/zk-governance/IZkCappedMinterV2.sol @@ -4,10 +4,19 @@ pragma solidity ^0.8.24; interface IZkCappedMinterV2 { error ZkCappedMinterV2__CapExceeded(address minter, uint256 amount); + function DEFAULT_ADMIN_ROLE() external returns (bytes32); function MINTER_ROLE() external returns (bytes32); + function PAUSER_ROLE() external returns (bytes32); function START_TIME() external returns (uint48); function grantRole(bytes32 role, address account) external; function mint(address _to, uint256 _amount) external; + + function pause() external; + function unpause() external; + function paused() external view returns (bool); + + function close() external; + function closed() external view returns (bool); } diff --git a/test/VestingAllocation.t.sol b/test/VestingAllocation.t.sol index 965ef09..5f4d063 100644 --- a/test/VestingAllocation.t.sol +++ b/test/VestingAllocation.t.sol @@ -2,24 +2,21 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; -//import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import {ZkCappedMinterV2, IMintable} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; import {ZkTokenV1} from "zk-governance/l2-contracts/src/ZkTokenV1.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; import {VestingAllocation} from "../src/VestingAllocation.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; -//contract TestToken is ERC20 { -// uint8 _decimals; -// -// constructor(uint256 initialSupply, uint8 __decimals) ERC20("Test Token", "TestUSDC") { -// _decimals = __decimals; -// _mint(msg.sender, initialSupply); -// } -// -// function decimals() public view override returns (uint8) { -// return _decimals; -// } -//} +contract MockMetaVesTController { + address public authority; + + constructor( + address _authority + ) { + authority = _authority; + } +} contract VestingAllocationTest is Test { @@ -29,12 +26,15 @@ contract VestingAllocationTest is Test { ZkTokenV1 zkToken; + MockMetaVesTController mockController; VestingAllocation vestingAllocation; function setUp() public { zkToken = new ZkTokenV1(); zkToken.initialize(address(this), address(this), 0 ether); + mockController = new MockMetaVesTController(address(this)); + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); milestones[0] = BaseAllocation.Milestone({ milestoneAward: 2000 ether, @@ -46,7 +46,7 @@ contract VestingAllocationTest is Test { vestingAllocation = new VestingAllocation( grantee, recipient, - address(this), + address(mockController), BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -78,15 +78,16 @@ contract VestingAllocationTest is Test { zkCappedMinter.MINTER_ROLE(), address(vestingAllocation) ); + vm.prank(address(mockController)); vestingAllocation.setZkCappedMinterAddress(address(zkCappedMinter)); } - function test_metadata() public { + function test_Metadata() public { assertEq(vestingAllocation.grantee(), grantee, "Unexpected grantee"); assertEq(vestingAllocation.recipient(), recipient, "Unexpected recipient"); } - function test_withdraw() public { + function test_Withdraw() public { // Should withdraw to recipient by default uint256 balanceBefore = zkToken.balanceOf(recipient); @@ -112,8 +113,23 @@ contract VestingAllocationTest is Test { assertEq(zkToken.balanceOf(newRecipient), balanceBefore + 100 ether); } - function test_RevertIf_NonGranteeUpdateRecipient() public { + function test_RevertIf_UpdateRecipientNonGrantee() public { vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_OnlyGrantee.selector)); VestingAllocation(vestingAllocation).updateRecipient(newRecipient); } + + function test_Terminate() public { + // Controller should be able to terminate it + assertFalse(vestingAllocation.terminated(), "vesting contract should not be terminated yet"); + vm.prank(address(mockController)); + vm.expectEmit(true, true, true, true); + emit BaseAllocation.MetaVesT_Terminated(grantee, 0); // No token recovered because it is mint-on-demand + vestingAllocation.terminate(); + assertTrue(vestingAllocation.terminated(), "vesting contract should be terminated"); + } + + function test_RevertIf_TerminateNonController() public { + vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_OnlyController.selector)); + vestingAllocation.terminate(); + } } diff --git a/test/controller.t.sol b/test/controller.t.sol index 451b252..627026d 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -1505,4 +1505,56 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert ); } + + function test_TogglePauseMinting() public { + assertFalse(zkCappedMinter.paused(), "minter should not be paused yet"); + + // Authority should be able to pause minting through controller + vm.prank(authority); + controller.pauseZkCappedMinter(); + assertTrue(zkCappedMinter.paused(), "minter should be paused now"); + + vm.prank(authority); + controller.unpauseZkCappedMinter(); + assertFalse(zkCappedMinter.paused(), "minter should be unpaused now"); + } + + function test_RevertIf_PauseMintingNonGuardianSafe() public { + // Non-authority should not be able to pause minting through controller + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); + controller.pauseZkCappedMinter(); + + // Non-controller should not be able to pause minting directly + vm.expectRevert(abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(zkCappedMinter.PAUSER_ROLE()), 32) + )); + zkCappedMinter.pause(); + } + + function test_CloseMinting() public { + assertFalse(zkCappedMinter.closed(), "minter should not be closed yet"); + + // Authority should be able to close minting through controller + vm.prank(authority); + controller.closeZkCappedMinter(); + assertTrue(zkCappedMinter.closed(), "minter should be closed now"); + } + + function test_RevertIf_CloseMintingNonGuardianSafe() public { + // Non-authority should not be able to close minting through controller + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); + controller.closeZkCappedMinter(); + + // Non-controller should not be able to close minting directly + vm.expectRevert(abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(zkCappedMinter.DEFAULT_ADMIN_ROLE()), 32) + )); + zkCappedMinter.close(); + } } From 9e6eb467c6333480e97acaf922f1fa38091c6e70 Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 11 Aug 2025 09:04:58 -0700 Subject: [PATCH 26/68] feat: update agreement template fields --- test/lib/MetaVesTControllerTestBase.sol | 68 +++++++++++-------------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index 6df1c7b..20199fb 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -61,27 +61,24 @@ contract MetaVesTControllerTestBase is Test { ) ))); - globalFields = new string[](13); - globalFields[0] = "governingJurisdiction"; // TODO do we need this? - globalFields[1] = "disputeResolution"; // TODO do we need this? - globalFields[2] = "metavestType"; - globalFields[3] = "grantee"; - globalFields[4] = "recipient"; - globalFields[5] = "tokenContract"; - globalFields[6] = "tokenStreamTotal"; - globalFields[7] = "vestingCliffCredit"; - globalFields[8] = "unlockingCliffCredit"; - globalFields[9] = "vestingRate"; - globalFields[10] = "vestingStartTime"; - globalFields[11] = "unlockRate"; - globalFields[12] = "unlockStartTime"; - - partyFields = new string[](5); + globalFields = new string[](11); + globalFields[0] = "metavestType"; + globalFields[1] = "grantee"; + globalFields[2] = "recipient"; + globalFields[3] = "tokenContract"; + globalFields[4] = "tokenStreamTotal"; + globalFields[5] = "vestingCliffCredit"; + globalFields[6] = "unlockingCliffCredit"; + globalFields[7] = "vestingRate"; + globalFields[8] = "vestingStartTime"; + globalFields[9] = "unlockRate"; + globalFields[10] = "unlockStartTime"; + + partyFields = new string[](4); partyFields[0] = "name"; partyFields[1] = "evmAddress"; - partyFields[2] = "contactDetails"; // TODO do we need this? - partyFields[3] = "granteeType"; // TODO do we need this? - partyFields[4] = "granteeJurisdiction"; // TODO do we need this? + partyFields[2] = "contactDetails"; + partyFields[3] = "granteeType"; registry.createTemplate( templateId, @@ -132,29 +129,26 @@ contract MetaVesTControllerTestBase is Test { uint256 expiry, bytes memory expectRevertData ) internal returns(bytes32) { - string[] memory globalValues = new string[](13); - globalValues[0] = "test governingJurisdiction"; // TODO do we need this? - globalValues[1] = "test disputeResolution"; // TODO do we need this? - globalValues[2] = "0"; // metavestType: Vesting - globalValues[3] = vm.toString(grantee); // grantee - globalValues[4] = vm.toString(grantee); // recipient - globalValues[5] = vm.toString(allocation.tokenContract); // tokenContract - globalValues[6] = vm.toString(allocation.tokenStreamTotal); //tokenStreamTotal - globalValues[7] = vm.toString(allocation.vestingCliffCredit); // vestingCliffCredit - globalValues[8] = vm.toString(allocation.unlockingCliffCredit); // unlockingCliffCredit - globalValues[9] = vm.toString(allocation.vestingRate); // vestingRate - globalValues[10] = vm.toString(allocation.vestingStartTime); // vestingStartTime - globalValues[11] = vm.toString(allocation.unlockRate); // unlockRate - globalValues[12] = vm.toString(allocation.unlockStartTime); // unlockStartTime + string[] memory globalValues = new string[](11); + globalValues[0] = "0"; // metavestType: Vesting + globalValues[1] = vm.toString(grantee); // grantee + globalValues[2] = vm.toString(grantee); // recipient + globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract + globalValues[4] = vm.toString(allocation.tokenStreamTotal); //tokenStreamTotal + globalValues[5] = vm.toString(allocation.vestingCliffCredit); // vestingCliffCredit + globalValues[6] = vm.toString(allocation.unlockingCliffCredit); // unlockingCliffCredit + globalValues[7] = vm.toString(allocation.vestingRate); // vestingRate + globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime + globalValues[9] = vm.toString(allocation.unlockRate); // unlockRate + globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime // TODO what to do with milestones, which could be of dynamic lengths - string[] memory partyValues = new string[](5); + string[] memory partyValues = new string[](4); partyValues[0] = partyName; partyValues[1] = vm.toString(grantee); // evmAddress - partyValues[2] = "email@company.com"; // TODO do we need this? - partyValues[3] = "individual"; // TODO do we need this? - partyValues[4] = "test granteeJurisdiction"; // TODO do we need this? + partyValues[2] = "email@company.com"; + partyValues[3] = "individual"; address[] memory allParties = new address[](1); allParties[0] = grantee; From 6ebb078cb9bffa4e5c29bfd6d8c898bab6487222 Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 11 Aug 2025 10:53:04 -0700 Subject: [PATCH 27/68] chore: remove unused codes --- src/MetaVesTController.sol | 58 +------- src/RestrictedTokenAllocation.sol | 232 ------------------------------ src/RestrictedTokenFactory.sol | 26 ---- src/TokenOptionAllocation.sol | 223 ---------------------------- src/TokenOptionFactory.sol | 26 ---- test/amendement.t.sol | 6 +- test/controller.t.sol | 9 +- 7 files changed, 9 insertions(+), 571 deletions(-) delete mode 100644 src/RestrictedTokenAllocation.sol delete mode 100644 src/RestrictedTokenFactory.sol delete mode 100644 src/TokenOptionAllocation.sol delete mode 100644 src/TokenOptionFactory.sol diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 3ea6d91..0a0b761 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -8,10 +8,9 @@ pragma solidity ^0.8.24; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {ICyberAgreementRegistry} from "cybercorps-contracts/src/interfaces/ICyberAgreementRegistry.sol"; import "./BaseAllocation.sol"; -import "./RestrictedTokenAllocation.sol"; +//import "./RestrictedTokenAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; import "./interfaces/IPriceAllocation.sol"; import "./interfaces/zk-governance/IZkCappedMinterV2.sol"; @@ -28,24 +27,12 @@ import "./lib/EnumberableSet.sol"; * by an applicable affected grantee or a majority-in-governing power of similar token grantees **/ contract metavestController is SafeTransferLib { - using ECDSA for bytes32; using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.Bytes32Set; - - // Domain information - string public constant name = "metavestController"; - string public version; - bytes32 public DOMAIN_SEPARATOR; - // Type hash for SignedAgreementData - bytes32 public SIGNED_AGREEMENT_DATA_TYPEHASH; - bytes32 public ALLOCATION_TYPEHASH; - bytes32 public MILESTONE_TYPEHASH; - /// @dev opinionated time limit for a MetaVesT amendment, one calendar week in seconds uint256 internal constant AMENDMENT_TIME_LIMIT = 604800; uint256 internal constant ARRAY_LENGTH_LIMIT = 20; - mapping(bytes32 => EnumerableSet.AddressSet) private sets; EnumerableSet.Bytes32Set private setNames; @@ -59,7 +46,6 @@ contract metavestController is SafeTransferLib { address public ZkTokenAddress; address internal _pendingAuthority; address internal _pendingDao; - uint256 public metavestCounter; struct AmendmentProposal { bool isPending; @@ -87,11 +73,6 @@ contract metavestController is SafeTransferLib { BaseAllocation.Milestone[] milestones; } - struct Delegation { - address delegate; - uint256 expiry; - } - enum metavestType { Vesting, TokenOption, RestrictedTokenAward } /// @notice maps a function's signature to a Condition contract address @@ -126,7 +107,6 @@ contract metavestController is SafeTransferLib { event MetaVesTController_SetRemoved(string indexed set); event MetaVesTController_AddressAddedToSet(string set, address indexed grantee); event MetaVesTController_AddressRemovedFromSet(string set, address indexed grantee); - event MetaVesTController_MetaVestCreated(address indexed metavest, bytes32 agreementId); event MetaVesTController_ZkCappedMinterUpdated(address zkCappedMinter); event MetaVesTController_DealProposed( bytes32 indexed agreementId, @@ -142,8 +122,6 @@ contract metavestController is SafeTransferLib { bytes32 indexed agreementId, address metavest ); - event MetaVesTController_DelegationSet(address indexed delegator, address indexed delegate, uint256 expiry); - event MetaVesTController_DelegationRevoked(address indexed delegator, address indexed delegate); /// /// ERRORS @@ -244,31 +222,6 @@ contract metavestController is SafeTransferLib { // tokenOptionFactory = _tokenOptionFactory; // restrictedTokenFactory = _restrictedTokenFactory; dao = _dao; - - version = "1"; - DOMAIN_SEPARATOR = keccak256( - abi.encode( - keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ), - keccak256(bytes(name)), - keccak256(bytes(version)), - block.chainid, - address(this) - ) - ); - - SIGNED_AGREEMENT_DATA_TYPEHASH = keccak256( - "SignedAgreementData(bytes32 id,string agreementUri,metavestType _metavestType,address grantee,address recipient,Allocation allocation,Milestone[] milestones)" - ); - - ALLOCATION_TYPEHASH = keccak256( - "Allocation(uint256 tokenStreamTotal,uint128 vestingCliffCredit,uint128 unlockingCliffCredit,uint160 vestingRate,uint48 vestingStartTime,uint160 unlockRate,uint48 unlockStartTime,address tokenContract)" - ); - - MILESTONE_TYPEHASH = keccak256( - "Milestone(uint256 milestoneAward,bool unlockOnCompletion,bool complete,address[] conditionContracts)" - ); } /// @notice for a grantee to consent to an update to one of their metavestDetails by 'authority' corresponding to the applicable function in this controller @@ -440,13 +393,6 @@ contract metavestController is SafeTransferLib { } } - function validateTokenApprovalAndBalance(address tokenContract, uint256 total) internal view { - if ( - IERC20M(tokenContract).allowance(authority, address(this)) < total || - IERC20M(tokenContract).balanceOf(authority) < total - ) revert MetaVesTController_AmountNotApprovedForTransferFrom(); - } - // function createAndInitializeTokenOptionAllocation( // address _grantee, // address _paymentToken, @@ -496,7 +442,6 @@ contract metavestController is SafeTransferLib { uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; if (_total == 0) revert MetaVesTController_ZeroAmount(); - //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); address vestingAllocation = IAllocationFactory(vestingFactory).createAllocation( IAllocationFactory.AllocationType.Vesting, @@ -509,7 +454,6 @@ contract metavestController is SafeTransferLib { 0, 0 ); - //safeTransferFrom(_allocation.tokenContract, authority, vestingAllocation, _total); return vestingAllocation; } diff --git a/src/RestrictedTokenAllocation.sol b/src/RestrictedTokenAllocation.sol deleted file mode 100644 index f527e3b..0000000 --- a/src/RestrictedTokenAllocation.sol +++ /dev/null @@ -1,232 +0,0 @@ -// TODO WIP -//// SPDX-License-Identifier: AGPL-3.0-only -//import "./BaseAllocation.sol"; -// -//pragma solidity ^0.8.24; -// -//contract RestrictedTokenAward is BaseAllocation { -// -// /// @notice address of payment token used for token option exercises or restricted token repurchases -// address public immutable paymentToken; -// uint256 public shortStopDuration; -// uint256 public shortStopDate; -// uint256 public repurchasePrice; -// uint256 public tokensRepurchased; -// uint256 public tokensRepurchasedWithdrawn; -// -// /// @notice Constructor to deploy a new RestrictedTokenAward -// /// @param _grantee - address of the grantee -// /// @param _controller - address of the controller -// /// @param _paymentToken - address of the payment token -// /// @param _repurchasePrice - price at which the restricted tokens can be repurchased in vesting token decimals but only up to payment decimal precision -// /// @param _shortStopDuration - duration after termination during which restricted tokens can be repurchased -// /// @param _allocation - allocation details as an Allocation struct -// /// @param _milestones - milestones with their conditions and awards -// constructor ( -// address _grantee, -// address _controller, -// address _paymentToken, -// uint256 _repurchasePrice, -// uint256 _shortStopDuration, -// Allocation memory _allocation, -// Milestone[] memory _milestones -// ) BaseAllocation( -// _grantee, -// _controller -// ) { -// //perform input validation -// if (_allocation.tokenContract == address(0)) revert MetaVesT_ZeroAddress(); -// if (_allocation.tokenStreamTotal == 0) revert MetaVesT_ZeroAmount(); -// if (_allocation.vestingRate > 1000*1e18 || _allocation.unlockRate > 1000*1e18) revert MetaVesT_RateTooHigh(); -// -// //set vesting allocation variables -// allocation.tokenContract = _allocation.tokenContract; -// allocation.tokenStreamTotal = _allocation.tokenStreamTotal; -// allocation.vestingCliffCredit = _allocation.vestingCliffCredit; -// allocation.unlockingCliffCredit = _allocation.unlockingCliffCredit; -// allocation.vestingRate = _allocation.vestingRate; -// allocation.vestingStartTime = _allocation.vestingStartTime; -// allocation.unlockRate = _allocation.unlockRate; -// allocation.unlockStartTime = _allocation.unlockStartTime; -// -// // set token option variables -// repurchasePrice = _repurchasePrice; -// shortStopDuration = _shortStopDuration; -// -// paymentToken = _paymentToken; -// -// // manually copy milestones -// for (uint256 i; i < _milestones.length; ++i) { -// milestones.push(_milestones[i]); -// } -// } -// -// /// @notice returns the vesting type for RestrictedTokenAward -// /// @return uint256 type 3 -// function getVestingType() external pure override returns (uint256) { -// return 3; -// } -// -// /// @notice returns the governing power for RestrictedTokenAward based on the govType -// /// @return uint256 governingPower for this RestrictedTokenAward contract -// function getGoverningPower() external view override returns (uint256) { -// uint256 governingPower; -// if(govType==GovType.all) -// { -// uint256 totalMilestoneAward = 0; -// for(uint256 i; i < milestones.length; ++i) -// { -// totalMilestoneAward += milestones[i].milestoneAward; -// } -// governingPower = (allocation.tokenStreamTotal + totalMilestoneAward) - tokensWithdrawn; -// } -// else if(govType==GovType.vested) -// governingPower = getVestedTokenAmount() - tokensWithdrawn; -// else -// governingPower = _min(getVestedTokenAmount(), getUnlockedTokenAmount()) - tokensWithdrawn; -// -// return governingPower; -// } -// -// /// @notice updates the short stop time of the vesting contract -// /// @dev onlyController -- must be called from the metavest controller -// /// @param _shortStopTime - new short stop time to be set in seconds -// function updateStopTimes(uint48 _shortStopTime) external override onlyController { -// if(terminated) revert MetaVesT_AlreadyTerminated(); -// shortStopDuration = _shortStopTime; -// emit MetaVesT_StopTimesUpdated(grantee, _shortStopTime); -// } -// -// /// @notice updates the exercise price -// /// @dev onlyController -- must be called from the metavest controller -// /// @param _newPrice - the new exercise price in vesting token decimals but only up to payment decimal precision -// function updatePrice(uint256 _newPrice) external onlyController { -// if(terminated) revert MetaVesT_AlreadyTerminated(); -// repurchasePrice = _newPrice; -// emit MetaVesT_PriceUpdated(grantee, _newPrice); -// } -// -// /// @notice gets the payment amount for a given amount of tokens -// /// @param _amount - the amount of tokens to calculate the payment amount in the vesting token decimals -// /// @return paymentAmount - the payment amount for the given token amount in the payment token decimals -// function getPaymentAmount(uint256 _amount) public view returns (uint256) { -// uint8 paymentDecimals = IERC20M(paymentToken).decimals(); -// uint8 repurchaseTokenDecimals = IERC20M(allocation.tokenContract).decimals(); -// -// // Calculate paymentAmount -// uint256 paymentAmount; -// paymentAmount = _amount * repurchasePrice / (10**repurchaseTokenDecimals); -// -// //scale paymentAmount to payment token decimals -// if(paymentDecimals getAmountRepurchasable()) revert MetaVesT_MoreThanAvailable(); -// if(block.timestampIERC20M(allocation.tokenContract).balanceOf(address(this))) -// repurchaseAmount = IERC20M(allocation.tokenContract).balanceOf(address(this)); -// return repurchaseAmount; -// } -// -// /// @notice returns the amount of tokens that are vested -// /// @return uint256 amount of tokens that are vested -// function getVestedTokenAmount() public view returns (uint256) { -// if(block.timestampallocation.tokenStreamTotal) -// _tokensVested = allocation.tokenStreamTotal; -// return _tokensVested += milestoneAwardTotal; -// } -// -// /// @notice returns the amount of tokens that are unlocked -// /// @return uint256 amount of tokens that are unlocked -// function getUnlockedTokenAmount() public view returns (uint256) { -// if(block.timestampallocation.tokenStreamTotal + milestoneAwardTotal) -// _tokensUnlocked = allocation.tokenStreamTotal + milestoneAwardTotal; -// -// return _tokensUnlocked += milestoneUnlockedTotal; -// } -// -// /// @notice returns the amount of tokens that can be withdrawn -// /// @return uint256 amount of tokens that can be withdrawn -// function getAmountWithdrawable() public view override returns (uint256) { -// uint256 _tokensVested = getVestedTokenAmount(); -// uint256 _tokensUnlocked = getUnlockedTokenAmount(); -// uint256 withdrawableAmount = _min(_tokensVested, _tokensUnlocked); -// if(withdrawableAmount>tokensWithdrawn) -// return withdrawableAmount - tokensWithdrawn; -// else -// return 0; -// } -// -//} diff --git a/src/RestrictedTokenFactory.sol b/src/RestrictedTokenFactory.sol deleted file mode 100644 index 20c7f21..0000000 --- a/src/RestrictedTokenFactory.sol +++ /dev/null @@ -1,26 +0,0 @@ -// TODO WIP -//// SPDX-License-Identifier: GPL-3.0 -//pragma solidity ^0.8.24; -// -//import "./RestrictedTokenAllocation.sol"; -//import "./interfaces/IAllocationFactory.sol"; -// -//contract RestrictedTokenFactory is IAllocationFactory { -// -// function createAllocation( -// AllocationType _allocationType, -// address _grantee, -// address _controller, -// RestrictedTokenAward.Allocation memory _allocation, -// RestrictedTokenAward.Milestone[] memory _milestones, -// address _paymentToken, -// uint256 _exercisePrice, -// uint256 _shortStopDuration -// ) external returns (address) { -// if (_allocationType == AllocationType.RestrictedToken) { -// return address(new RestrictedTokenAward(_grantee, _controller, _paymentToken, _exercisePrice, _shortStopDuration, _allocation, _milestones)); -// } else { -// revert("AllocationFactory: invalid allocation type"); -// } -// } -//} diff --git a/src/TokenOptionAllocation.sol b/src/TokenOptionAllocation.sol deleted file mode 100644 index 520918b..0000000 --- a/src/TokenOptionAllocation.sol +++ /dev/null @@ -1,223 +0,0 @@ -// TODO WIP -//// SPDX-License-Identifier: AGPL-3.0-only -//import "./BaseAllocation.sol"; -// -//pragma solidity ^0.8.24; -// -//contract TokenOptionAllocation is BaseAllocation { -// -// /// @notice address of payment token used for token option exercises or restricted token repurchases -// address public immutable paymentToken; -// uint256 public tokensExercised; -// uint256 public exercisePrice; -// uint256 public shortStopDuration; -// uint256 public shortStopTime; -// -// event MetaVesT_TokenOptionExercised(address indexed _grantee, uint256 _tokensToExercise, uint256 _paymentAmount); -// -// /// @notice Constructor to create a TokenOptionAllocation -// /// @param _grantee - address of the grantee -// /// @param _controller - address of the controller -// /// @param _paymentToken - address of the payment token -// /// @param _exercisePrice - price of the token option exercise in vesting token decimals but only up to payment decimal precision -// /// @param _shortStopDuration - duration of the short stop -// /// @param _allocation - allocation details as an Allocation struct -// /// @param _milestones - milestones with conditions and awards -// constructor ( -// address _grantee, -// address _controller, -// address _paymentToken, -// uint256 _exercisePrice, -// uint256 _shortStopDuration, -// Allocation memory _allocation, -// Milestone[] memory _milestones -// ) BaseAllocation( -// _grantee, -// _controller -// ) { -// //perform input validation -// if (_allocation.tokenContract == address(0)) revert MetaVesT_ZeroAddress(); -// if (_allocation.tokenStreamTotal == 0) revert MetaVesT_ZeroAmount(); -// if (_allocation.vestingRate > 1000*1e18 || _allocation.unlockRate > 1000*1e18) revert MetaVesT_RateTooHigh(); -// -// //set vesting allocation variables -// allocation.tokenContract = _allocation.tokenContract; -// allocation.tokenStreamTotal = _allocation.tokenStreamTotal; -// allocation.vestingCliffCredit = _allocation.vestingCliffCredit; -// allocation.unlockingCliffCredit = _allocation.unlockingCliffCredit; -// allocation.vestingRate = _allocation.vestingRate; -// allocation.vestingStartTime = _allocation.vestingStartTime; -// allocation.unlockRate = _allocation.unlockRate; -// allocation.unlockStartTime = _allocation.unlockStartTime; -// -// // set token option variables -// exercisePrice = _exercisePrice; -// shortStopDuration = _shortStopDuration; -// -// paymentToken = _paymentToken; -// -// // manually copy milestones -// for (uint256 i; i < _milestones.length; ++i) { -// milestones.push(_milestones[i]); -// } -// } -// -// /// @notice returns the contract vesting type 2 for TokenOptionAllocation -// /// @return 2 -// function getVestingType() external pure override returns (uint256) { -// return 2; -// } -// -// /// @notice returns the governing power of the TokenOptionAllocation -// /// @return governingPower - the governing power of the TokenOptionAllocation based on the governance setting -// function getGoverningPower() external view override returns (uint256) { -// uint256 governingPower; -// if(govType==GovType.all) -// { -// uint256 totalMilestoneAward = 0; -// for(uint256 i; i < milestones.length; ++i) -// { -// totalMilestoneAward += milestones[i].milestoneAward; -// } -// governingPower = (allocation.tokenStreamTotal + totalMilestoneAward) - tokensWithdrawn; -// } -// else if(govType==GovType.vested) -// governingPower = tokensExercised - tokensWithdrawn; -// else -// governingPower = _min(tokensExercised, getUnlockedTokenAmount()) - tokensWithdrawn; -// -// return governingPower; -// } -// -// /// @notice updates the short stop time of the TokenOptionAllocation -// /// @dev onlyController -- must be called from the metavest controller -// /// @param _shortStopTime - the new short stop time -// function updateStopTimes(uint48 _shortStopTime) external override onlyController { -// if(terminated) revert MetaVesT_AlreadyTerminated(); -// shortStopDuration = _shortStopTime; -// emit MetaVesT_StopTimesUpdated(grantee, _shortStopTime); -// } -// -// /// @notice updates the exercise price -// /// @dev onlyController -- must be called from the metavest controller -// /// @param _newPrice - the new exercise price in vesting token decimals but only up to payment decimal precision -// function updatePrice(uint256 _newPrice) external onlyController { -// if(terminated) revert MetaVesT_AlreadyTerminated(); -// exercisePrice = _newPrice; -// emit MetaVesT_PriceUpdated(grantee, _newPrice); -// } -// -// /// @notice gets the payment amount for a given amount of tokens -// /// @param _amount - the amount of tokens to calculate the payment amount in the vesting token decimals -// /// @return paymentAmount - the payment amount for the given token amount in the payment token decimals -// function getPaymentAmount(uint256 _amount) public view returns (uint256) { -// uint8 paymentDecimals = IERC20M(paymentToken).decimals(); -// uint8 exerciseTokenDecimals = IERC20M(allocation.tokenContract).decimals(); -// -// // Calculate paymentAmount -// uint256 paymentAmount; -// paymentAmount = _amount * exercisePrice / (10**exerciseTokenDecimals); -// -// //scale paymentAmount to payment token decimals -// if(paymentDecimalsshortStopTime && terminated) revert MetaVest_ShortStopDatePassed(); -// if (_tokensToExercise == 0) revert MetaVesT_ZeroAmount(); -// if (_tokensToExercise > getAmountExercisable()) revert MetaVesT_MoreThanAvailable(); -// -// // Calculate paymentAmount -// uint256 paymentAmount = getPaymentAmount(_tokensToExercise); -// if(paymentAmount == 0) revert MetaVesT_TooSmallAmount(); -// -// safeTransferFrom(paymentToken, msg.sender, getAuthority(), paymentAmount); -// tokensExercised += _tokensToExercise; -// emit MetaVesT_TokenOptionExercised(msg.sender, _tokensToExercise, paymentAmount); -// } -// -// /// @notice Allows the controller to terminate the TokenOptionAllocation -// /// @dev onlyController -- must be called from the metavest controller -// function terminate() external override onlyController nonReentrant { -// if(terminated) revert MetaVesT_AlreadyTerminated(); -// -// uint256 milestonesAllocation = 0; -// for (uint256 i; i < milestones.length; ++i) { -// milestonesAllocation += milestones[i].milestoneAward; -// } -// uint256 tokensToRecover = allocation.tokenStreamTotal + milestonesAllocation - getAmountExercisable() - tokensExercised; -// terminationTime = block.timestamp; -// shortStopTime = block.timestamp + shortStopDuration; -// safeTransfer(allocation.tokenContract, getAuthority(), tokensToRecover); -// terminated = true; -// emit MetaVesT_Terminated(grantee, tokensToRecover); -// } -// -// /// @notice recovers any forfeited tokens after the short stop time -// /// @dev onlyAuthority -- must be called from the authority -// function recoverForfeitTokens() external onlyAuthority nonReentrant { -// if(block.timestamp tokensExercised - tokensWithdrawn) -// tokensToRecover = IERC20M(allocation.tokenContract).balanceOf(address(this)) + tokensWithdrawn - tokensExercised; -// safeTransfer(allocation.tokenContract, getAuthority(), tokensToRecover); -// } -// -// /// @notice gets the amount of tokens available for a grantee to exercise -// /// @return uint256 amount of tokens available for the grantee to exercise -// function getAmountExercisable() public view returns (uint256) { -// if(block.timestampshortStopTime && shortStopTime>0)) -// return 0; -// -// uint256 _timeElapsedSinceVest = block.timestamp - allocation.vestingStartTime; -// if(terminated) -// _timeElapsedSinceVest = terminationTime - allocation.vestingStartTime; -// -// uint256 _tokensVested = (_timeElapsedSinceVest * allocation.vestingRate) + allocation.vestingCliffCredit; -// -// if(_tokensVested>allocation.tokenStreamTotal) -// _tokensVested = allocation.tokenStreamTotal; -// uint256 _tokensExercisable = _tokensVested + milestoneAwardTotal; -// if(_tokensExercisable>tokensExercised) -// return _tokensExercisable - tokensExercised; -// else -// return 0; -// } -// -// /// @notice gets the amount of tokens unlocked for a grantee -// /// @return uint256 amount of tokens unlocked for the grantee -// function getUnlockedTokenAmount() public view returns (uint256) { -// if(block.timestampallocation.tokenStreamTotal + milestoneAwardTotal) -// _tokensUnlocked = allocation.tokenStreamTotal + milestoneAwardTotal; -// -// return _tokensUnlocked + milestoneUnlockedTotal; -// } -// -// /// @notice gets the amount of tokens available for a grantee to withdraw -// /// @return uint256 amount of tokens available for the grantee to withdraw -// function getAmountWithdrawable() public view override returns (uint256) { -// uint256 _tokensUnlocked = getUnlockedTokenAmount(); -// -// uint256 withdrawableAmount = _min(tokensExercised, _tokensUnlocked); -// if(withdrawableAmount>tokensWithdrawn) -// return withdrawableAmount - tokensWithdrawn; -// else -// return 0; -// } -// -//} diff --git a/src/TokenOptionFactory.sol b/src/TokenOptionFactory.sol deleted file mode 100644 index c708a5f..0000000 --- a/src/TokenOptionFactory.sol +++ /dev/null @@ -1,26 +0,0 @@ -// TODO WIP -//// SPDX-License-Identifier: GPL-3.0 -//pragma solidity ^0.8.24; -// -//import "./TokenOptionAllocation.sol"; -//import "./interfaces/IAllocationFactory.sol"; -// -//contract TokenOptionFactory is IAllocationFactory { -// -// function createAllocation( -// AllocationType _allocationType, -// address _grantee, -// address _controller, -// TokenOptionAllocation.Allocation memory _allocation, -// TokenOptionAllocation.Milestone[] memory _milestones, -// address _paymentToken, -// uint256 _exercisePrice, -// uint256 _shortStopDuration -// ) external returns (address) { -// if (_allocationType == AllocationType.TokenOption) { -// return address(new TokenOptionAllocation(_grantee, _controller, _paymentToken, _exercisePrice, _shortStopDuration, _allocation, _milestones)); -// } else { -// revert("AllocationFactory: invalid allocation type"); -// } -// } -//} diff --git a/test/amendement.t.sol b/test/amendement.t.sol index 09b47d8..62fc961 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; import "../src/BaseAllocation.sol"; -import "../src/RestrictedTokenAllocation.sol"; +//import "../src/RestrictedTokenAllocation.sol"; import "../src/interfaces/IAllocationFactory.sol"; import "../src/VestingAllocationFactory.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/RestrictedTokenFactory.sol"; +//import "../src/TokenOptionFactory.sol"; +//import "../src/RestrictedTokenFactory.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; import "./lib/MetaVesTControllerTestBase.sol"; diff --git a/test/controller.t.sol b/test/controller.t.sol index 627026d..d057455 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.20; -import "../src/RestrictedTokenAllocation.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "../src/TokenOptionAllocation.sol"; -import "../src/TokenOptionFactory.sol"; +//import "../src/RestrictedTokenAllocation.sol"; +//import "../src/RestrictedTokenFactory.sol"; +//import "../src/TokenOptionAllocation.sol"; +//import "../src/TokenOptionFactory.sol"; import "../src/VestingAllocation.sol"; import "../src/VestingAllocationFactory.sol"; import "../src/interfaces/IAllocationFactory.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; import "./lib/MetaVesTControllerTestBase.sol"; import "./mocks/MockCondition.sol"; +import {Strings} from "zk-governance/l2-contracts/lib/openzeppelin-contracts/contracts/utils/Strings.sol"; contract MetaVestControllerTest is MetaVesTControllerTestBase { address authority = guardianSafe; From f4614e009f713bc2c974d8bca53b43a99b2ce875 Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 12 Aug 2025 13:05:26 -0700 Subject: [PATCH 28/68] feat: improve deal proposal flow --- src/MetaVesTController.sol | 115 ++++++++----- test/ZkGuardianCompensation.t.sol | 175 ++++++------------- test/amendement.t.sol | 21 ++- test/controller.t.sol | 212 ++++++++++++------------ test/lib/MetaVesTControllerTestBase.sol | 114 +++++++++++-- 5 files changed, 340 insertions(+), 297 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 0a0b761..edb2a95 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -26,6 +26,7 @@ import "./lib/EnumberableSet.sol"; * other permissioned functions, with some powers checked by the applicable 'dao' or subject to consent * by an applicable affected grantee or a majority-in-governing power of similar token grantees **/ +// TODO make it upgradeable contract metavestController is SafeTransferLib { using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -47,6 +48,9 @@ contract metavestController is SafeTransferLib { address internal _pendingAuthority; address internal _pendingDao; + // Simple indexer for UX + bytes32[] public dealIds; + struct AmendmentProposal { bool isPending; bytes32 dataHash; @@ -68,7 +72,6 @@ contract metavestController is SafeTransferLib { bytes32 agreementId; metavestType _metavestType; address grantee; - address recipient; BaseAllocation.Allocation allocation; BaseAllocation.Milestone[] milestones; } @@ -92,6 +95,9 @@ contract metavestController is SafeTransferLib { /// @notice granteeId => granteeData mapping(bytes32 => DealData) public deals; + /// @notice Maps agreement IDs to arrays of counter party values for closed deals. + mapping(bytes32 => string[]) public counterPartyValues; + /// /// EVENTS /// @@ -111,7 +117,6 @@ contract metavestController is SafeTransferLib { event MetaVesTController_DealProposed( bytes32 indexed agreementId, address indexed grantee, - address indexed recipient, metavestType metavestType, BaseAllocation.Allocation allocation, BaseAllocation.Milestone[] milestones, @@ -120,6 +125,7 @@ contract metavestController is SafeTransferLib { ); event MetaVesTController_DealFinalizedAndMetaVestCreated( bytes32 indexed agreementId, + address indexed recipient, address metavest ); @@ -158,6 +164,10 @@ contract metavestController is SafeTransferLib { error MetaVesTController_StringTooLong(); error MetaVesTController_TypeNotSupported(metavestType _type); error MetaVesTController_DealAlreadyFinalized(); + error MetaVesTController_DealVoided(); + error MetaVesTController_CounterPartyNotFound(); + error MetaVesTController_PartyValuesLengthMismatch(); + error MetaVesTController_CounterPartyValueMismatch(); /// /// FUNCTIONS @@ -264,74 +274,99 @@ contract metavestController is SafeTransferLib { emit MetaVesTController_ConditionUpdated(_condition, _functionSig); } + // It can be called by anyone but must have DAO's or delegate's signature function proposeAndSignDeal( bytes32 templateId, uint256 salt, metavestType _metavestType, address grantee, - address recipient, BaseAllocation.Allocation calldata allocation, BaseAllocation.Milestone[] calldata milestones, string[] memory globalValues, - string[] memory partyValues, + address[] memory parties, + string[][] memory partyValues, bytes calldata signature, bytes32 secretHash, uint256 expiry - ) external onlyAuthority returns (bytes32) { - address[] memory allParties = new address[](1); - allParties[0] = grantee; - string[][] memory allPartyValues = new string[][](1); - allPartyValues[0] = partyValues; + ) external returns (bytes32) { bytes32 agreementId = ICyberAgreementRegistry(registry).createContract( templateId, salt, globalValues, - allParties, - allPartyValues, + parties, + partyValues, secretHash, address(this), expiry ); - deals[agreementId] = DealData({ - agreementId: agreementId, - _metavestType: _metavestType, - grantee: grantee, - recipient: recipient, - allocation: allocation, - milestones: milestones - }); + if (partyValues.length < 2) revert MetaVesTController_CounterPartyNotFound(); + if (partyValues[1].length != partyValues[0].length) revert MetaVesTController_PartyValuesLengthMismatch(); + counterPartyValues[agreementId] = partyValues[1]; ICyberAgreementRegistry(registry).signContractFor( - grantee, + authority, // First party (grantor) should always be the authority agreementId, - allPartyValues[0], + partyValues[0], signature, false, // Not meant for anyone else other than the signer "" // Signer == proposer, no secret needed ); + deals[agreementId] = DealData({ + agreementId: agreementId, + _metavestType: _metavestType, + grantee: grantee, + allocation: allocation, + milestones: milestones + }); + dealIds.push(agreementId); + emit MetaVesTController_DealProposed( - agreementId, grantee, recipient, _metavestType, allocation, milestones, + agreementId, grantee, _metavestType, allocation, milestones, secretHash > 0, registry ); return agreementId; } - function createMetavest(bytes32 agreementId) external conditionCheck returns (address) - { - if (ICyberAgreementRegistry(registry).isFinalized(agreementId)) { - revert MetaVesTController_DealAlreadyFinalized(); - } + function signDealAndCreateMetavest( + address grantee, + address recipient, + bytes32 agreementId, + string[] memory partyValues, + bytes memory signature, + string memory secret + ) external conditionCheck returns (address) { + // Finalize agreement + + if(ICyberAgreementRegistry(registry).isVoided(agreementId)) revert MetaVesTController_DealVoided(); + if(ICyberAgreementRegistry(registry).isFinalized(agreementId)) revert MetaVesTController_DealAlreadyFinalized(); + + string[] storage counterPartyCheck = counterPartyValues[agreementId]; + if (keccak256(abi.encode(counterPartyCheck)) != keccak256(abi.encode(partyValues))) revert MetaVesTController_CounterPartyValueMismatch(); + + ICyberAgreementRegistry(registry).signContractFor(grantee, agreementId, partyValues, signature, false, secret); + + ICyberAgreementRegistry(registry).finalizeContract(agreementId); + + // Create and provision MetaVesT + + address newMetavest = _createMetavest(agreementId, recipient); + + emit MetaVesTController_DealFinalizedAndMetaVestCreated(agreementId, recipient, newMetavest); + + return newMetavest; + } + function _createMetavest(bytes32 agreementId, address recipient) internal returns (address) { DealData storage deal = deals[agreementId]; address newMetavest; if(deal._metavestType == metavestType.Vesting) { - newMetavest = createVestingAllocation(deal.grantee, deal.recipient, deal.allocation, deal.milestones); + newMetavest = createVestingAllocation(deal.grantee, recipient, deal.allocation, deal.milestones); } else if(deal._metavestType == metavestType.TokenOption) { @@ -354,9 +389,6 @@ contract metavestController is SafeTransferLib { ); BaseAllocation(newMetavest).setZkCappedMinterAddress(address(zkCappedMinter)); - ICyberAgreementRegistry(registry).finalizeContract(agreementId); - - emit MetaVesTController_DealFinalizedAndMetaVestCreated(agreementId, newMetavest); return newMetavest; } @@ -869,18 +901,17 @@ contract metavestController is SafeTransferLib { IZkCappedMinterV2(zkCappedMinter).close(); } - function computeContractId( - uint256 salt, - string memory agreementUri, - address grantee, - address recipient, - BaseAllocation.Allocation memory allocation, - BaseAllocation.Milestone[] memory milestones - ) public pure returns (bytes32) { - return keccak256(abi.encode(salt, agreementUri, grantee, recipient, allocation, milestones)); - } - function getDeal(bytes32 agreementId) public view returns (DealData memory) { return deals[agreementId]; } + + // Simple indexer for UX + + function getNumberOfDeals() public view returns(uint256) { + return dealIds.length; + } + + function getDealId(uint256 index) public view returns(bytes32) { + return dealIds[index]; + } } diff --git a/test/ZkGuardianCompensation.t.sol b/test/ZkGuardianCompensation.t.sol index ab3b29c..9b4bb4f 100644 --- a/test/ZkGuardianCompensation.t.sol +++ b/test/ZkGuardianCompensation.t.sol @@ -54,14 +54,12 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { function test_GuardianCompensationYear1_2() public { // Assume ZK Capped Minter and its MetaVesTController counterpart are already deployed - (bytes32 contractIdAlice, bytes32 contractIdBob) = _guardiansSignAndTppPass(); + (address metavestAddressAlice, address metavestAddressBob) = _guardiansSignAndTppPass(); - // Anyone can create MetaVesT for Alice and Bob (per agreements) to start vesting - - VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); + VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddressAlice); assertEq(zkToken.balanceOf(address(vestingAllocationAlice)), 0, "Alice's vesting contract should not have any token (it mints on-demand)"); - VestingAllocation vestingAllocationBob = VestingAllocation(controller.createMetavest(contractIdBob)); + VestingAllocation vestingAllocationBob = VestingAllocation(metavestAddressBob); assertEq(zkToken.balanceOf(address(vestingAllocationBob)), 0, "Vesting contract should not have any token (it mints on-demand)"); // Grantees should not be able to withdraw yet @@ -91,25 +89,29 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { } function test_AdminToolingCompensation() public { - (bytes32 contractIdAlice, bytes32 contractIdBob) = _guardiansSignAndTppPass(); + (address metavestAddressAlice, address metavestAddressBob) = _guardiansSignAndTppPass(); + VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddressAlice); // Vesting starts and a month has passed vm.warp(cappedMinterStartTime + 30 days); - // Alice creates vesting contract and start withdrawal - VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); _granteeWithdrawAndAsserts(vestingAllocationAlice, uint256(50e3 ether) + uint160(50e3 ether) / 365 days * 30 days, "Alice cliff + first month"); // Second month skip(30 days); // Add new grantee for admin/tooling compensation + + // Guardian SAFE to delegate signing to an EOA + vm.prank(guardianSafe); + registry.setDelegation(delegate, block.timestamp + 60); + assertTrue(registry.isValidDelegate(guardianSafe, delegate), "delegate should be Guardian SAFE's delegate"); + bytes32 contractIdChad = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, chad, - chadPrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), // 10k ZK total in one cliff @@ -125,127 +127,29 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { "Chad", cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); + VestingAllocation vestingAllocationChad = VestingAllocation(_granteeSignDeal( + contractIdChad, + chad, // grantee + chad, // recipient + chadPrivateKey, + "Chad" + )); _granteeWithdrawAndAsserts(vestingAllocationChad, 10e3 ether, "Chad cliff"); } - function test_RevertIf_NotAuthority() public { - // Non Guardian SAFE should not be able to accept agreement and create contract - _proposeAndSignDeal( - templateId, - block.timestamp, // salt - deployer, // Not authority - alice, - alicePrivateKey, - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 10e18, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - "Alice", - cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible - abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert - ); - } - - function test_RevertIf_IncorrectAgreementSignature() public { - // Register Alice with someone else's signature should fail - _proposeAndSignDeal( - templateId, - block.timestamp, // salt - guardianSafe, - alice, - bobPrivateKey, // Use someone else to sign - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 10e18, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - "Alice", - cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible - abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert - ); - } - - function test_DelegateSignature() public { - // Alice to delegate to Bob - vm.prank(alice); - registry.setDelegation(bob, block.timestamp + 60); - assertTrue(registry.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); - - // Bob should be able to sign for Alice now - bytes32 agreementId = _proposeAndSignDeal( - templateId, - block.timestamp, // salt - guardianSafe, - alice, - bobPrivateKey, // Use Bob to sign - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 10e18, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - "Alice", - cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible - ); - metavestController.DealData memory deal = controller.getDeal(agreementId); - assertEq(deal.grantee, alice, "Alice should be the grantee"); - - // Wait until expiry - skip(61); - - // Bob should no longer be able to sign for Alice - assertFalse(registry.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); - _proposeAndSignDeal( - templateId, - block.timestamp, // salt - guardianSafe, - alice, - bobPrivateKey, // Use Bob to sign - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 10e18, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - "Alice", - cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible - abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert - ); - } + function _guardiansSignAndTppPass() internal returns(address, address) { + // Guardian SAFE to delegate signing to an EOA + vm.prank(guardianSafe); + registry.setDelegation(delegate, block.timestamp + 60); + assertTrue(registry.isValidDelegate(guardianSafe, delegate), "delegate should be Guardian SAFE's delegate"); - function _guardiansSignAndTppPass() internal returns(bytes32, bytes32) { - // Guardians to sign agreements and register on MetaVesTController + // Guardian SAFE to propose deals on MetaVesTController bytes32 contractIdAlice = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, alice, - alicePrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year @@ -259,15 +163,14 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { }), new BaseAllocation.Milestone[](0), "Alice", - cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + block.timestamp + 7 days ); bytes32 contractIdBob = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, bob, - bobPrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), // 80k ZK total, the first half unlocks with a cliff and the second half unlocks over an year @@ -281,7 +184,25 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { }), new BaseAllocation.Milestone[](0), "Bob", - cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + block.timestamp + 7 days + ); + + // Guardians to sign agreements + + address metavestAlice = _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" + ); + + address metavestBob = _granteeSignDeal( + contractIdBob, + bob, // grantee + bob, // recipient + bobPrivateKey, + "Bob" ); // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions @@ -290,6 +211,6 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { vm.prank(zkTokenAdmin); zkToken.grantRole(minterRole, address(zkCappedMinter)); - return (contractIdAlice, contractIdBob); + return (metavestAlice, metavestBob); } } diff --git a/test/amendement.t.sol b/test/amendement.t.sol index 62fc961..7899bfa 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -55,8 +55,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.stopPrank(); vm.startPrank(guardianSafe); + controller.setZkCappedMinter(address(zkCappedMinter)); controller.createSet("testSet"); + + // Guardian SAFE to delegate signing to an EOA + registry.setDelegation(delegate, block.timestamp + 365 days * 3); // This is a hack. One should not delegate signing for this long + assertTrue(registry.isValidDelegate(guardianSafe, delegate), "delegate should be Guardian SAFE's delegate"); + vm.stopPrank(); vestingAllocation = createDummyVestingAllocation(); @@ -367,9 +373,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { bytes32 contractIdAlice = _proposeAndSignDeal( templateId, agreementSaltCounter++, - guardianSafe, + delegatePrivateKey, alice, // = grantee - alicePrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -390,10 +395,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.prank(zkTokenAdmin); zkToken.grantRole(minterRole, address(zkCappedMinter)); - if (expectRevertData.length > 0) { - vm.expectRevert(expectRevertData); - } - return controller.createMetavest(contractIdAlice); + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice", + expectRevertData + ); } // function createDummyTokenOptionAllocation() internal returns (address) { diff --git a/test/controller.t.sol b/test/controller.t.sol index d057455..3533227 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -57,15 +57,24 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { controller.setZkCappedMinter(address(zkCappedMinter)); controller.createSet("testSet"); vm.stopPrank(); + + // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions + bytes32 minterRole = zkToken.MINTER_ROLE(); + vm.prank(zkTokenAdmin); + zkToken.grantRole(minterRole, address(zkCappedMinter)); + + // Guardian SAFE to delegate signing to an EOA + vm.prank(guardianSafe); + registry.setDelegation(delegate, block.timestamp + 365 days * 3); // This is a hack. One should not delegate signing for this long + assertTrue(registry.isValidDelegate(guardianSafe, delegate), "delegate should be Guardian SAFE's delegate"); } function testCreateVestingAllocation() public { bytes32 contractIdAlice = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, alice, - alicePrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year @@ -82,14 +91,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - // Anyone can create MetaVesT (per agreements) to start vesting - VestingAllocation vestingAllocationAlice = VestingAllocation(controller.createMetavest(contractIdAlice)); + VestingAllocation vestingAllocationAlice = VestingAllocation(_granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" + )); // Grantees should be able to withdraw all remaining tokens after sufficient time passed skip(61); @@ -543,9 +552,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { bytes32 contractIdAlice = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, alice, // = grantee - alicePrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -561,15 +569,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - if (expectRevertData.length > 0) { - vm.expectRevert(expectRevertData); - } - return controller.createMetavest(contractIdAlice); + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice", + expectRevertData + ); } // Helper functions to create dummy allocations for testing @@ -586,9 +593,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { bytes32 contractIdAlice = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, alice, // = grantee - alicePrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -604,12 +610,13 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - return controller.createMetavest(contractIdAlice); + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" + ); } // Helper functions to create dummy allocations for testing @@ -626,9 +633,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { bytes32 contractIdAlice = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, alice, // = grantee - alicePrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -644,12 +650,13 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - return controller.createMetavest(contractIdAlice); + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" + ); } // Helper functions to create dummy allocations for testing @@ -660,9 +667,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { bytes32 contractIdAlice = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, alice, // = grantee - alicePrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -678,12 +684,13 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - return controller.createMetavest(contractIdAlice); + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" + ); } // Helper functions to create dummy allocations for testing @@ -694,9 +701,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { bytes32 contractIdAlice = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, alice, // = grantee - alicePrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 1000 ether, @@ -712,12 +718,13 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - return controller.createMetavest(contractIdAlice); + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" + ); } // function createDummyTokenOptionAllocation() internal returns (address) { @@ -855,11 +862,10 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { bytes32 contractIdAlice = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, alice, // = grantee - alicePrivateKey, BaseAllocation.Allocation({ - tokenContract: address(0), + tokenContract: address(0), // zero address tokenStreamTotal: 1000 ether, vestingCliffCredit: 100 ether, unlockingCliffCredit: 100 ether, @@ -873,13 +879,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_ZeroAddress.selector)); - controller.createMetavest(contractIdAlice); + _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice", + abi.encodeWithSelector(metavestController.MetaVesTController_ZeroAddress.selector) + ); } function testTerminateVestAndRecovers() public { @@ -1330,7 +1337,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { } function test_RevertIf_CheckFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("createMetavest(bytes32)")); + bytes4 functionSig = bytes4(keccak256("signDealAndCreateMetavest(address,address,bytes32,string[],bytes,string)")); /* constructor( address[] memory _signers, uint256 _threshold, @@ -1375,9 +1382,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { bytes32 contractIdChad = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, chad, - chadPrivateKey, BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 2001 ether, @@ -1392,21 +1398,26 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { "Chad", cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - VestingAllocation vestingAllocationChad = VestingAllocation(controller.createMetavest(contractIdChad)); + VestingAllocation vestingAllocationChad = VestingAllocation(_granteeSignDeal( + contractIdChad, + chad, // grantee + chad, // recipient + chadPrivateKey, + "Chad" + )); vm.prank(chad); vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(vestingAllocationChad), 2001 ether)); vestingAllocationChad.withdraw(2001 ether); } - function test_RevertIf_NotAuthority() public { - // Non Guardian SAFE should not be able to accept agreement and create contract + function test_RevertIf_IncorrectGrantorSignature() public { + // Should not be able to propose a deal without grantor's authorization _proposeAndSignDeal( templateId, block.timestamp, // salt - deployer, // Not authority - alice, - alicePrivateKey, + alicePrivateKey, // Should fail because Alice is not delegated by the grantor + alice, // grantee BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 100 ether, @@ -1419,19 +1430,17 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { }), new BaseAllocation.Milestone[](0), "Alice", - cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible - abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector) // Expected revert + block.timestamp + 7 days, + abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert ); } - function test_RevertIf_IncorrectAgreementSignature() public { - // Register Alice with someone else's signature should fail - _proposeAndSignDeal( + function test_RevertIf_IncorrectGranteeSignature() public { + bytes32 contractIdAlice = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, alice, - bobPrivateKey, // Use someone else to sign BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 100 ether, @@ -1444,12 +1453,21 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { }), new BaseAllocation.Milestone[](0), "Alice", - cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + block.timestamp + 7 days + ); + + // Should not be able to sign Alice's agreement with other's signature + _granteeSignDeal( + contractIdAlice, + alice, + alice, + bobPrivateKey, // Wrong signer + "Alice", abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert ); } - function test_DelegateSignature() public { + function test_GranteeDelegateSignature() public { // Alice to delegate to Bob vm.prank(alice); registry.setDelegation(bob, block.timestamp + 60); @@ -1459,9 +1477,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { bytes32 contractId = _proposeAndSignDeal( templateId, block.timestamp, // salt - guardianSafe, + delegatePrivateKey, alice, - bobPrivateKey, // Use Bob to sign BaseAllocation.Allocation({ tokenContract: address(zkToken), tokenStreamTotal: 100 ether, @@ -1476,35 +1493,20 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { "Alice", cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - metavestController.DealData memory deal = controller.getDeal(contractId); - assertEq(deal.grantee, alice, "Alice should be the grantee"); + VestingAllocation vestingAllocation = VestingAllocation(_granteeSignDeal( + contractId, + alice, + alice, + bobPrivateKey, // Use Bob to sign + "Alice" + )); + assertEq(vestingAllocation.grantee(), alice, "Alice should be the grantee"); // Wait until expiry skip(61); // Bob should no longer be able to sign for Alice assertFalse(registry.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); - _proposeAndSignDeal( - templateId, - block.timestamp, // salt - guardianSafe, - alice, - bobPrivateKey, // Use Bob to sign - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - tokenStreamTotal: 100 ether, - vestingCliffCredit: 10 ether, - unlockingCliffCredit: 10 ether, - vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter - }), - new BaseAllocation.Milestone[](0), - "Alice", - cappedMinterExpirationTime, // Same expiry as the minter so grantee can defer vesting contract creation as much as possible - abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert - ); } function test_TogglePauseMinting() public { diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index 20199fb..3841e0b 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -31,6 +31,8 @@ contract MetaVesTControllerTestBase is Test { address bob = vm.addr(bobPrivateKey); uint256 chadPrivateKey = 3; address chad = vm.addr(chadPrivateKey); + uint256 delegatePrivateKey = 4; + address delegate = vm.addr(delegatePrivateKey); bytes32 salt = keccak256("MetaVesTControllerTestBase"); @@ -103,16 +105,15 @@ contract MetaVesTControllerTestBase is Test { function _proposeAndSignDeal( bytes32 templateId, uint256 agreementSalt, - address authority, + uint256 grantorOrDelegatePrivateKey, address grantee, - uint256 granteePrivateKey, BaseAllocation.Allocation memory allocation, BaseAllocation.Milestone[] memory milestones, string memory partyName, uint256 expiry ) internal returns(bytes32) { return _proposeAndSignDeal( - templateId, agreementSalt, authority, grantee, granteePrivateKey, allocation, milestones, partyName, expiry, + templateId, agreementSalt, grantorOrDelegatePrivateKey, grantee, allocation, milestones, partyName, expiry, "" // Not expecting revert ); } @@ -120,9 +121,8 @@ contract MetaVesTControllerTestBase is Test { function _proposeAndSignDeal( bytes32 templateId, uint256 agreementSalt, - address authority, + uint256 grantorOrDelegatePrivateKey, address grantee, - uint256 granteePrivateKey, BaseAllocation.Allocation memory allocation, BaseAllocation.Milestone[] memory milestones, string memory partyName, @@ -144,20 +144,27 @@ contract MetaVesTControllerTestBase is Test { // TODO what to do with milestones, which could be of dynamic lengths - string[] memory partyValues = new string[](4); - partyValues[0] = partyName; - partyValues[1] = vm.toString(grantee); // evmAddress - partyValues[2] = "email@company.com"; - partyValues[3] = "individual"; + string[][] memory partyValues = new string[][](2); + partyValues[0] = new string[](4); + partyValues[0][0] = "Guardian BORG"; + partyValues[0][1] = vm.toString(address(guardianSafe)); + partyValues[0][2] = "guardian-safe@company.com"; + partyValues[0][3] = "Foundation"; + partyValues[1] = new string[](4); + partyValues[1][0] = partyName; + partyValues[1][1] = vm.toString(grantee); // evmAddress + partyValues[1][2] = "email@company.com"; + partyValues[1][3] = "individual"; - address[] memory allParties = new address[](1); - allParties[0] = grantee; + address[] memory parties = new address[](2); + parties[0] = address(guardianSafe); + parties[1] = grantee; bytes32 expectedContractId = keccak256( abi.encode( templateId, agreementSalt, globalValues, - allParties + parties ) ); @@ -170,23 +177,22 @@ contract MetaVesTControllerTestBase is Test { globalFields, partyFields, globalValues, - partyValues, - granteePrivateKey + partyValues[0], + grantorOrDelegatePrivateKey ); if (expectRevertData.length > 0) { vm.expectRevert(expectRevertData); } - vm.prank(authority); bytes32 contractId = controller.proposeAndSignDeal( templateId, agreementSalt, metavestController.metavestType.Vesting, grantee, - grantee, allocation, milestones, globalValues, + parties, partyValues, signature, bytes32(0), // no secrets @@ -200,4 +206,78 @@ contract MetaVesTControllerTestBase is Test { return 0; } } + + function _granteeSignDeal( + bytes32 contractId, + address grantee, + address recipient, + uint256 granteePrivateKey, + string memory partyName + ) internal returns(address) { + return _granteeSignDeal( + contractId, grantee, recipient, granteePrivateKey, partyName, + "" // Not expecting revert + ); + } + + function _granteeSignDeal( + bytes32 contractId, + address grantee, + address recipient, + uint256 granteePrivateKey, + string memory partyName, + bytes memory expectRevertData + ) internal returns(address) { + metavestController.DealData memory deal = controller.getDeal(contractId); + + string[] memory globalValues = new string[](11); + globalValues[0] = "0"; // metavestType: Vesting + globalValues[1] = vm.toString(grantee); // grantee + globalValues[2] = vm.toString(grantee); // recipient + globalValues[3] = vm.toString(deal.allocation.tokenContract); // tokenContract + globalValues[4] = vm.toString(deal.allocation.tokenStreamTotal); //tokenStreamTotal + globalValues[5] = vm.toString(deal.allocation.vestingCliffCredit); // vestingCliffCredit + globalValues[6] = vm.toString(deal.allocation.unlockingCliffCredit); // unlockingCliffCredit + globalValues[7] = vm.toString(deal.allocation.vestingRate); // vestingRate + globalValues[8] = vm.toString(deal.allocation.vestingStartTime); // vestingStartTime + globalValues[9] = vm.toString(deal.allocation.unlockRate); // unlockRate + globalValues[10] = vm.toString(deal.allocation.unlockStartTime); // unlockStartTime + + string[] memory partyValues = new string[](4); + partyValues[0] = partyName; + partyValues[1] = vm.toString(grantee); // evmAddress + partyValues[2] = "email@company.com"; // Make sure it matches the proposed deal + partyValues[3] = "individual"; // Make sure it matches the proposed deal + + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + agreementUri, + globalFields, + partyFields, + globalValues, + partyValues, + granteePrivateKey + ); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + address metavest = controller.signDealAndCreateMetavest( + grantee, + recipient, + contractId, + partyValues, + signature, + "" // no secrets + ); + + if (expectRevertData.length == 0) { + return metavest; + } else { + return address(0); + } + } } From 318678c303ba6862dc5f609361f76f6c5a03f643 Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 12 Aug 2025 15:02:09 -0700 Subject: [PATCH 29/68] feat: make MetaVesTController upgradeable --- src/MetaVesTController.sol | 17 ++++++-- test/ZkGuardianCompensation.t.sol | 16 +++++--- test/amendement.t.sol | 16 +++++--- test/controller.t.sol | 67 ++++++++++++++++++++++++++++--- test/lib/ERC1967ProxyLib.sol | 53 ++++++++++++++++++++++++ 5 files changed, 147 insertions(+), 22 deletions(-) create mode 100644 test/lib/ERC1967ProxyLib.sol diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index edb2a95..24eb96e 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -9,6 +9,7 @@ pragma solidity ^0.8.24; import {ICyberAgreementRegistry} from "cybercorps-contracts/src/interfaces/ICyberAgreementRegistry.sol"; +import {UUPSUpgradeable} from "zk-governance/l2-contracts/lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol"; import "./BaseAllocation.sol"; //import "./RestrictedTokenAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; @@ -26,8 +27,7 @@ import "./lib/EnumberableSet.sol"; * other permissioned functions, with some powers checked by the applicable 'dao' or subject to consent * by an applicable affected grantee or a majority-in-governing power of similar token grantees **/ -// TODO make it upgradeable -contract metavestController is SafeTransferLib { +contract metavestController is UUPSUpgradeable, SafeTransferLib { using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.Bytes32Set; /// @dev opinionated time limit for a MetaVesT amendment, one calendar week in seconds @@ -217,14 +217,16 @@ contract metavestController is SafeTransferLib { /// @param _authority address of the authority who can call the functions in this contract and update each MetaVesT in '_metavest', such as a BORG /// @param _dao DAO governance contract address which exercises control over ability of 'authority' to call certain functions via imposing /// conditions through 'updateFunctionCondition'. - constructor( + function initialize( address _authority, address _dao, address _registry, address _vestingFactory // address _tokenOptionFactory, // address _restrictedTokenFactory - ) { + ) public initializer { + __UUPSUpgradeable_init(); + if (_authority == address(0)) revert MetaVesTController_ZeroAddress(); authority = _authority; registry = _registry; @@ -914,4 +916,11 @@ contract metavestController is SafeTransferLib { function getDealId(uint256 index) public view returns(bytes32) { return dealIds[index]; } + + function _authorizeUpgrade( + address newImplementation + ) internal virtual override onlyAuthority {} + + // Avoid "Address: low-level delegate call failed" due to `UUPSUpgradeable.upgradeToAndCall()` runs with `forceCall=true` + fallback() external {} } diff --git a/test/ZkGuardianCompensation.t.sol b/test/ZkGuardianCompensation.t.sol index 9b4bb4f..4c146eb 100644 --- a/test/ZkGuardianCompensation.t.sol +++ b/test/ZkGuardianCompensation.t.sol @@ -27,12 +27,16 @@ contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { vestingAllocationFactory = new VestingAllocationFactory(); - controller = new metavestController{salt: salt}( - guardianSafe, - guardianSafe, - address(registry), - address(vestingAllocationFactory) - ); + controller = metavestController(address(new ERC1967Proxy{salt: salt}( + address(new metavestController{salt: salt}()), + abi.encodeWithSelector( + metavestController.initialize.selector, + guardianSafe, + guardianSafe, + address(registry), + address(vestingAllocationFactory) + ) + ))); // Deploy ZK Capped Minter v2 diff --git a/test/amendement.t.sol b/test/amendement.t.sol index 7899bfa..9b300ad 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -34,12 +34,16 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vestingAllocationFactory = new VestingAllocationFactory(); - controller = new metavestController{salt: salt}( - guardianSafe, - guardianSafe, - address(registry), - address(vestingAllocationFactory) - ); + controller = metavestController(address(new ERC1967Proxy{salt: salt}( + address(new metavestController{salt: salt}()), + abi.encodeWithSelector( + metavestController.initialize.selector, + guardianSafe, + guardianSafe, + address(registry), + address(vestingAllocationFactory) + ) + ))); // Deploy ZK Capped Minter v2 diff --git a/test/controller.t.sol b/test/controller.t.sol index 3533227..3295a15 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -12,8 +12,11 @@ import "../src/interfaces/zk-governance/IZkTokenV1.sol"; import "./lib/MetaVesTControllerTestBase.sol"; import "./mocks/MockCondition.sol"; import {Strings} from "zk-governance/l2-contracts/lib/openzeppelin-contracts/contracts/utils/Strings.sol"; +import {ERC1967ProxyLib} from "./lib/ERC1967ProxyLib.sol"; contract MetaVestControllerTest is MetaVesTControllerTestBase { + using ERC1967ProxyLib for address; + address authority = guardianSafe; address dao = guardianSafe; address grantee = alice; @@ -33,12 +36,16 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vestingAllocationFactory = new VestingAllocationFactory(); - controller = new metavestController{salt: salt}( - guardianSafe, - guardianSafe, - address(registry), - address(vestingAllocationFactory) - ); + controller = metavestController(address(new ERC1967Proxy{salt: salt}( + address(new metavestController{salt: salt}()), + abi.encodeWithSelector( + metavestController.initialize.selector, + guardianSafe, + guardianSafe, + address(registry), + address(vestingAllocationFactory) + ) + ))); // Deploy ZK Capped Minter v2 @@ -1560,4 +1567,52 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { )); zkCappedMinter.close(); } + + function test_UpgradeMetaVesTController() public { + // Deploy new implementation + address newImplementation = address(new metavestController()); + + // Upgrade to new implementation without initialization data + + // Non-owner should not be able to upgrade it + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); + controller.upgradeToAndCall(newImplementation, ""); + + // Owner should be able to upgrade it + vm.prank(guardianSafe); + controller.upgradeToAndCall(newImplementation, ""); + assertEq(address(controller).getErc1967Implementation(vm), newImplementation); + + // Verify the controller still works + + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 60 ether, + vestingCliffCredit: 30 ether, + unlockingCliffCredit: 30 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + ); + + VestingAllocation vestingAllocationAlice = VestingAllocation(_granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" + )); + assertEq(vestingAllocationAlice.grantee(), alice); + } } diff --git a/test/lib/ERC1967ProxyLib.sol b/test/lib/ERC1967ProxyLib.sol new file mode 100644 index 0000000..087302b --- /dev/null +++ b/test/lib/ERC1967ProxyLib.sol @@ -0,0 +1,53 @@ +/* .o. + .888. + .8"888. + .8' `888. + .88ooo8888. + .8' `888. +o88o o8888o + + + +ooo ooooo . ooooo ooooooo ooooo +`88. .888' .o8 `888' `8888 d8' + 888b d'888 .ooooo. .o888oo .oooo. 888 .ooooo. Y888..8P + 8 Y88. .P 888 d88' `88b 888 `P )88b 888 d88' `88b `8888' + 8 `888' 888 888ooo888 888 .oP"888 888 888ooo888 .8PY888. + 8 Y 888 888 .o 888 . d8( 888 888 o 888 .o d8' `888b +o8o o888o `Y8bod8P' "888" `Y888""8o o888ooooood8 `Y8bod8P' o888o o88888o + + + + .oooooo. .o8 .oooooo. + d8P' `Y8b "888 d8P' `Y8b +888 oooo ooo 888oooo. .ooooo. oooo d8b 888 .ooooo. oooo d8b oo.ooooo. +888 `88. .8' d88' `88b d88' `88b `888""8P 888 d88' `88b `888""8P 888' `88b +888 `88..8' 888 888 888ooo888 888 888 888 888 888 888 888 +`88b ooo `888' 888 888 888 .o 888 `88b ooo 888 888 888 888 888 .o. + `Y8bood8P' .8' `Y8bod8P' `Y8bod8P' d888b `Y8bood8P' `Y8bod8P' d888b 888bod8P' Y8P + .o..P' 888 + `Y8P' o888o +_______________________________________________________________________________________________________ + +All software, documentation and other files and information in this repository (collectively, the "Software") +are copyright MetaLeX Labs, Inc., a Delaware corporation. + +All rights reserved. + +The Software is proprietary and shall not, in part or in whole, be used, copied, modified, merged, published, +distributed, transmitted, sublicensed, sold, or otherwise used in any form or by any means, electronic or +mechanical, including photocopying, recording, or by any information storage and retrieval system, +except with the express prior written permission of the copyright holder.*/ + +pragma solidity 0.8.28; + +import {Vm} from "forge-std/Test.sol"; + +library ERC1967ProxyLib { + + function getErc1967Implementation(address proxy, Vm vm) internal view returns (address) { + // Workaround since there is no public function to get the implementation address: + // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/acd4ff74de833399287ed6b31b4debf6b2b35527/contracts/proxy/ERC1967/ERC1967Proxy.sol#L35 + return address(uint160(uint256(vm.load(proxy, 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc)))); + } +} From a1f3584ac5b5c4ec08259cff47fe282d9845bd3d Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 12 Aug 2025 21:25:10 -0700 Subject: [PATCH 30/68] feat: add metavest address to DealData for better UX --- src/MetaVesTController.sol | 13 +++++++------ test/controller.t.sol | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 24eb96e..ecacde5 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -74,6 +74,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { address grantee; BaseAllocation.Allocation allocation; BaseAllocation.Milestone[] milestones; + address metavest; } enum metavestType { Vesting, TokenOption, RestrictedTokenAward } @@ -321,7 +322,8 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { _metavestType: _metavestType, grantee: grantee, allocation: allocation, - milestones: milestones + milestones: milestones, + metavest: address(0) // Not deployed yet }); dealIds.push(agreementId); @@ -365,10 +367,9 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { function _createMetavest(bytes32 agreementId, address recipient) internal returns (address) { DealData storage deal = deals[agreementId]; - address newMetavest; if(deal._metavestType == metavestType.Vesting) { - newMetavest = createVestingAllocation(deal.grantee, recipient, deal.allocation, deal.milestones); + deal.metavest = createVestingAllocation(deal.grantee, recipient, deal.allocation, deal.milestones); } else if(deal._metavestType == metavestType.TokenOption) { @@ -387,11 +388,11 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { // Grant MetaVesT minter privilege IZkCappedMinterV2(zkCappedMinter).grantRole( IZkCappedMinterV2(zkCappedMinter).MINTER_ROLE(), - newMetavest + deal.metavest ); - BaseAllocation(newMetavest).setZkCappedMinterAddress(address(zkCappedMinter)); + BaseAllocation(deal.metavest).setZkCappedMinterAddress(address(zkCappedMinter)); - return newMetavest; + return deal.metavest; } diff --git a/test/controller.t.sol b/test/controller.t.sol index 3295a15..1d28677 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -98,7 +98,6 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - // Anyone can create MetaVesT (per agreements) to start vesting VestingAllocation vestingAllocationAlice = VestingAllocation(_granteeSignDeal( contractIdAlice, alice, // grantee @@ -107,6 +106,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { "Alice" )); + assertEq(controller.getDeal(contractIdAlice).metavest, address(vestingAllocationAlice), "deal data should be updated with MetaVesT address"); + // Grantees should be able to withdraw all remaining tokens after sufficient time passed skip(61); _granteeWithdrawAndAsserts(vestingAllocationAlice, 60 ether, "Alice full"); From cc39911304f46ebf2cb8590d46f027473c16e388 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 13 Aug 2025 11:57:43 -0700 Subject: [PATCH 31/68] test: update template to specs --- test/lib/MetaVesTControllerTestBase.sol | 34 ++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index 3841e0b..03d47a9 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -65,8 +65,8 @@ contract MetaVesTControllerTestBase is Test { globalFields = new string[](11); globalFields[0] = "metavestType"; - globalFields[1] = "grantee"; - globalFields[2] = "recipient"; + globalFields[1] = "grantor"; + globalFields[2] = "grantee"; globalFields[3] = "tokenContract"; globalFields[4] = "tokenStreamTotal"; globalFields[5] = "vestingCliffCredit"; @@ -80,7 +80,7 @@ contract MetaVesTControllerTestBase is Test { partyFields[0] = "name"; partyFields[1] = "evmAddress"; partyFields[2] = "contactDetails"; - partyFields[3] = "granteeType"; + partyFields[3] = "type"; registry.createTemplate( templateId, @@ -131,15 +131,15 @@ contract MetaVesTControllerTestBase is Test { ) internal returns(bytes32) { string[] memory globalValues = new string[](11); globalValues[0] = "0"; // metavestType: Vesting - globalValues[1] = vm.toString(grantee); // grantee - globalValues[2] = vm.toString(grantee); // recipient + globalValues[1] = vm.toString(address(guardianSafe)); // grantor + globalValues[2] = vm.toString(grantee); // grantee globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract - globalValues[4] = vm.toString(allocation.tokenStreamTotal); //tokenStreamTotal - globalValues[5] = vm.toString(allocation.vestingCliffCredit); // vestingCliffCredit - globalValues[6] = vm.toString(allocation.unlockingCliffCredit); // unlockingCliffCredit - globalValues[7] = vm.toString(allocation.vestingRate); // vestingRate + globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) + globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) + globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) + globalValues[7] = vm.toString(allocation.vestingRate * 365 days / 1 ether); // vestingRate (annually) (human-readable) globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime - globalValues[9] = vm.toString(allocation.unlockRate); // unlockRate + globalValues[9] = vm.toString(allocation.unlockRate * 365 days / 1 ether); // unlockRate (annually) (human-readable) globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime // TODO what to do with milestones, which could be of dynamic lengths @@ -232,15 +232,15 @@ contract MetaVesTControllerTestBase is Test { string[] memory globalValues = new string[](11); globalValues[0] = "0"; // metavestType: Vesting - globalValues[1] = vm.toString(grantee); // grantee - globalValues[2] = vm.toString(grantee); // recipient + globalValues[1] = vm.toString(address(guardianSafe)); // grantor + globalValues[2] = vm.toString(grantee); // grantee globalValues[3] = vm.toString(deal.allocation.tokenContract); // tokenContract - globalValues[4] = vm.toString(deal.allocation.tokenStreamTotal); //tokenStreamTotal - globalValues[5] = vm.toString(deal.allocation.vestingCliffCredit); // vestingCliffCredit - globalValues[6] = vm.toString(deal.allocation.unlockingCliffCredit); // unlockingCliffCredit - globalValues[7] = vm.toString(deal.allocation.vestingRate); // vestingRate + globalValues[4] = vm.toString(deal.allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) + globalValues[5] = vm.toString(deal.allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) + globalValues[6] = vm.toString(deal.allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) + globalValues[7] = vm.toString(deal.allocation.vestingRate * 365 days / 1 ether); // vestingRate (annually) (human-readable) globalValues[8] = vm.toString(deal.allocation.vestingStartTime); // vestingStartTime - globalValues[9] = vm.toString(deal.allocation.unlockRate); // unlockRate + globalValues[9] = vm.toString(deal.allocation.unlockRate * 365 days / 1 ether); // unlockRate (annually) (human-readable) globalValues[10] = vm.toString(deal.allocation.unlockStartTime); // unlockStartTime string[] memory partyValues = new string[](4); From 225923ee7db8fdf043a6b701a9ae96989edc2243 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 20 Aug 2025 17:58:18 -0700 Subject: [PATCH 32/68] wip: chore: add production deploy scripts --- .gitignore | 1 + .../deployZkSyncGuardianCompensation.s.sol | 125 +++++++++++++++ scripts/lib/ZkSyncGuardianCompConfig.sol | 147 ++++++++++++++++++ scripts/lib/safeTxHelper.sol | 75 +++++++++ test/lib/safe.sol | 69 ++++++++ 5 files changed, 417 insertions(+) create mode 100644 scripts/deployZkSyncGuardianCompensation.s.sol create mode 100644 scripts/lib/ZkSyncGuardianCompConfig.sol create mode 100644 scripts/lib/safeTxHelper.sol create mode 100644 test/lib/safe.sol diff --git a/.gitignore b/.gitignore index 8345343..359e274 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ cache/ out/ .env +.env.* test-command.txt broadcast/ .DS_Store diff --git a/scripts/deployZkSyncGuardianCompensation.s.sol b/scripts/deployZkSyncGuardianCompensation.s.sol new file mode 100644 index 0000000..09d6b26 --- /dev/null +++ b/scripts/deployZkSyncGuardianCompensation.s.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompConfig} from "./lib/ZkSyncGuardianCompConfig.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console} from "forge-std/console.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, SafeTxHelper, Script { + // Assume zkSync Era mainnet @ 64166260 + + function run() public { + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + + console.log(deployer.balance); + + bytes32 salt = keccak256("MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0"); + + vm.startBroadcast(deployerPrivateKey); + + // Deploy CyberAgreementRegistry and create templates + // MetaLeX does not have a CyberAgreementRegistry on zkSync Era yet, so we will deploy it here + + // TODO who should own BorgAuth? + BorgAuth auth = new BorgAuth{salt: salt}(deployer); + CyberAgreementRegistry registry = CyberAgreementRegistry(address(new ERC1967Proxy{salt: salt}( + address(new CyberAgreementRegistry{salt: salt}()), + abi.encodeWithSelector( + CyberAgreementRegistry.initialize.selector, + address(auth) + ) + ))); + + // Create zkSync Guardian Compensation Agreement template + registry.createTemplate( + compTemplateId, + compTemplateName, + compAgreementUri, + compGlobalFields, + compPartyFields + ); + + // Create MetaLeX <> zkSync Guardian BORG Service Agreement template + registry.createTemplate( + serviceTemplateId, + serviceTemplateName, + serviceAgreementUri, + serviceGlobalFields, + servicePartyFields + ); + + // Deploy MetaVesT Controller + + VestingAllocationFactory vestingAllocationFactory = new VestingAllocationFactory{salt: salt}(); + + metavestController controller = metavestController(address(new ERC1967Proxy{salt: salt}( + address(new metavestController{salt: salt}()), + abi.encodeWithSelector( + metavestController.initialize.selector, + address(guardianSafe), + address(guardianSafe), + address(registry), + address(vestingAllocationFactory) + ) + ))); + + // Deploy ZK Capped Minter v2 + + ZkCappedMinterV2 zkCappedMinter = ZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + cap, + zkCappedMinterStartTime, + zkCappedMinterExpirationTime, + uint256(salt) + )); + + // Guardian SAFE to set MetaVesT Controller's ZK Capped Minter + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](1); + safeTxs[0] = GnosisTransaction({ + to: address(controller), + value: 0, + data: abi.encodeWithSelector( + controller.setZkCappedMinter.selector, + address(zkCappedMinter) + ) + }); + + vm.stopBroadcast(); + + console.log("Deployer: ", deployer); + console.log("Guardian Safe: ", address(guardianSafe)); + console.log("ZK token: ", address(zkToken)); + console.log("ZkCappedMinterFactoryV2: ", address(zkCappedMinterFactory)); + console.log(""); + + console.log("Deployed addresses:"); + console.log(" BorgAuth: ", address(auth)); + console.log(" CyberAgreementRegistry: ", address(registry)); + console.log(" VestingAllocationFactory: ", address(vestingAllocationFactory)); + console.log(" MetavesTController: ", address(controller)); + console.log(" ZkCappedMinterV2: ", address(zkCappedMinter)); + console.log(""); + + console.log("Safe TXs:"); + for (uint256 i = 0 ; i < safeTxs.length ; i++) { + console.log(" #", i); + console.log(" to:", safeTxs[i].to); + console.log(" value:", safeTxs[i].value); + console.log(" data:"); + console.logBytes(safeTxs[i].data); + console.log(""); + } + } +} diff --git a/scripts/lib/ZkSyncGuardianCompConfig.sol b/scripts/lib/ZkSyncGuardianCompConfig.sol new file mode 100644 index 0000000..1e05230 --- /dev/null +++ b/scripts/lib/ZkSyncGuardianCompConfig.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.28; + +import {CommonBase} from "forge-std/Base.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {IZkTokenV1} from "../../src/interfaces/zk-governance/IZkTokenV1.sol"; +import {IZkCappedMinterV2Factory} from "../../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {IGnosisSafe} from "../../test/lib/safe.sol"; +import {BaseAllocation} from "../../src/BaseAllocation.sol"; + +contract ZkSyncGuardianCompConfig is CommonBase { + // Assume zkSync Era mainnet @ 64165479 + + // zkSync Guardian SAFE + + IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); + + // ZK Governance + + IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); + IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); + + // ZK Capped Minter + + uint48 zkCappedMinterStartTime = 1756684800; // 2025/09/01 00:00 UTC + uint48 zkCappedMinterExpirationTime = zkCappedMinterStartTime + 365 days * 2; // Expect to vest over an year with a margin of an extra year for withdrawal + uint256 cap = 1e6 ether; // 1M ZK + + // zkSync Guardian Compensation Agreement template + + string compAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP + string compTemplateName = "zkSync Guardian Compensation Agreement"; // TODO WIP + bytes32 compTemplateId = hex"59ac13333823089915f19c84afec3537118dfd884fdbe0618cf4503811691590"; // Randomly generated + + string[] compGlobalFields; + string[] compPartyFields; + + // TODO WIP: zkSync Guardian Compensation default rate + + uint160 annualVestingRate = 50e3 ether; + uint160 annualUnlockRate = 50e3 ether; + BaseAllocation.Milestone[] milestones = new BaseAllocation.Milestone[](0); + + // MetaLeX <> zkSync Guardian BORG Service Agreement template + + string serviceAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP + string serviceTemplateName = "MetaLeX <> zkSync Guardian BORG Service Agreement"; // TODO WIP + bytes32 serviceTemplateId = hex"3af0e11862b617ece70550dbb5ec0a260053aa21466a4209036d5b74f9be107b"; // Randomly generated + + string[] serviceGlobalFields; + string[] servicePartyFields; + + constructor() { + + // zkSync Guardian Compensation Agreement template + + compGlobalFields = new string[](11); + compGlobalFields[0] = "metavestType"; + compGlobalFields[1] = "grantor"; + compGlobalFields[2] = "grantee"; + compGlobalFields[3] = "tokenContract"; + compGlobalFields[4] = "tokenStreamTotal"; + compGlobalFields[5] = "vestingCliffCredit"; + compGlobalFields[6] = "unlockingCliffCredit"; + compGlobalFields[7] = "vestingRate"; + compGlobalFields[8] = "vestingStartTime"; + compGlobalFields[9] = "unlockRate"; + compGlobalFields[10] = "unlockStartTime"; + + compPartyFields = new string[](4); + compPartyFields[0] = "name"; + compPartyFields[1] = "evmAddress"; + compPartyFields[2] = "contactDetails"; + compPartyFields[3] = "type"; + + // MetaLeX <> zkSync Guardian BORG Service Agreement template + + // TODO WIP + serviceGlobalFields = new string[](0); + + // TODO WIP + servicePartyFields = new string[](4); + servicePartyFields[0] = "name"; + servicePartyFields[1] = "evmAddress"; + servicePartyFields[2] = "contactDetails"; + servicePartyFields[3] = "type"; + } + + function _parseAllocation(address token, uint48 startTime) internal returns(BaseAllocation.Allocation memory) { + // TODO WIP + return BaseAllocation.Allocation({ + tokenContract: token, + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 100e3 ether, + vestingCliffCredit: 50e3 ether, + unlockingCliffCredit: 50e3 ether, + vestingRate: annualVestingRate / 365 days, + vestingStartTime: startTime, // start along with capped minter + unlockRate: annualUnlockRate / 365 days, + unlockStartTime: startTime // start along with capped minter + }); + } + + function _formatGlobalValues( + address grantor, + address grantee, + address token, + uint48 startTime + ) internal returns(string[] memory) { + // TODO WIP + BaseAllocation.Allocation memory allocation = _parseAllocation(token, startTime); + + string[] memory globalValues = new string[](11); + globalValues[0] = "0"; // metavestType: Vesting + globalValues[1] = vm.toString(grantor); // grantor + globalValues[2] = vm.toString(grantee); // grantee + globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract + globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) + globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) + globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) + globalValues[7] = vm.toString(annualVestingRate / 1 ether); // vestingRate (annually) (human-readable) + globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime + globalValues[9] = vm.toString(annualUnlockRate / 1 ether); // unlockRate (annually) (human-readable) + globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime + return globalValues; + } + + function _formatPartyValues( + address grantor, + address grantee, + string memory granteeName + ) internal returns(string[][] memory) { + // TODO WIP + string[][] memory partyValues = new string[][](2); + partyValues[0] = new string[](4); + partyValues[0][0] = "Guardian BORG"; + partyValues[0][1] = vm.toString(grantor); + partyValues[0][2] = "inbox@guardian.borg"; + partyValues[0][3] = "Foundation"; + partyValues[1] = new string[](4); + partyValues[1][0] = granteeName; + partyValues[1][1] = vm.toString(grantee); + partyValues[1][2] = "email@company.com"; + partyValues[1][3] = "individual"; + return partyValues; + } +} diff --git a/scripts/lib/safeTxHelper.sol b/scripts/lib/safeTxHelper.sol new file mode 100644 index 0000000..ce8e536 --- /dev/null +++ b/scripts/lib/safeTxHelper.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.28; + +import {CommonBase} from "forge-std/Base.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../../test/lib/safe.sol"; + +contract SafeTxHelper is CommonBase { + function _signAndExecSafeTransaction(uint256 privateKey, address safe, address to, uint256 value, bytes memory data) internal { + uint8 operation = 0; // Call + uint256 safeTxGas = 0; + uint256 baseGas = 0; + uint256 gasPrice = 0; + address gasToken = address(0); + address refundReceiver = address(0); + uint256 nonce = IGnosisSafe(safe).nonce(); + + IGnosisSafe(safe).execTransaction( + to, + value, + data, + operation, + safeTxGas, + baseGas, + gasPrice, + gasToken, + refundReceiver, + _getSafeTxSignature( + privateKey, + safe, + to, + value, + data, + operation, + safeTxGas, + baseGas, + gasPrice, + gasToken, + refundReceiver, + nonce + ) + ); + } + + function _getSafeTxSignature( + uint256 privateKey, + address safe, + address to, + uint256 value, + bytes memory data, + uint8 operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address refundReceiver, + uint256 nonce + ) internal view returns (bytes memory) { + bytes memory txHashData = IGnosisSafe(safe).encodeTransactionData( + to, + value, + data, + operation, + safeTxGas, + baseGas, + gasPrice, + gasToken, + refundReceiver, + nonce + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, keccak256(txHashData)); + bytes memory signature = abi.encodePacked(r, s, v); + return signature; + } +} diff --git a/test/lib/safe.sol b/test/lib/safe.sol new file mode 100644 index 0000000..be09f19 --- /dev/null +++ b/test/lib/safe.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +interface IGnosisSafe { + function getThreshold() external view returns (uint256); + + function isOwner(address owner) external view returns (bool); + + function getOwners() external view returns (address[] memory); + + function isModuleEnabled(address module) external view returns (bool); + + function setGuard(address guard) external; + + function addOwnerWithThreshold(address owner, uint256 threshold) external; + + function execTransaction( + address to, + uint256 value, + bytes memory data, + uint8 operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address refundReceiver, + bytes memory signatures + ) external payable returns (bool success); + + function encodeTransactionData( + address to, + uint256 value, + bytes memory data, + uint8 operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address refundReceiver, + uint256 _nonce + ) external view returns (bytes memory); + + function nonce() external view returns (uint256); + + function setup( + address[] calldata _owners, + uint256 _threshold, + address to, + bytes calldata data, + address fallbackHandler, + address paymentToken, + uint256 payment, + address payable paymentReceiver + ) external; +} + +struct GnosisTransaction { + address to; + uint256 value; + bytes data; +} + +interface IMultiSendCallOnly { + function multiSend(bytes memory transactions) external payable; +} + +interface ISafeProxyFactory { + function createProxyWithNonce(address _singleton, bytes memory initializer, uint256 saltNonce) external returns (address proxy); +} From e8ca3e2d6853fa3d94dc36f1ae67351721ce93d9 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 21 Aug 2025 14:49:37 -0700 Subject: [PATCH 33/68] wip: chore: add production deploy scripts --- .../deployZkSyncGuardianCompensation.s.sol | 51 ++++++++++--------- scripts/lib/ZkSyncGuardianCompConfig.sol | 4 ++ ...t.sol => ZkSyncGuardianCompensation.t.sol} | 2 +- 3 files changed, 32 insertions(+), 25 deletions(-) rename test/{ZkGuardianCompensation.t.sol => ZkSyncGuardianCompensation.t.sol} (99%) diff --git a/scripts/deployZkSyncGuardianCompensation.s.sol b/scripts/deployZkSyncGuardianCompensation.s.sol index 09d6b26..0d866af 100644 --- a/scripts/deployZkSyncGuardianCompensation.s.sol +++ b/scripts/deployZkSyncGuardianCompensation.s.sol @@ -12,7 +12,7 @@ import {Script} from "forge-std/Script.sol"; import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import {console} from "forge-std/console.sol"; +import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, SafeTxHelper, Script { @@ -22,8 +22,6 @@ contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, Saf uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); address deployer = vm.addr(deployerPrivateKey); - console.log(deployer.balance); - bytes32 salt = keccak256("MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0"); vm.startBroadcast(deployerPrivateKey); @@ -41,6 +39,11 @@ contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, Saf ) ))); + // TODO do it when metalexSafe is deployed +// // Transfer CyberAgreementRegistry ownership to MetaLeX SAFE +// auth.updateRole(address(metalexSafe), auth.OWNER_ROLE()); +// auth.zeroOwner(); + // Create zkSync Guardian Compensation Agreement template registry.createTemplate( compTemplateId, @@ -98,28 +101,28 @@ contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, Saf vm.stopBroadcast(); - console.log("Deployer: ", deployer); - console.log("Guardian Safe: ", address(guardianSafe)); - console.log("ZK token: ", address(zkToken)); - console.log("ZkCappedMinterFactoryV2: ", address(zkCappedMinterFactory)); - console.log(""); - - console.log("Deployed addresses:"); - console.log(" BorgAuth: ", address(auth)); - console.log(" CyberAgreementRegistry: ", address(registry)); - console.log(" VestingAllocationFactory: ", address(vestingAllocationFactory)); - console.log(" MetavesTController: ", address(controller)); - console.log(" ZkCappedMinterV2: ", address(zkCappedMinter)); - console.log(""); - - console.log("Safe TXs:"); + console2.log("Deployer: ", deployer); + console2.log("Guardian Safe: ", address(guardianSafe)); + console2.log("ZK token: ", address(zkToken)); + console2.log("ZkCappedMinterFactoryV2: ", address(zkCappedMinterFactory)); + console2.log(""); + + console2.log("Deployed addresses:"); + console2.log(" BorgAuth: ", address(auth)); + console2.log(" CyberAgreementRegistry: ", address(registry)); + console2.log(" VestingAllocationFactory: ", address(vestingAllocationFactory)); + console2.log(" MetavesTController: ", address(controller)); + console2.log(" ZkCappedMinterV2: ", address(zkCappedMinter)); + console2.log(""); + + console2.log("Safe TXs:"); for (uint256 i = 0 ; i < safeTxs.length ; i++) { - console.log(" #", i); - console.log(" to:", safeTxs[i].to); - console.log(" value:", safeTxs[i].value); - console.log(" data:"); - console.logBytes(safeTxs[i].data); - console.log(""); + console2.log(" #", i); + console2.log(" to:", safeTxs[i].to); + console2.log(" value:", safeTxs[i].value); + console2.log(" data:"); + console2.logBytes(safeTxs[i].data); + console2.log(""); } } } diff --git a/scripts/lib/ZkSyncGuardianCompConfig.sol b/scripts/lib/ZkSyncGuardianCompConfig.sol index 1e05230..e1d0586 100644 --- a/scripts/lib/ZkSyncGuardianCompConfig.sol +++ b/scripts/lib/ZkSyncGuardianCompConfig.sol @@ -11,6 +11,10 @@ import {BaseAllocation} from "../../src/BaseAllocation.sol"; contract ZkSyncGuardianCompConfig is CommonBase { // Assume zkSync Era mainnet @ 64165479 + // MetaLeX SAFE + + address metalexSafe = vm.addr(vm.envUint("DEPLOYER_PRIVATE_KEY")); // TODO WIP: use deployer as the placeholder for now + // zkSync Guardian SAFE IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); diff --git a/test/ZkGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol similarity index 99% rename from test/ZkGuardianCompensation.t.sol rename to test/ZkSyncGuardianCompensation.t.sol index 4c146eb..b95cc10 100644 --- a/test/ZkGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -9,7 +9,7 @@ import "./lib/MetaVesTControllerTestBase.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; // Test by forge test --zksync --via-ir -contract ZkGuardianCompensationTest is MetaVesTControllerTestBase { +contract ZkSyncGuardianCompensationTest is MetaVesTControllerTestBase { // Parameters uint256 cap = 1e6 ether; // 1M ZK uint48 cappedMinterStartTime = 1756684800; // 2025/9/1 UTC From 791acb73d92e9bcc35671da29ffb28d06d1f2beb Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 22 Aug 2025 10:10:20 -0700 Subject: [PATCH 34/68] chore: add MetaLeX SAFE address --- scripts/deployZkSyncGuardianCompensation.s.sol | 12 +++++++----- scripts/lib/ZkSyncGuardianCompConfig.sol | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/deployZkSyncGuardianCompensation.s.sol b/scripts/deployZkSyncGuardianCompensation.s.sol index 0d866af..9727f91 100644 --- a/scripts/deployZkSyncGuardianCompensation.s.sol +++ b/scripts/deployZkSyncGuardianCompensation.s.sol @@ -39,11 +39,6 @@ contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, Saf ) ))); - // TODO do it when metalexSafe is deployed -// // Transfer CyberAgreementRegistry ownership to MetaLeX SAFE -// auth.updateRole(address(metalexSafe), auth.OWNER_ROLE()); -// auth.zeroOwner(); - // Create zkSync Guardian Compensation Agreement template registry.createTemplate( compTemplateId, @@ -62,6 +57,13 @@ contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, Saf servicePartyFields ); + // Transfer CyberAgreementRegistry ownership to MetaLeX SAFE + + auth.updateRole(address(metalexSafe), auth.OWNER_ROLE()); + auth.zeroOwner(); + auth.onlyRole(auth.OWNER_ROLE(), address(metalexSafe)); // MetaLeX SAFE should own BorgAuth + require(auth.userRoles(deployer) == 0, "deployer should revoke BorgAuth ownership"); + // Deploy MetaVesT Controller VestingAllocationFactory vestingAllocationFactory = new VestingAllocationFactory{salt: salt}(); diff --git a/scripts/lib/ZkSyncGuardianCompConfig.sol b/scripts/lib/ZkSyncGuardianCompConfig.sol index e1d0586..ff75a13 100644 --- a/scripts/lib/ZkSyncGuardianCompConfig.sol +++ b/scripts/lib/ZkSyncGuardianCompConfig.sol @@ -9,11 +9,11 @@ import {IGnosisSafe} from "../../test/lib/safe.sol"; import {BaseAllocation} from "../../src/BaseAllocation.sol"; contract ZkSyncGuardianCompConfig is CommonBase { - // Assume zkSync Era mainnet @ 64165479 + // Assume zkSync Era mainnet @ 64202885 // MetaLeX SAFE - address metalexSafe = vm.addr(vm.envUint("DEPLOYER_PRIVATE_KEY")); // TODO WIP: use deployer as the placeholder for now + IGnosisSafe metalexSafe = IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc); // zkSync Guardian SAFE From 51dab55ee1a621dddf1534c4f368f6fbbacbbe5c Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 22 Aug 2025 14:26:56 -0700 Subject: [PATCH 35/68] chore: add post-deployment verification --- .../deployZkSyncGuardianCompensation.s.sol | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/scripts/deployZkSyncGuardianCompensation.s.sol b/scripts/deployZkSyncGuardianCompensation.s.sol index 9727f91..b974645 100644 --- a/scripts/deployZkSyncGuardianCompensation.s.sol +++ b/scripts/deployZkSyncGuardianCompensation.s.sol @@ -61,8 +61,6 @@ contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, Saf auth.updateRole(address(metalexSafe), auth.OWNER_ROLE()); auth.zeroOwner(); - auth.onlyRole(auth.OWNER_ROLE(), address(metalexSafe)); // MetaLeX SAFE should own BorgAuth - require(auth.userRoles(deployer) == 0, "deployer should revoke BorgAuth ownership"); // Deploy MetaVesT Controller @@ -103,6 +101,42 @@ contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, Saf vm.stopBroadcast(); + // Post-deployment verifications + + auth.onlyRole(auth.OWNER_ROLE(), address(metalexSafe)); // MetaLeX SAFE should own BorgAuth + vm.assertEq(auth.userRoles(deployer), 0, "deployer should revoke BorgAuth ownership"); + vm.assertEq(address(registry.AUTH()), address(auth), "Unexpected CyberAgreementRegistry auth"); + + _assertTemplate( + registry, + compTemplateId, + compAgreementUri, + compTemplateName, + compGlobalFields, + compPartyFields + ); + _assertTemplate( + registry, + serviceTemplateId, + serviceAgreementUri, + serviceTemplateName, + serviceGlobalFields, + servicePartyFields + ); + + vm.assertEq(controller.authority(), address(guardianSafe), "MetaVesTController's authority should be Guardian SAFE"); + vm.assertEq(controller.dao(), address(guardianSafe), "MetaVesTController's DAO should be Guardian SAFE"); + vm.assertEq(controller.registry(), address(registry), "Unexpected MetaVesTController registry"); + vm.assertEq(controller.vestingFactory(), address(vestingAllocationFactory), "Unexpected MetaVesTController vesting allocation factory"); + + vm.assertEq(address(zkCappedMinter.MINTABLE()), address(zkToken), "ZkCappedMinter should mint ZK"); + vm.assertTrue(zkCappedMinter.hasRole(zkCappedMinter.DEFAULT_ADMIN_ROLE(), address(controller)), "MetaVesTController should be the admin of ZkCappedMinter"); + vm.assertEq(zkCappedMinter.CAP(), 1e6 ether, "Unexpected ZkCappedMinter cap"); + vm.assertEq(zkCappedMinter.START_TIME(), 1756684800, "Unexpected ZkCappedMinter start time"); // 2025/09/01 00:00 UTC + vm.assertEq(zkCappedMinter.EXPIRATION_TIME(), 1756684800 + 365 days * 2, "Unexpected ZkCappedMinter expiry"); + + // Output logs + console2.log("Deployer: ", deployer); console2.log("Guardian Safe: ", address(guardianSafe)); console2.log("ZK token: ", address(zkToken)); @@ -127,4 +161,24 @@ contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, Saf console2.log(""); } } + + function _assertTemplate( + CyberAgreementRegistry registry, + bytes32 templateId, + string memory _legalContractUri, + string memory _title, + string[] memory _globalFields, + string[] memory _partyFields + ) internal { + ( + string memory legalContractUri, + string memory title, + string[] memory globalFields, + string[] memory partyFields + ) = registry.getTemplateDetails(templateId); + vm.assertEq(legalContractUri, _legalContractUri, "Unexpected legalContractUri"); + vm.assertEq(title, _title, "Unexpected template title"); + vm.assertEq(globalFields, _globalFields, "Unexpected template global fields"); + vm.assertEq(partyFields, _partyFields, "Unexpected template party fields"); + } } From 2ff5aabfe80f842fdd9be961914c77eac0119231 Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 22 Aug 2025 16:10:47 -0700 Subject: [PATCH 36/68] feat: accommodate cases when MetaVesTController is not the admin of ZkCappedMinter --- src/BaseAllocation.sol | 8 +-- src/MetaVesTController.sol | 21 +++++-- test/VestingAllocation.t.sol | 57 +++++++++++------- test/ZkSyncGuardianCompensation.t.sol | 20 ++++--- test/controller.t.sol | 80 +++++++++++++++---------- test/lib/MetaVesTControllerTestBase.sol | 4 ++ 6 files changed, 115 insertions(+), 75 deletions(-) diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index d047695..bbd3ec8 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -17,6 +17,7 @@ interface IERC20M { interface IController { function authority() external view returns (address); + function mint(address recipient, uint256 amount) external; } /// @notice Solady's SafeTransferLib 'SafeTransfer()' and 'SafeTransferFrom()'; (https://github.com/Vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) @@ -171,7 +172,6 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ GovType public govType; bool public terminated; uint256 public terminationTime; - address public ZkCappedMinterAddress; /// @notice BaseAllocation constructor /// @param _grantee: address of the grantee, cannot be a zero address @@ -231,10 +231,6 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ emit MetaVesT_UnlockRateUpdated(grantee, _newUnlockRate); } - function setZkCappedMinterAddress(address _ZkCappedMinterAddress) external onlyController { - ZkCappedMinterAddress = _ZkCappedMinterAddress; - } - /// @notice Sets the governing power type for the MetaVesT /// @param _govType: the type of governing power to be used function setGovVariables(GovType _govType) external onlyController { @@ -326,7 +322,7 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ if (_amount == 0) revert MetaVesT_ZeroAmount(); if (_amount > getAmountWithdrawable()) revert MetaVesT_MoreThanAvailable(); tokensWithdrawn += _amount; - IZkCappedMinterV2(ZkCappedMinterAddress).mint(recipient, _amount); + IController(controller).mint(recipient, _amount); emit MetaVesT_Withdrawn(msg.sender, recipient, allocation.tokenContract, _amount); } diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index ecacde5..42c7b11 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -99,6 +99,9 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { /// @notice Maps agreement IDs to arrays of counter party values for closed deals. mapping(bytes32 => string[]) public counterPartyValues; + /// @notice Map MetaVesT contract address to its corresponding agreement ID + mapping(address => bytes32) public metavestAgreementIds; + /// /// EVENTS /// @@ -129,6 +132,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { address indexed recipient, address metavest ); + event MetaVesTController_Minted(address indexed metavest, address indexed recipient, address zkCappedMinter, uint256 amount); /// /// ERRORS @@ -169,6 +173,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { error MetaVesTController_CounterPartyNotFound(); error MetaVesTController_PartyValuesLengthMismatch(); error MetaVesTController_CounterPartyValueMismatch(); + error MetaVesTController_UnauthorizedToMint(); /// /// FUNCTIONS @@ -386,11 +391,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { revert MetaVesTController_IncorrectMetaVesTType(); } // Grant MetaVesT minter privilege - IZkCappedMinterV2(zkCappedMinter).grantRole( - IZkCappedMinterV2(zkCappedMinter).MINTER_ROLE(), - deal.metavest - ); - BaseAllocation(deal.metavest).setZkCappedMinterAddress(address(zkCappedMinter)); + metavestAgreementIds[deal.metavest] = agreementId; return deal.metavest; } @@ -537,6 +538,16 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { // //safeTransferFrom(_allocation.tokenContract, authority, restrictedTokenAward, _total); // return restrictedTokenAward; // } + + function mint(address recipient, uint256 amount) external { + bytes32 agreementId = metavestAgreementIds[msg.sender]; + if (agreementId == bytes32(0)) { + revert MetaVesTController_UnauthorizedToMint(); + } + + IZkCappedMinterV2(zkCappedMinter).mint(recipient, amount); + emit MetaVesTController_Minted(msg.sender, recipient, zkCappedMinter, amount); + } function getMetaVestType(address _grant) public view returns (uint256) { return BaseAllocation(_grant).getVestingType(); diff --git a/test/VestingAllocation.t.sol b/test/VestingAllocation.t.sol index 5f4d063..feea277 100644 --- a/test/VestingAllocation.t.sol +++ b/test/VestingAllocation.t.sol @@ -10,11 +10,18 @@ import {metavestController} from "../src/MetaVesTController.sol"; contract MockMetaVesTController { address public authority; + address public zkCappedMinter; constructor( - address _authority + address _authority, + address _zkCappedMinter ) { authority = _authority; + zkCappedMinter = _zkCappedMinter; + } + + function mint(address recipient, uint256 amount) external { + ZkCappedMinterV2(zkCappedMinter).mint(recipient, amount); } } @@ -33,7 +40,26 @@ contract VestingAllocationTest is Test { zkToken = new ZkTokenV1(); zkToken.initialize(address(this), address(this), 0 ether); - mockController = new MockMetaVesTController(address(this)); + // Deploy ZK Capped Minter v2 + ZkCappedMinterV2 zkCappedMinter = new ZkCappedMinterV2( + IMintable(address(zkToken)), + address(this), + 10000 ether, + uint48(block.timestamp), + uint48(block.timestamp + 365 days * 10) + ); + + // Grant ZkCappedMinter permissions + zkToken.grantRole(zkToken.MINTER_ROLE(), address(zkCappedMinter)); + + // Create mock controller + mockController = new MockMetaVesTController(address(this), address(zkCappedMinter)); + + // Grant controller minter privilege + zkCappedMinter.grantRole( + zkCappedMinter.MINTER_ROLE(), + address(mockController) + ); BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); milestones[0] = BaseAllocation.Milestone({ @@ -59,27 +85,6 @@ contract VestingAllocationTest is Test { }), milestones ); - - // Deploy ZK Capped Minter v2 - ZkCappedMinterV2 zkCappedMinter = new ZkCappedMinterV2( - IMintable(address(zkToken)), - address(this), - 10000 ether, - uint48(block.timestamp), - uint48(block.timestamp + 365 days * 10) - ); - - // Grant ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - - // Grant MetaVesT minter privilege - zkCappedMinter.grantRole( - zkCappedMinter.MINTER_ROLE(), - address(vestingAllocation) - ); - vm.prank(address(mockController)); - vestingAllocation.setZkCappedMinterAddress(address(zkCappedMinter)); } function test_Metadata() public { @@ -99,6 +104,12 @@ contract VestingAllocationTest is Test { assertEq(zkToken.balanceOf(recipient), balanceBefore + 100 ether); } + function test_RevertIf_WithdrawTooMuch() public { + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_MoreThanAvailable.selector)); + VestingAllocation(vestingAllocation).withdraw(101 ether); + } + function test_UpdateRecipient() public { // Grantee should be able to update recipient vm.prank(grantee); diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index b95cc10..f09267e 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -38,19 +38,29 @@ contract ZkSyncGuardianCompensationTest is MetaVesTControllerTestBase { ) ))); - // Deploy ZK Capped Minter v2 + vm.stopPrank(); + + vm.startPrank(zkTokenAdmin); + // Simulate ZK Capped Minter v2 deployemnt zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( address(zkToken), - address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + address(zkTokenAdmin), cap, cappedMinterStartTime, cappedMinterExpirationTime, uint256(salt) )); + // Simulate TPP approval + zkToken.grantRole(zkToken.MINTER_ROLE(), address(zkCappedMinter)); + + // Simulate capped minter granting permission to MetaVesTcontroller + zkCappedMinter.grantRole(zkCappedMinter.MINTER_ROLE(), address(controller)); + vm.stopPrank(); + // Guardian SAFE to set capped minter on MetaVesTController vm.prank(guardianSafe); controller.setZkCappedMinter(address(zkCappedMinter)); } @@ -209,12 +219,6 @@ contract ZkSyncGuardianCompensationTest is MetaVesTControllerTestBase { "Bob" ); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - return (metavestAlice, metavestBob); } } diff --git a/test/controller.t.sol b/test/controller.t.sol index 1d28677..46de3a6 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -13,6 +13,7 @@ import "./lib/MetaVesTControllerTestBase.sol"; import "./mocks/MockCondition.sol"; import {Strings} from "zk-governance/l2-contracts/lib/openzeppelin-contracts/contracts/utils/Strings.sol"; import {ERC1967ProxyLib} from "./lib/ERC1967ProxyLib.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; contract MetaVestControllerTest is MetaVesTControllerTestBase { using ERC1967ProxyLib for address; @@ -47,29 +48,31 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { ) ))); - // Deploy ZK Capped Minter v2 + vm.startPrank(zkTokenAdmin); + // Simulate ZK Capped Minter v2 deployemnt zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( address(zkToken), - address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT + address(zkTokenAdmin), cap, cappedMinterStartTime, cappedMinterExpirationTime, uint256(salt) )); + // Simulate TPP approval + zkToken.grantRole(zkToken.MINTER_ROLE(), address(zkCappedMinter)); + + // Simulate capped minter granting permission to MetaVesTcontroller + zkCappedMinter.grantRole(zkCappedMinter.MINTER_ROLE(), address(controller)); + vm.stopPrank(); vm.startPrank(guardianSafe); - controller.setZkCappedMinter(address(zkCappedMinter)); + controller.setZkCappedMinter(address(zkCappedMinter)); // Guardian SAFE to set capped minter on MetaVesTController controller.createSet("testSet"); vm.stopPrank(); - // TPP to review agreements and on-chain parameters, then approve by granting our ZkCappedMinter permissions - bytes32 minterRole = zkToken.MINTER_ROLE(); - vm.prank(zkTokenAdmin); - zkToken.grantRole(minterRole, address(zkCappedMinter)); - // Guardian SAFE to delegate signing to an EOA vm.prank(guardianSafe); registry.setDelegation(delegate, block.timestamp + 365 days * 3); // This is a hack. One should not delegate signing for this long @@ -1415,7 +1418,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { )); vm.prank(chad); - vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(vestingAllocationChad), 2001 ether)); + vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(controller), 2001 ether)); vestingAllocationChad.withdraw(2001 ether); } @@ -1518,55 +1521,66 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { } function test_TogglePauseMinting() public { - assertFalse(zkCappedMinter.paused(), "minter should not be paused yet"); + IZkCappedMinterV2 controllerOwnedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Note MetaVesTController being the admin + cap, + cappedMinterStartTime, + cappedMinterExpirationTime, + uint256(salt) + )); + vm.prank(authority); + controller.setZkCappedMinter(address(controllerOwnedMinter)); + + assertFalse(controllerOwnedMinter.paused(), "minter should not be paused yet"); // Authority should be able to pause minting through controller vm.prank(authority); controller.pauseZkCappedMinter(); - assertTrue(zkCappedMinter.paused(), "minter should be paused now"); + assertTrue(controllerOwnedMinter.paused(), "minter should be paused now"); vm.prank(authority); controller.unpauseZkCappedMinter(); - assertFalse(zkCappedMinter.paused(), "minter should be unpaused now"); + assertFalse(controllerOwnedMinter.paused(), "minter should be unpaused now"); } - function test_RevertIf_PauseMintingNonGuardianSafe() public { + function test_RevertIf_PauseMintingNonAuthority() public { // Non-authority should not be able to pause minting through controller vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); controller.pauseZkCappedMinter(); - - // Non-controller should not be able to pause minting directly - vm.expectRevert(abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(address(this)), - " is missing role ", - Strings.toHexString(uint256(zkCappedMinter.PAUSER_ROLE()), 32) - )); - zkCappedMinter.pause(); } function test_CloseMinting() public { - assertFalse(zkCappedMinter.closed(), "minter should not be closed yet"); + IZkCappedMinterV2 controllerOwnedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(zkToken), + address(controller), // Note MetaVesTController being the admin + cap, + cappedMinterStartTime, + cappedMinterExpirationTime, + uint256(salt) + )); + vm.prank(authority); + controller.setZkCappedMinter(address(controllerOwnedMinter)); + + assertFalse(controllerOwnedMinter.closed(), "minter should not be closed yet"); // Authority should be able to close minting through controller vm.prank(authority); controller.closeZkCappedMinter(); - assertTrue(zkCappedMinter.closed(), "minter should be closed now"); + assertTrue(controllerOwnedMinter.closed(), "minter should be closed now"); } - function test_RevertIf_CloseMintingNonGuardianSafe() public { + function test_RevertIf_CloseMintingNonAuthority() public { // Non-authority should not be able to close minting through controller vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); controller.closeZkCappedMinter(); + } - // Non-controller should not be able to close minting directly - vm.expectRevert(abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(address(this)), - " is missing role ", - Strings.toHexString(uint256(zkCappedMinter.DEFAULT_ADMIN_ROLE()), 32) - )); - zkCappedMinter.close(); + function test_RevertIf_MintUnauthorized() public { + // Should not be able to mint arbitrarily + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_UnauthorizedToMint.selector)); + controller.mint(alice, 1 ether); } function test_UpgradeMetaVesTController() public { diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index 03d47a9..a9877a7 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -96,8 +96,12 @@ contract MetaVesTControllerTestBase is Test { function _granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) internal { address grantee = vestingAllocation.grantee(); uint256 balanceBefore = zkToken.balanceOf(grantee); + vm.prank(grantee); + vm.expectEmit(true, true, true, true); + emit metavestController.MetaVesTController_Minted(address(vestingAllocation), grantee, address(zkCappedMinter), amount); vestingAllocation.withdraw(amount); + assertEq(zkToken.balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); } From b4230166acae55009f849a158a5dc1cc9152f976 Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 22 Aug 2025 16:14:14 -0700 Subject: [PATCH 37/68] chore: commit modified zk-governance library for compiler version conflicts --- lib/zk-governance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zk-governance b/lib/zk-governance index 36f7d4e..f9915cb 160000 --- a/lib/zk-governance +++ b/lib/zk-governance @@ -1 +1 @@ -Subproject commit 36f7d4e19e586fb539f4c8723e28e52b5864fb8e +Subproject commit f9915cb59ff1ab8f2819f3ec3f6189a71e3b65f0 From 38c56c29e23cc05959bb41cfddc0060b1e57fccb Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 25 Aug 2025 15:05:04 -0700 Subject: [PATCH 38/68] wip: test: integrate deploy scripts to tests --- .../deployZkSyncGuardianCompensation.s.sol | 184 ------- ...yZkSyncGuardianCompensation2024_2025.s.sol | 108 +++++ ...yZkSyncGuardianCompensation2025_2026.s.sol | 87 ++++ ...yncGuardianCompensationPrerequisites.s.sol | 92 ++++ .../lib/ZkSyncGuardianCompConfig2024_2025.sol | 36 ++ .../lib/ZkSyncGuardianCompConfig2025_2026.sol | 37 ++ ...g.sol => ZkSyncGuardianCompConfigBase.sol} | 54 +-- .../zk-governance/IZkCappedMinterV2.sol | 3 + test/ZkSyncGuardianCompensation.t.sol | 455 ++++++++++++++---- 9 files changed, 746 insertions(+), 310 deletions(-) delete mode 100644 scripts/deployZkSyncGuardianCompensation.s.sol create mode 100644 scripts/deployZkSyncGuardianCompensation2024_2025.s.sol create mode 100644 scripts/deployZkSyncGuardianCompensation2025_2026.s.sol create mode 100644 scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol create mode 100644 scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol create mode 100644 scripts/lib/ZkSyncGuardianCompConfig2025_2026.sol rename scripts/lib/{ZkSyncGuardianCompConfig.sol => ZkSyncGuardianCompConfigBase.sol} (77%) diff --git a/scripts/deployZkSyncGuardianCompensation.s.sol b/scripts/deployZkSyncGuardianCompensation.s.sol deleted file mode 100644 index b974645..0000000 --- a/scripts/deployZkSyncGuardianCompensation.s.sol +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.24; - -import {ZkSyncGuardianCompConfig} from "./lib/ZkSyncGuardianCompConfig.sol"; -import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; -import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; -import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; -import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; -import {Script} from "forge-std/Script.sol"; -import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; -import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; -import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import {console2} from "forge-std/console2.sol"; -import {metavestController} from "../src/MetaVesTController.sol"; - -contract DeployZkSyncGuardianCompensationScript is ZkSyncGuardianCompConfig, SafeTxHelper, Script { - // Assume zkSync Era mainnet @ 64166260 - - function run() public { - uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - - bytes32 salt = keccak256("MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0"); - - vm.startBroadcast(deployerPrivateKey); - - // Deploy CyberAgreementRegistry and create templates - // MetaLeX does not have a CyberAgreementRegistry on zkSync Era yet, so we will deploy it here - - // TODO who should own BorgAuth? - BorgAuth auth = new BorgAuth{salt: salt}(deployer); - CyberAgreementRegistry registry = CyberAgreementRegistry(address(new ERC1967Proxy{salt: salt}( - address(new CyberAgreementRegistry{salt: salt}()), - abi.encodeWithSelector( - CyberAgreementRegistry.initialize.selector, - address(auth) - ) - ))); - - // Create zkSync Guardian Compensation Agreement template - registry.createTemplate( - compTemplateId, - compTemplateName, - compAgreementUri, - compGlobalFields, - compPartyFields - ); - - // Create MetaLeX <> zkSync Guardian BORG Service Agreement template - registry.createTemplate( - serviceTemplateId, - serviceTemplateName, - serviceAgreementUri, - serviceGlobalFields, - servicePartyFields - ); - - // Transfer CyberAgreementRegistry ownership to MetaLeX SAFE - - auth.updateRole(address(metalexSafe), auth.OWNER_ROLE()); - auth.zeroOwner(); - - // Deploy MetaVesT Controller - - VestingAllocationFactory vestingAllocationFactory = new VestingAllocationFactory{salt: salt}(); - - metavestController controller = metavestController(address(new ERC1967Proxy{salt: salt}( - address(new metavestController{salt: salt}()), - abi.encodeWithSelector( - metavestController.initialize.selector, - address(guardianSafe), - address(guardianSafe), - address(registry), - address(vestingAllocationFactory) - ) - ))); - - // Deploy ZK Capped Minter v2 - - ZkCappedMinterV2 zkCappedMinter = ZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( - address(zkToken), - address(controller), // Grant controller admin privilege so it can grant minter privilege to deployed MetaVesT - cap, - zkCappedMinterStartTime, - zkCappedMinterExpirationTime, - uint256(salt) - )); - - // Guardian SAFE to set MetaVesT Controller's ZK Capped Minter - GnosisTransaction[] memory safeTxs = new GnosisTransaction[](1); - safeTxs[0] = GnosisTransaction({ - to: address(controller), - value: 0, - data: abi.encodeWithSelector( - controller.setZkCappedMinter.selector, - address(zkCappedMinter) - ) - }); - - vm.stopBroadcast(); - - // Post-deployment verifications - - auth.onlyRole(auth.OWNER_ROLE(), address(metalexSafe)); // MetaLeX SAFE should own BorgAuth - vm.assertEq(auth.userRoles(deployer), 0, "deployer should revoke BorgAuth ownership"); - vm.assertEq(address(registry.AUTH()), address(auth), "Unexpected CyberAgreementRegistry auth"); - - _assertTemplate( - registry, - compTemplateId, - compAgreementUri, - compTemplateName, - compGlobalFields, - compPartyFields - ); - _assertTemplate( - registry, - serviceTemplateId, - serviceAgreementUri, - serviceTemplateName, - serviceGlobalFields, - servicePartyFields - ); - - vm.assertEq(controller.authority(), address(guardianSafe), "MetaVesTController's authority should be Guardian SAFE"); - vm.assertEq(controller.dao(), address(guardianSafe), "MetaVesTController's DAO should be Guardian SAFE"); - vm.assertEq(controller.registry(), address(registry), "Unexpected MetaVesTController registry"); - vm.assertEq(controller.vestingFactory(), address(vestingAllocationFactory), "Unexpected MetaVesTController vesting allocation factory"); - - vm.assertEq(address(zkCappedMinter.MINTABLE()), address(zkToken), "ZkCappedMinter should mint ZK"); - vm.assertTrue(zkCappedMinter.hasRole(zkCappedMinter.DEFAULT_ADMIN_ROLE(), address(controller)), "MetaVesTController should be the admin of ZkCappedMinter"); - vm.assertEq(zkCappedMinter.CAP(), 1e6 ether, "Unexpected ZkCappedMinter cap"); - vm.assertEq(zkCappedMinter.START_TIME(), 1756684800, "Unexpected ZkCappedMinter start time"); // 2025/09/01 00:00 UTC - vm.assertEq(zkCappedMinter.EXPIRATION_TIME(), 1756684800 + 365 days * 2, "Unexpected ZkCappedMinter expiry"); - - // Output logs - - console2.log("Deployer: ", deployer); - console2.log("Guardian Safe: ", address(guardianSafe)); - console2.log("ZK token: ", address(zkToken)); - console2.log("ZkCappedMinterFactoryV2: ", address(zkCappedMinterFactory)); - console2.log(""); - - console2.log("Deployed addresses:"); - console2.log(" BorgAuth: ", address(auth)); - console2.log(" CyberAgreementRegistry: ", address(registry)); - console2.log(" VestingAllocationFactory: ", address(vestingAllocationFactory)); - console2.log(" MetavesTController: ", address(controller)); - console2.log(" ZkCappedMinterV2: ", address(zkCappedMinter)); - console2.log(""); - - console2.log("Safe TXs:"); - for (uint256 i = 0 ; i < safeTxs.length ; i++) { - console2.log(" #", i); - console2.log(" to:", safeTxs[i].to); - console2.log(" value:", safeTxs[i].value); - console2.log(" data:"); - console2.logBytes(safeTxs[i].data); - console2.log(""); - } - } - - function _assertTemplate( - CyberAgreementRegistry registry, - bytes32 templateId, - string memory _legalContractUri, - string memory _title, - string[] memory _globalFields, - string[] memory _partyFields - ) internal { - ( - string memory legalContractUri, - string memory title, - string[] memory globalFields, - string[] memory partyFields - ) = registry.getTemplateDetails(templateId); - vm.assertEq(legalContractUri, _legalContractUri, "Unexpected legalContractUri"); - vm.assertEq(title, _title, "Unexpected template title"); - vm.assertEq(globalFields, _globalFields, "Unexpected template global fields"); - vm.assertEq(partyFields, _partyFields, "Unexpected template party fields"); - } -} diff --git a/scripts/deployZkSyncGuardianCompensation2024_2025.s.sol b/scripts/deployZkSyncGuardianCompensation2024_2025.s.sol new file mode 100644 index 0000000..02fe268 --- /dev/null +++ b/scripts/deployZkSyncGuardianCompensation2024_2025.s.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompConfig2024_2025} from "./lib/ZkSyncGuardianCompConfig2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IZkCappedMinterV2} from "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract DeployZkSyncGuardianCompensation2024_2025Script is ZkSyncGuardianCompConfig2024_2025, SafeTxHelper, Script { + // Assume zkSync Era mainnet @ 64166260 + + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + vm.envUint("DEPLOYER_PRIVATE_KEY"), + registry, + vestingAllocationFactory + ); + } + + /// @dev For running in tests + function run( + uint256 deployerPrivateKey, + CyberAgreementRegistry _registry, + VestingAllocationFactory _vestingAllocationFactory + ) public virtual returns( + metavestController, + GnosisTransaction[] memory + ) { + address deployer = vm.addr(deployerPrivateKey); + registry = _registry; + vestingAllocationFactory = _vestingAllocationFactory; + + string memory saltStr = "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2024-2025"; + bytes32 salt = keccak256(bytes(saltStr)); + + vm.startBroadcast(deployerPrivateKey); + + // Deploy MetaVesT Controller + + metavestController controller = metavestController(address(new ERC1967Proxy{salt: salt}( + address(new metavestController{salt: salt}()), + abi.encodeWithSelector( + metavestController.initialize.selector, + address(guardianSafe), + address(guardianSafe), + address(registry), + address(vestingAllocationFactory) + ) + ))); + + vm.stopBroadcast(); + + // Prepare Guardian SAFE txs to: + // 1. Grant MetaVesT Controller MINTER ROLE + // 2. Set MetaVesT Controller's ZK Capped Minter + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](2); + safeTxs[0] = GnosisTransaction({ + to: address(zkCappedMinter), + value: 0, + data: abi.encodeWithSelector( + IZkCappedMinterV2.grantRole.selector, + zkCappedMinter.MINTER_ROLE(), + address(controller) + ) + }); + safeTxs[1] = GnosisTransaction({ + to: address(controller), + value: 0, + data: abi.encodeWithSelector( + controller.setZkCappedMinter.selector, + address(zkCappedMinter) + ) + }); + + // Output logs + + console2.log("Deployer: ", deployer); + console2.log("salt: ", saltStr); + console2.log("Guardian Safe: ", address(guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(registry)); + console2.log("VestingAllocationFactory: ", address(vestingAllocationFactory)); + console2.log(""); + + console2.log("Deployed addresses:"); + console2.log(" MetavesTController: ", address(controller)); + console2.log(""); + + console2.log("Safe TXs:"); + for (uint256 i = 0 ; i < safeTxs.length ; i++) { + console2.log(" #", i); + console2.log(" to:", safeTxs[i].to); + console2.log(" value:", safeTxs[i].value); + console2.log(" data:"); + console2.logBytes(safeTxs[i].data); + console2.log(""); + } + + return (controller, safeTxs); + } +} diff --git a/scripts/deployZkSyncGuardianCompensation2025_2026.s.sol b/scripts/deployZkSyncGuardianCompensation2025_2026.s.sol new file mode 100644 index 0000000..5823a6f --- /dev/null +++ b/scripts/deployZkSyncGuardianCompensation2025_2026.s.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompConfig2024_2025} from "./lib/ZkSyncGuardianCompConfig2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract DeployZkSyncGuardianCompensation2025_2026Script is ZkSyncGuardianCompConfig2025_2026, SafeTxHelper, Script { + // Assume zkSync Era mainnet @ 64166260 + + function run() public { + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + + string memory saltStr = "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2025-2026"; + bytes32 salt = keccak256(saltStr); + + vm.startBroadcast(deployerPrivateKey); + + // Deploy MetaVesT Controller + + metavestController controller = metavestController(address(new ERC1967Proxy{salt: salt}( + address(new metavestController{salt: salt}()), + abi.encodeWithSelector( + metavestController.initialize.selector, + address(guardianSafe), + address(guardianSafe), + address(registry), + address(vestingAllocationFactory) + ) + ))); + + vm.stopBroadcast(); + + // Prepare Guardian SAFE txs to set MetaVesT Controller's ZK Capped Minter + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](1); + safeTxs[0] = GnosisTransaction({ + to: address(controller), + value: 0, + data: abi.encodeWithSelector( + controller.setZkCappedMinter.selector, + address(zkCappedMinter) + ) + }); + + // Post-deployment verifications + + vm.assertEq(controller.authority(), address(guardianSafe), "MetaVesTController's authority should be Guardian SAFE"); + vm.assertEq(controller.dao(), address(guardianSafe), "MetaVesTController's DAO should be Guardian SAFE"); + vm.assertEq(controller.registry(), address(registry), "Unexpected MetaVesTController registry"); + vm.assertEq(controller.vestingFactory(), address(vestingAllocationFactory), "Unexpected MetaVesTController vesting allocation factory"); + + // Output logs + + console2.log("Deployer: ", deployer); + console2.log("salt: ", saltStr); + console2.log("Guardian Safe: ", address(guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(registry)); + console2.log("VestingAllocationFactory: ", address(vestingAllocationFactory)); + console2.log(""); + + console2.log("Deployed addresses:"); + console2.log(" MetavesTController: ", address(controller)); + console2.log(" ZkCappedMinterV2: ", address(zkCappedMinter)); + console2.log(""); + + console2.log("Safe TXs:"); + for (uint256 i = 0 ; i < safeTxs.length ; i++) { + console2.log(" #", i); + console2.log(" to:", safeTxs[i].to); + console2.log(" value:", safeTxs[i].value); + console2.log(" data:"); + console2.logBytes(safeTxs[i].data); + console2.log(""); + } + } +} diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol new file mode 100644 index 0000000..a32d74c --- /dev/null +++ b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompConfigBase} from "./lib/ZkSyncGuardianCompConfigBase.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract DeployZkSyncGuardianCompensationPrerequisitesScript is ZkSyncGuardianCompConfigBase, SafeTxHelper, Script { + // Assume zkSync Era mainnet @ 64166260 + + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run(vm.envUint("DEPLOYER_PRIVATE_KEY")); + } + + /// @dev For running in tests + function run(uint256 deployerPrivateKey) public virtual returns( + BorgAuth, + CyberAgreementRegistry, + VestingAllocationFactory + ) { + address deployer = vm.addr(deployerPrivateKey); + + bytes32 salt = keccak256("MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0"); + + vm.startBroadcast(deployerPrivateKey); + + // Deploy CyberAgreementRegistry and create templates + // MetaLeX does not have a CyberAgreementRegistry on zkSync Era yet, so we will deploy it here + + BorgAuth auth = new BorgAuth{salt: salt}(deployer); + CyberAgreementRegistry registry = CyberAgreementRegistry(address(new ERC1967Proxy{salt: salt}( + address(new CyberAgreementRegistry{salt: salt}()), + abi.encodeWithSelector( + CyberAgreementRegistry.initialize.selector, + address(auth) + ) + ))); + + // Create zkSync Guardian Compensation Agreement template + registry.createTemplate( + compTemplateId, + compTemplateName, + compAgreementUri, + compGlobalFields, + compPartyFields + ); + + // Create MetaLeX <> zkSync Guardian BORG Service Agreement template + registry.createTemplate( + serviceTemplateId, + serviceTemplateName, + serviceAgreementUri, + serviceGlobalFields, + servicePartyFields + ); + + // Transfer CyberAgreementRegistry ownership to MetaLeX SAFE + + auth.updateRole(address(metalexSafe), auth.OWNER_ROLE()); + auth.zeroOwner(); + + // Deploy MetaVesT pre-requisites + + VestingAllocationFactory vestingAllocationFactory = new VestingAllocationFactory{salt: salt}(); + + vm.stopBroadcast(); + + // Output logs + + console2.log("Deployer: ", deployer); + console2.log("Guardian Safe: ", address(guardianSafe)); + console2.log(""); + + console2.log("Deployed addresses:"); + console2.log(" BorgAuth: ", address(auth)); + console2.log(" CyberAgreementRegistry: ", address(registry)); + console2.log(" VestingAllocationFactory: ", address(vestingAllocationFactory)); + + return (auth, registry, vestingAllocationFactory); + } +} diff --git a/scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol b/scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol new file mode 100644 index 0000000..9b49633 --- /dev/null +++ b/scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.28; + +import {ZkSyncGuardianCompConfigBase} from "./ZkSyncGuardianCompConfigBase.sol"; +import {CommonBase} from "forge-std/Base.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {IGnosisSafe} from "../../test/lib/safe.sol"; +import {BaseAllocation} from "../../src/BaseAllocation.sol"; +import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; + +contract ZkSyncGuardianCompConfig2024_2025 is ZkSyncGuardianCompConfigBase { + // ZK Governance + // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 + IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(0xE555FC98E45637D1B45e60E4fc05cF0F22836156); + + // MetaVesT + CyberAgreementRegistry registry = CyberAgreementRegistry(address(0)); // TODO TBD + VestingAllocationFactory vestingAllocationFactory = VestingAllocationFactory(address(0)); // TODO TBD + + constructor() { + // Vesting parameters + + guardians = new address[](6); + guardians[0] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + guardians[1] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + guardians[2] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + guardians[3] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + guardians[4] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + guardians[5] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + + fixedAnnualCompensation = 625e3 ether; + metavestVestingAndUnlockStartTime = 1725148800; // 2024/09/01 00:00 UTC (means by the deployment @ 2025/09/01 it would've been fully unlocked) + } +} diff --git a/scripts/lib/ZkSyncGuardianCompConfig2025_2026.sol b/scripts/lib/ZkSyncGuardianCompConfig2025_2026.sol new file mode 100644 index 0000000..ec0575b --- /dev/null +++ b/scripts/lib/ZkSyncGuardianCompConfig2025_2026.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.28; + +import {ZkSyncGuardianCompConfigBase} from "./ZkSyncGuardianCompConfigBase.sol"; +import {CommonBase} from "forge-std/Base.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {IGnosisSafe} from "../../test/lib/safe.sol"; +import {BaseAllocation} from "../../src/BaseAllocation.sol"; + +contract ZkSyncGuardianCompConfig2025_2026 is ZkSyncGuardianCompConfigBase { + // ZK Governance + // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 + IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A); + + // MetaVesT + CyberAgreementRegistry registry = CyberAgreementRegistry(0); // TODO TBD + VestingAllocationFactory vestingAllocationFactory = VestingAllocationFactory(0); // TODO TBD + + // Vesting parameters + + uint256 fixedAnnualCompensation = 625e3 ether; + uint48 metavestVestingAndUnlockStartTime = 1756684800; // 2025/09/01 00:00 UTC + + constructor() { + super(); + + guardians = new string[](6); + guardians[0] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + guardians[1] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + guardians[2] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + guardians[3] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + guardians[4] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + guardians[5] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + } +} diff --git a/scripts/lib/ZkSyncGuardianCompConfig.sol b/scripts/lib/ZkSyncGuardianCompConfigBase.sol similarity index 77% rename from scripts/lib/ZkSyncGuardianCompConfig.sol rename to scripts/lib/ZkSyncGuardianCompConfigBase.sol index ff75a13..b1db8b9 100644 --- a/scripts/lib/ZkSyncGuardianCompConfig.sol +++ b/scripts/lib/ZkSyncGuardianCompConfigBase.sol @@ -3,12 +3,14 @@ pragma solidity ^0.8.28; import {CommonBase} from "forge-std/Base.sol"; import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; import {IZkTokenV1} from "../../src/interfaces/zk-governance/IZkTokenV1.sol"; import {IZkCappedMinterV2Factory} from "../../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; import {IGnosisSafe} from "../../test/lib/safe.sol"; import {BaseAllocation} from "../../src/BaseAllocation.sol"; +import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; -contract ZkSyncGuardianCompConfig is CommonBase { +contract ZkSyncGuardianCompConfigBase is CommonBase { // Assume zkSync Era mainnet @ 64202885 // MetaLeX SAFE @@ -22,37 +24,32 @@ contract ZkSyncGuardianCompConfig is CommonBase { // ZK Governance IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); - IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); - // ZK Capped Minter + // MetaLeX <> zkSync Guardian BORG Service Agreement template - uint48 zkCappedMinterStartTime = 1756684800; // 2025/09/01 00:00 UTC - uint48 zkCappedMinterExpirationTime = zkCappedMinterStartTime + 365 days * 2; // Expect to vest over an year with a margin of an extra year for withdrawal - uint256 cap = 1e6 ether; // 1M ZK + string serviceAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP + string serviceTemplateName = "MetaLeX <> zkSync Guardian BORG Service Agreement"; // TODO WIP + bytes32 serviceTemplateId = bytes32(uint256(200)); // TODO TBD + + string[] serviceGlobalFields; + string[] servicePartyFields; // zkSync Guardian Compensation Agreement template string compAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP string compTemplateName = "zkSync Guardian Compensation Agreement"; // TODO WIP - bytes32 compTemplateId = hex"59ac13333823089915f19c84afec3537118dfd884fdbe0618cf4503811691590"; // Randomly generated + bytes32 compTemplateId = bytes32(uint256(201)); // TODO TBD string[] compGlobalFields; string[] compPartyFields; - // TODO WIP: zkSync Guardian Compensation default rate - - uint160 annualVestingRate = 50e3 ether; - uint160 annualUnlockRate = 50e3 ether; BaseAllocation.Milestone[] milestones = new BaseAllocation.Milestone[](0); - // MetaLeX <> zkSync Guardian BORG Service Agreement template + // Vesting parameters (should be overridden by child deploy configs) - string serviceAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP - string serviceTemplateName = "MetaLeX <> zkSync Guardian BORG Service Agreement"; // TODO WIP - bytes32 serviceTemplateId = hex"3af0e11862b617ece70550dbb5ec0a260053aa21466a4209036d5b74f9be107b"; // Randomly generated - - string[] serviceGlobalFields; - string[] servicePartyFields; + address[] guardians; + uint256 fixedAnnualCompensation; + uint48 metavestVestingAndUnlockStartTime; constructor() { @@ -79,10 +76,9 @@ contract ZkSyncGuardianCompConfig is CommonBase { // MetaLeX <> zkSync Guardian BORG Service Agreement template - // TODO WIP - serviceGlobalFields = new string[](0); + serviceGlobalFields = new string[](1); + serviceGlobalFields[0] = "expiryDate"; - // TODO WIP servicePartyFields = new string[](4); servicePartyFields[0] = "name"; servicePartyFields[1] = "evmAddress"; @@ -91,16 +87,15 @@ contract ZkSyncGuardianCompConfig is CommonBase { } function _parseAllocation(address token, uint48 startTime) internal returns(BaseAllocation.Allocation memory) { - // TODO WIP return BaseAllocation.Allocation({ tokenContract: token, // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: 100e3 ether, - vestingCliffCredit: 50e3 ether, - unlockingCliffCredit: 50e3 ether, - vestingRate: annualVestingRate / 365 days, + tokenStreamTotal: fixedAnnualCompensation, + vestingCliffCredit: 0e3 ether, + unlockingCliffCredit: 0e3 ether, + vestingRate: uint160(fixedAnnualCompensation / 365 days), vestingStartTime: startTime, // start along with capped minter - unlockRate: annualUnlockRate / 365 days, + unlockRate: uint160(fixedAnnualCompensation / 365 days), unlockStartTime: startTime // start along with capped minter }); } @@ -111,7 +106,6 @@ contract ZkSyncGuardianCompConfig is CommonBase { address token, uint48 startTime ) internal returns(string[] memory) { - // TODO WIP BaseAllocation.Allocation memory allocation = _parseAllocation(token, startTime); string[] memory globalValues = new string[](11); @@ -122,9 +116,9 @@ contract ZkSyncGuardianCompConfig is CommonBase { globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) - globalValues[7] = vm.toString(annualVestingRate / 1 ether); // vestingRate (annually) (human-readable) + globalValues[7] = vm.toString(fixedAnnualCompensation / 1 ether); // vestingRate (annually) (human-readable) globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime - globalValues[9] = vm.toString(annualUnlockRate / 1 ether); // unlockRate (annually) (human-readable) + globalValues[9] = vm.toString(fixedAnnualCompensation / 1 ether); // unlockRate (annually) (human-readable) globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime return globalValues; } diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2.sol b/src/interfaces/zk-governance/IZkCappedMinterV2.sol index 31f220c..683550d 100644 --- a/src/interfaces/zk-governance/IZkCappedMinterV2.sol +++ b/src/interfaces/zk-governance/IZkCappedMinterV2.sol @@ -4,12 +4,15 @@ pragma solidity ^0.8.24; interface IZkCappedMinterV2 { error ZkCappedMinterV2__CapExceeded(address minter, uint256 amount); + function MINTABLE() external returns (address); + function DEFAULT_ADMIN_ROLE() external returns (bytes32); function MINTER_ROLE() external returns (bytes32); function PAUSER_ROLE() external returns (bytes32); function START_TIME() external returns (uint48); function grantRole(bytes32 role, address account) external; + function hasRole(bytes32 role, address account) external view returns (bool); function mint(address _to, uint256 _amount) external; diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index f09267e..1023ae0 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -6,123 +6,155 @@ import "../src/VestingAllocationFactory.sol"; import "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; import "../src/interfaces/zk-governance/IZkTokenV1.sol"; import "./lib/MetaVesTControllerTestBase.sol"; +import {Test} from "forge-std/Test.sol"; +import {console2} from "forge-std/console2.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {DeployZkSyncGuardianCompensationPrerequisitesScript} from "../scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol"; +import {DeployZkSyncGuardianCompensation2024_2025Script} from "../scripts/deployZkSyncGuardianCompensation2024_2025.s.sol"; +import {GnosisTransaction} from "./lib/safe.sol"; // Test by forge test --zksync --via-ir -contract ZkSyncGuardianCompensationTest is MetaVesTControllerTestBase { - // Parameters - uint256 cap = 1e6 ether; // 1M ZK - uint48 cappedMinterStartTime = 1756684800; // 2025/9/1 UTC - uint48 cappedMinterExpirationTime = cappedMinterStartTime + 365 days * 2; // Expect to vest over an year with a margin of an extra year for withdrawal +contract ZkSyncGuardianCompensationTest is + DeployZkSyncGuardianCompensationPrerequisitesScript, + DeployZkSyncGuardianCompensation2024_2025Script, + Test +{ + // zkSync Era mainnet @ 63631890 + address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; + + // Randomly generated to avoid contaminated common test address + uint256 privateKeySalt = 0x4425fdf88097e51c669a66d392c4019ad555544e966908af6ee3cec32f53ab77; + + uint256 deployerPrivateKey = privateKeySalt + 0; + address deployer = vm.addr(deployerPrivateKey); + uint256 delegatePrivateKey = privateKeySalt + 1; + address delegate = vm.addr(delegatePrivateKey); + uint256 alicePrivateKey = privateKeySalt + 2; + address alice = vm.addr(alicePrivateKey); + uint256 bobPrivateKey = privateKeySalt + 3; + address bob = vm.addr(bobPrivateKey); + uint256 chadPrivateKey = privateKeySalt + 4; + address chad = vm.addr(chadPrivateKey); + + IZkCappedMinterV2 masterMinter; + + BorgAuth auth; + metavestController controller; + + function setUp() public { + deal(deployer, 1 ether); + + // Run deploy scripts + GnosisTransaction[] memory safeTxs; + (auth, registry, vestingAllocationFactory) = DeployZkSyncGuardianCompensationPrerequisitesScript.run(deployerPrivateKey); + (controller, safeTxs) = DeployZkSyncGuardianCompensation2024_2025Script.run(deployerPrivateKey, registry, vestingAllocationFactory); + + // Simulate Guardian SAFE to execute txs as instructed + for (uint256 i = 0; i < safeTxs.length; i++) { + vm.prank(address(guardianSafe)); + (safeTxs[i].to).call{value: safeTxs[i].value}(safeTxs[i].data); + } + + // Simulate vote pass (https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746) - function setUp() public override { - // Assume deployment 7 days before the vesting starts - vm.warp(cappedMinterStartTime - 7 days); + vm.startPrank(zkTokenAdmin); - MetaVesTControllerTestBase.setUp(); + masterMinter = IZkCappedMinterV2(zkCappedMinter.MINTABLE()); + masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(zkCappedMinter)); - vm.startPrank(deployer); + IZkCappedMinterV2 grandMasterMinter = IZkCappedMinterV2(masterMinter.MINTABLE()); + grandMasterMinter.grantRole(grandMasterMinter.MINTER_ROLE(), address(masterMinter)); - // Deploy MetaVesT controller + vm.stopPrank(); + } - vestingAllocationFactory = new VestingAllocationFactory(); + function run() public override( + DeployZkSyncGuardianCompensationPrerequisitesScript, + DeployZkSyncGuardianCompensation2024_2025Script + ) { + // No-op, we don't use this part of the scripts + } - controller = metavestController(address(new ERC1967Proxy{salt: salt}( - address(new metavestController{salt: salt}()), - abi.encodeWithSelector( - metavestController.initialize.selector, - guardianSafe, - guardianSafe, - address(registry), - address(vestingAllocationFactory) - ) - ))); + function test_metadata() public { + // ZK governance pre-requisites - vm.stopPrank(); + assertTrue(masterMinter.hasRole(masterMinter.MINTER_ROLE(), address(zkCappedMinter)), "Master Minter should grant this year's ZK Capped Minter access"); - vm.startPrank(zkTokenAdmin); + // MetaVesT pre-requisites - // Simulate ZK Capped Minter v2 deployemnt - zkCappedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( - address(zkToken), - address(zkTokenAdmin), - cap, - cappedMinterStartTime, - cappedMinterExpirationTime, - uint256(salt) - )); - - // Simulate TPP approval - zkToken.grantRole(zkToken.MINTER_ROLE(), address(zkCappedMinter)); + auth.onlyRole(auth.OWNER_ROLE(), address(metalexSafe)); // MetaLeX SAFE should own BorgAuth + vm.assertEq(auth.userRoles(deployer), 0, "deployer should revoke BorgAuth ownership"); + vm.assertEq(address(registry.AUTH()), address(auth), "Unexpected CyberAgreementRegistry auth"); - // Simulate capped minter granting permission to MetaVesTcontroller - zkCappedMinter.grantRole(zkCappedMinter.MINTER_ROLE(), address(controller)); + _assertTemplate( + registry, + compTemplateId, + compAgreementUri, + compTemplateName, + compGlobalFields, + compPartyFields + ); + _assertTemplate( + registry, + serviceTemplateId, + serviceAgreementUri, + serviceTemplateName, + serviceGlobalFields, + servicePartyFields + ); - vm.stopPrank(); + // MetaVesT deployments - // Guardian SAFE to set capped minter on MetaVesTController - vm.prank(guardianSafe); - controller.setZkCappedMinter(address(zkCappedMinter)); + vm.assertEq(controller.authority(), address(guardianSafe), "MetaVesTController's authority should be Guardian SAFE"); + vm.assertEq(controller.dao(), address(guardianSafe), "MetaVesTController's DAO should be Guardian SAFE"); + vm.assertEq(controller.registry(), address(registry), "Unexpected MetaVesTController registry"); + vm.assertEq(controller.vestingFactory(), address(vestingAllocationFactory), "Unexpected MetaVesTController vesting allocation factory"); + vm.assertEq(controller.zkCappedMinter(), address(zkCappedMinter), "MetaVesTController should have ZK Capped Minter should set"); + vm.assertTrue(zkCappedMinter.hasRole(zkCappedMinter.MINTER_ROLE(), address(controller)), "ZK Capped Minter should grant MetaVesTController MINTER role"); } - function test_GuardianCompensationYear1_2() public { - // Assume ZK Capped Minter and its MetaVesTController counterpart are already deployed - + function test_GuardianCompensation() public { (address metavestAddressAlice, address metavestAddressBob) = _guardiansSignAndTppPass(); VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddressAlice); - assertEq(zkToken.balanceOf(address(vestingAllocationAlice)), 0, "Alice's vesting contract should not have any token (it mints on-demand)"); - VestingAllocation vestingAllocationBob = VestingAllocation(metavestAddressBob); - assertEq(zkToken.balanceOf(address(vestingAllocationBob)), 0, "Vesting contract should not have any token (it mints on-demand)"); - - // Grantees should not be able to withdraw yet - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_MoreThanAvailable.selector)); - vestingAllocationAlice.withdraw(1 ether); - vm.prank(bob); - vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_MoreThanAvailable.selector)); - vestingAllocationBob.withdraw(1 ether); - - // Grantees should be able to start withdrawal after capped minter and MetaVesT start - vm.warp(cappedMinterStartTime); - _granteeWithdrawAndAsserts(vestingAllocationAlice, 50e3 ether, "Alice cliff"); - _granteeWithdrawAndAsserts(vestingAllocationBob, 40e3 ether, "Bob cliff"); + // Grantee should be able to withdraw all on 2025/09/01 because this compensation is for 2024~2025 + vm.warp(1756684800 + 1); // 2025/09/01 00:00 UTC with some margin for precision error - // Grantees should be able to withdraw all remaining tokens after sufficient time passed - skip(365 days + 1); + console2.log("alice amount withdrawable: %d", vestingAllocationAlice.getAmountWithdrawable()); + _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationAlice, 625e3 ether, "Alice full"); + _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationBob, 615e3 ether, "Bob partial"); - _granteeWithdrawAndAsserts(vestingAllocationAlice, 50e3 ether, "Alice full"); - _granteeWithdrawAndAsserts(vestingAllocationBob, 30e3 ether, "Bob partial"); + // Grantees should be able to withdraw within the grace period (set by ZK Capped Minter expiry) + skip(60 days); - // Grantees should be able to withdraw within an year after vesting ends - skip(364 days); - - _granteeWithdrawAndAsserts(vestingAllocationBob, 10e3 ether, "Bob full"); + _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationBob, 10e3 ether, "Bob remaining"); } - + function test_AdminToolingCompensation() public { (address metavestAddressAlice, address metavestAddressBob) = _guardiansSignAndTppPass(); VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddressAlice); // Vesting starts and a month has passed - vm.warp(cappedMinterStartTime + 30 days); + vm.warp(1756684800 + 1); // 2025/09/01 00:00 UTC with some margin for precision error - _granteeWithdrawAndAsserts(vestingAllocationAlice, uint256(50e3 ether) + uint160(50e3 ether) / 365 days * 30 days, "Alice cliff + first month"); + _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationAlice, 300e3 ether, "Alice partial"); - // Second month + // A month has passed skip(30 days); // Add new grantee for admin/tooling compensation // Guardian SAFE to delegate signing to an EOA - vm.prank(guardianSafe); + vm.prank(address(guardianSafe)); registry.setDelegation(delegate, block.timestamp + 60); - assertTrue(registry.isValidDelegate(guardianSafe, delegate), "delegate should be Guardian SAFE's delegate"); + assertTrue(registry.isValidDelegate(address(guardianSafe), delegate), "delegate should be Guardian SAFE's delegate"); bytes32 contractIdChad = _proposeAndSignDeal( - templateId, + registry, + controller, + compTemplateId, block.timestamp, // salt delegatePrivateKey, chad, @@ -139,41 +171,46 @@ contract ZkSyncGuardianCompensationTest is MetaVesTControllerTestBase { }), new BaseAllocation.Milestone[](0), "Chad", - cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + block.timestamp + 60 ); VestingAllocation vestingAllocationChad = VestingAllocation(_granteeSignDeal( + registry, + controller, contractIdChad, chad, // grantee chad, // recipient chadPrivateKey, "Chad" )); - _granteeWithdrawAndAsserts(vestingAllocationChad, 10e3 ether, "Chad cliff"); + _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationChad, 10e3 ether, "Chad cliff"); } function _guardiansSignAndTppPass() internal returns(address, address) { // Guardian SAFE to delegate signing to an EOA - vm.prank(guardianSafe); + vm.prank(address(guardianSafe)); registry.setDelegation(delegate, block.timestamp + 60); - assertTrue(registry.isValidDelegate(guardianSafe, delegate), "delegate should be Guardian SAFE's delegate"); + assertTrue(registry.isValidDelegate(address(guardianSafe), delegate), "delegate should be Guardian SAFE's delegate"); // Guardian SAFE to propose deals on MetaVesTController + // TODO revise it to fit actual numbers bytes32 contractIdAlice = _proposeAndSignDeal( - templateId, + registry, + controller, + compTemplateId, block.timestamp, // salt delegatePrivateKey, alice, BaseAllocation.Allocation({ tokenContract: address(zkToken), // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: 100e3 ether, - vestingCliffCredit: 50e3 ether, - unlockingCliffCredit: 50e3 ether, - vestingRate: uint160(50e3 ether) / 365 days, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: uint160(50e3 ether) / 365 days, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + tokenStreamTotal: 625e3 ether, + vestingCliffCredit: 0e3 ether, + unlockingCliffCredit: 0e3 ether, + vestingRate: uint160(625e3 ether) / 365 days, + vestingStartTime: metavestVestingAndUnlockStartTime, + unlockRate: uint160(625e3 ether) / 365 days, + unlockStartTime: metavestVestingAndUnlockStartTime }), new BaseAllocation.Milestone[](0), "Alice", @@ -181,20 +218,22 @@ contract ZkSyncGuardianCompensationTest is MetaVesTControllerTestBase { ); bytes32 contractIdBob = _proposeAndSignDeal( - templateId, + registry, + controller, + compTemplateId, block.timestamp, // salt delegatePrivateKey, bob, BaseAllocation.Allocation({ tokenContract: address(zkToken), // 80k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: 80e3 ether, - vestingCliffCredit: 40e3 ether, - unlockingCliffCredit: 40e3 ether, - vestingRate: uint160(40e3 ether) / 365 days, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter - unlockRate: uint160(40e3 ether) / 365 days, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + tokenStreamTotal: 625e3 ether, + vestingCliffCredit: 0e3 ether, + unlockingCliffCredit: 0e3 ether, + vestingRate: uint160(625e3 ether) / 365 days, + vestingStartTime: metavestVestingAndUnlockStartTime, + unlockRate: uint160(625e3 ether) / 365 days, + unlockStartTime: metavestVestingAndUnlockStartTime }), new BaseAllocation.Milestone[](0), "Bob", @@ -204,6 +243,8 @@ contract ZkSyncGuardianCompensationTest is MetaVesTControllerTestBase { // Guardians to sign agreements address metavestAlice = _granteeSignDeal( + registry, + controller, contractIdAlice, alice, // grantee alice, // recipient @@ -212,6 +253,8 @@ contract ZkSyncGuardianCompensationTest is MetaVesTControllerTestBase { ); address metavestBob = _granteeSignDeal( + registry, + controller, contractIdBob, bob, // grantee bob, // recipient @@ -221,4 +264,224 @@ contract ZkSyncGuardianCompensationTest is MetaVesTControllerTestBase { return (metavestAlice, metavestBob); } + + function _assertTemplate( + CyberAgreementRegistry registry, + bytes32 templateId, + string memory _legalContractUri, + string memory _title, + string[] memory _globalFields, + string[] memory _partyFields + ) internal { + ( + string memory legalContractUri, + string memory title, + string[] memory globalFields, + string[] memory partyFields + ) = registry.getTemplateDetails(templateId); + vm.assertEq(legalContractUri, _legalContractUri, "Unexpected legalContractUri"); + vm.assertEq(title, _title, "Unexpected template title"); + vm.assertEq(globalFields, _globalFields, "Unexpected template global fields"); + vm.assertEq(partyFields, _partyFields, "Unexpected template party fields"); + } + + function _granteeWithdrawAndAsserts(IZkTokenV1 zkToken, IZkCappedMinterV2 zkCappedMinter, VestingAllocation vestingAllocation, uint256 amount, string memory assertName) internal { + address grantee = vestingAllocation.grantee(); + uint256 balanceBefore = zkToken.balanceOf(grantee); + + vm.prank(grantee); + vm.expectEmit(true, true, true, true); + emit metavestController.MetaVesTController_Minted(address(vestingAllocation), grantee, address(zkCappedMinter), amount); + vestingAllocation.withdraw(amount); + + assertEq(zkToken.balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); + assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); + } + + function _proposeAndSignDeal( + CyberAgreementRegistry registry, + metavestController controller, + bytes32 templateId, + uint256 agreementSalt, + uint256 grantorOrDelegatePrivateKey, + address grantee, + BaseAllocation.Allocation memory allocation, + BaseAllocation.Milestone[] memory milestones, + string memory partyName, + uint256 expiry + ) internal returns(bytes32) { + return _proposeAndSignDeal( + registry, controller, templateId, agreementSalt, grantorOrDelegatePrivateKey, grantee, allocation, milestones, partyName, expiry, + "" // Not expecting revert + ); + } + + function _proposeAndSignDeal( + CyberAgreementRegistry registry, + metavestController controller, + bytes32 templateId, + uint256 agreementSalt, + uint256 grantorOrDelegatePrivateKey, + address grantee, + BaseAllocation.Allocation memory allocation, + BaseAllocation.Milestone[] memory milestones, + string memory partyName, + uint256 expiry, + bytes memory expectRevertData + ) internal returns(bytes32) { + string[] memory globalValues = new string[](11); + globalValues[0] = "0"; // metavestType: Vesting + globalValues[1] = vm.toString(address(guardianSafe)); // grantor + globalValues[2] = vm.toString(grantee); // grantee + globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract + globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) + globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) + globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) + globalValues[7] = vm.toString(allocation.vestingRate * 365 days / 1 ether); // vestingRate (annually) (human-readable) + globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime + globalValues[9] = vm.toString(allocation.unlockRate * 365 days / 1 ether); // unlockRate (annually) (human-readable) + globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime + + // TODO what to do with milestones, which could be of dynamic lengths + + string[][] memory partyValues = new string[][](2); + partyValues[0] = new string[](4); + partyValues[0][0] = "Guardian BORG"; + partyValues[0][1] = vm.toString(address(guardianSafe)); + partyValues[0][2] = "guardian-safe@company.com"; + partyValues[0][3] = "Foundation"; + partyValues[1] = new string[](4); + partyValues[1][0] = partyName; + partyValues[1][1] = vm.toString(grantee); // evmAddress + partyValues[1][2] = "email@company.com"; + partyValues[1][3] = "individual"; + + address[] memory parties = new address[](2); + parties[0] = address(guardianSafe); + parties[1] = grantee; + bytes32 expectedContractId = keccak256( + abi.encode( + templateId, + agreementSalt, + globalValues, + parties + ) + ); + + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + expectedContractId, + compAgreementUri, + compGlobalFields, + compPartyFields, + globalValues, + partyValues[0], + grantorOrDelegatePrivateKey + ); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + bytes32 contractId = controller.proposeAndSignDeal( + templateId, + agreementSalt, + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + globalValues, + parties, + partyValues, + signature, + bytes32(0), // no secrets + expiry + ); + + if (expectRevertData.length == 0) { + assertEq(contractId, expectedContractId, "Unexpected contract ID"); + return contractId; + } else { + return 0; + } + } + + function _granteeSignDeal( + CyberAgreementRegistry registry, + metavestController controller, + bytes32 contractId, + address grantee, + address recipient, + uint256 granteePrivateKey, + string memory partyName + ) internal returns(address) { + return _granteeSignDeal( + registry, controller, contractId, grantee, recipient, granteePrivateKey, partyName, + "" // Not expecting revert + ); + } + + function _granteeSignDeal( + CyberAgreementRegistry registry, + metavestController controller, + bytes32 contractId, + address grantee, + address recipient, + uint256 granteePrivateKey, + string memory partyName, + bytes memory expectRevertData + ) internal returns(address) { + metavestController.DealData memory deal = controller.getDeal(contractId); + + string[] memory globalValues = new string[](11); + globalValues[0] = "0"; // metavestType: Vesting + globalValues[1] = vm.toString(address(guardianSafe)); // grantor + globalValues[2] = vm.toString(grantee); // grantee + globalValues[3] = vm.toString(deal.allocation.tokenContract); // tokenContract + globalValues[4] = vm.toString(deal.allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) + globalValues[5] = vm.toString(deal.allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) + globalValues[6] = vm.toString(deal.allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) + globalValues[7] = vm.toString(deal.allocation.vestingRate * 365 days / 1 ether); // vestingRate (annually) (human-readable) + globalValues[8] = vm.toString(deal.allocation.vestingStartTime); // vestingStartTime + globalValues[9] = vm.toString(deal.allocation.unlockRate * 365 days / 1 ether); // unlockRate (annually) (human-readable) + globalValues[10] = vm.toString(deal.allocation.unlockStartTime); // unlockStartTime + + string[] memory partyValues = new string[](4); + partyValues[0] = partyName; + partyValues[1] = vm.toString(grantee); // evmAddress + partyValues[2] = "email@company.com"; // Make sure it matches the proposed deal + partyValues[3] = "individual"; // Make sure it matches the proposed deal + + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + compAgreementUri, + compGlobalFields, + compPartyFields, + globalValues, + partyValues, + granteePrivateKey + ); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + address metavest = controller.signDealAndCreateMetavest( + grantee, + recipient, + contractId, + partyValues, + signature, + "" // no secrets + ); + + if (expectRevertData.length == 0) { + return metavest; + } else { + return address(0); + } + } } From bd83d3a988ddf4900495987d2951be3d0bdb6076 Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 25 Aug 2025 16:35:02 -0700 Subject: [PATCH 39/68] test: add tests to proposeServiceAgreement scripts --- .../lib/ZkSyncGuardianCompConfig2024_2025.sol | 5 +- scripts/lib/ZkSyncGuardianCompConfigBase.sol | 28 ++++- scripts/proposeServiceAgreement.s.sol | 105 ++++++++++++++++++ test/ZkSyncGuardianCompensation.t.sol | 56 +++++++--- 4 files changed, 178 insertions(+), 16 deletions(-) create mode 100644 scripts/proposeServiceAgreement.s.sol diff --git a/scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol b/scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol index 9b49633..1e50cfa 100644 --- a/scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol @@ -15,10 +15,13 @@ contract ZkSyncGuardianCompConfig2024_2025 is ZkSyncGuardianCompConfigBase { // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(0xE555FC98E45637D1B45e60E4fc05cF0F22836156); - // MetaVesT + // MetaVesT deployment CyberAgreementRegistry registry = CyberAgreementRegistry(address(0)); // TODO TBD VestingAllocationFactory vestingAllocationFactory = VestingAllocationFactory(address(0)); // TODO TBD + // MetaLeX <> zkSync Guardian BORG Service Agreement parameters + uint256 serviceAgreementExpiry = 1788220800; + constructor() { // Vesting parameters diff --git a/scripts/lib/ZkSyncGuardianCompConfigBase.sol b/scripts/lib/ZkSyncGuardianCompConfigBase.sol index b1db8b9..1b52383 100644 --- a/scripts/lib/ZkSyncGuardianCompConfigBase.sol +++ b/scripts/lib/ZkSyncGuardianCompConfigBase.sol @@ -100,7 +100,7 @@ contract ZkSyncGuardianCompConfigBase is CommonBase { }); } - function _formatGlobalValues( + function _compFormatGlobalValues( address grantor, address grantee, address token, @@ -123,7 +123,7 @@ contract ZkSyncGuardianCompConfigBase is CommonBase { return globalValues; } - function _formatPartyValues( + function _compFormatPartyValues( address grantor, address grantee, string memory granteeName @@ -142,4 +142,28 @@ contract ZkSyncGuardianCompConfigBase is CommonBase { partyValues[1][3] = "individual"; return partyValues; } + + function _serviceFormatGlobalValues(uint256 expiry) internal returns(string[] memory) { + string[] memory globalValues = new string[](1); + globalValues[0] = vm.toString(expiry); + return globalValues; + } + + function _serviceFormatPartyValues( + address metalex, + address guardianBorg + ) internal returns(string[][] memory) { + string[][] memory partyValues = new string[][](2); + partyValues[0] = new string[](4); + partyValues[0][0] = "MetaLeX"; + partyValues[0][1] = vm.toString(metalex); + partyValues[0][2] = "test@metalex.tech"; + partyValues[0][3] = "Corporation"; + partyValues[1] = new string[](4); + partyValues[1][0] = "Guardian BORG"; + partyValues[1][1] = vm.toString(guardianBorg); + partyValues[1][2] = "inbox@guardian.borg"; + partyValues[1][3] = "Foundation"; + return partyValues; + } } diff --git a/scripts/proposeServiceAgreement.s.sol b/scripts/proposeServiceAgreement.s.sol new file mode 100644 index 0000000..8031af4 --- /dev/null +++ b/scripts/proposeServiceAgreement.s.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompConfig2024_2025} from "./lib/ZkSyncGuardianCompConfig2024_2025.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreementUtils.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract ProposeServiceAgreementScript is ZkSyncGuardianCompConfig2024_2025, SafeTxHelper, Script { + + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + vm.envUint("METALEX_PROPOSER_PRIVATE_KEY"), + registry + ); + } + + /// @dev For running in tests + function run( + uint256 proposerPrivateKey, + CyberAgreementRegistry _registry + ) public virtual returns(bytes32) { + address metalexProposer = vm.addr(proposerPrivateKey); + registry = _registry; + + // Assume Guardian SAFE already delegate signing to the deployer + + // Propose a new deal + + address[] memory parties = new address[](2); + parties[0] = address(metalexSafe); + parties[1] = address(guardianSafe); + + string[] memory globalValues = _serviceFormatGlobalValues(serviceAgreementExpiry); + string[][] memory partyValues = _serviceFormatPartyValues(address(metalexProposer), address(guardianSafe)); + + uint256 agreementSalt = block.timestamp; + + bytes32 expectedContractId = keccak256( + abi.encode( + serviceTemplateId, + agreementSalt, // salt, + globalValues, + parties + ) + ); + + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + expectedContractId, + serviceAgreementUri, + serviceGlobalFields, + servicePartyFields, + globalValues, + partyValues[0], + proposerPrivateKey + ); + + vm.startBroadcast(proposerPrivateKey); + + bytes32 contractId = registry.createContract( + serviceTemplateId, + agreementSalt, + globalValues, + parties, + partyValues, + bytes32(0), // no secrets + address(0), // no finalizer + serviceAgreementExpiry + ); + + registry.signContract( + contractId, + partyValues[0], + signature, + false, // fillUnallocated + "" // no secrets + ); + + vm.stopBroadcast(); + + console2.log("MetaLeX proposer: ", address(metalexProposer)); + console2.log("Guardian Safe: ", address(guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(registry)); + console2.log("Created:"); + console2.log(" Agreement ID:"); + console2.logBytes32(contractId); + + return contractId; + } +} diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 1023ae0..50459da 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -11,12 +11,14 @@ import {console2} from "forge-std/console2.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; import {DeployZkSyncGuardianCompensationPrerequisitesScript} from "../scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol"; import {DeployZkSyncGuardianCompensation2024_2025Script} from "../scripts/deployZkSyncGuardianCompensation2024_2025.s.sol"; +import {ProposeServiceAgreementScript} from "../scripts/proposeServiceAgreement.s.sol"; import {GnosisTransaction} from "./lib/safe.sol"; // Test by forge test --zksync --via-ir contract ZkSyncGuardianCompensationTest is DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensation2024_2025Script, + ProposeServiceAgreementScript, Test { // zkSync Era mainnet @ 63631890 @@ -27,13 +29,15 @@ contract ZkSyncGuardianCompensationTest is uint256 deployerPrivateKey = privateKeySalt + 0; address deployer = vm.addr(deployerPrivateKey); - uint256 delegatePrivateKey = privateKeySalt + 1; - address delegate = vm.addr(delegatePrivateKey); - uint256 alicePrivateKey = privateKeySalt + 2; + uint256 metalexDelegatePrivateKey = privateKeySalt + 1; + address metalexDelegate = vm.addr(metalexDelegatePrivateKey); + uint256 guardianDelegatePrivateKey = privateKeySalt + 2; + address guardianDelegate = vm.addr(guardianDelegatePrivateKey); + uint256 alicePrivateKey = privateKeySalt + 3; address alice = vm.addr(alicePrivateKey); - uint256 bobPrivateKey = privateKeySalt + 3; + uint256 bobPrivateKey = privateKeySalt + 4; address bob = vm.addr(bobPrivateKey); - uint256 chadPrivateKey = privateKeySalt + 4; + uint256 chadPrivateKey = privateKeySalt + 5; address chad = vm.addr(chadPrivateKey); IZkCappedMinterV2 masterMinter; @@ -42,7 +46,10 @@ contract ZkSyncGuardianCompensationTest is metavestController controller; function setUp() public { + // Prepare funds for accounts used by the actual deployment scripts deal(deployer, 1 ether); + deal(metalexDelegate, 1 ether); + deal(guardianDelegate, 1 ether); // Run deploy scripts GnosisTransaction[] memory safeTxs; @@ -70,7 +77,8 @@ contract ZkSyncGuardianCompensationTest is function run() public override( DeployZkSyncGuardianCompensationPrerequisitesScript, - DeployZkSyncGuardianCompensation2024_2025Script + DeployZkSyncGuardianCompensation2024_2025Script, + ProposeServiceAgreementScript ) { // No-op, we don't use this part of the scripts } @@ -113,6 +121,28 @@ contract ZkSyncGuardianCompensationTest is vm.assertTrue(zkCappedMinter.hasRole(zkCappedMinter.MINTER_ROLE(), address(controller)), "ZK Capped Minter should grant MetaVesTController MINTER role"); } + function test_ProposeServiceAgreement() public { + // Simulate MetaLeX SAFE delegation + vm.prank(address(metalexSafe)); + registry.setDelegation(metalexDelegate, block.timestamp + 60); + assertTrue(registry.isValidDelegate(address(metalexSafe), metalexDelegate), "should be MetaLeX SAFE's delegate"); + + // Simulate MetaLeX delegate proposing and signing agreement + bytes32 agreementId = ProposeServiceAgreementScript.run(metalexDelegatePrivateKey, registry); + + // Verify agreement + + (bytes32 templateId, , , , , , uint256 expiry) = registry.agreements(agreementId); + assertEq(templateId, serviceTemplateId, "Unexpected service agreement template ID"); + assertEq(expiry, serviceAgreementExpiry, "Unexpected service agreement expiry"); + + (, , , , , address[] memory parties, , , , ) = registry.getContractDetails(agreementId); + vm.assertEq(parties[0], address(metalexSafe), "First party should be MetaLeX SAFE"); + vm.assertEq(parties[1], address(guardianSafe), "Second party should be Guardian SAFE"); + + vm.assertTrue(registry.hasSigned(agreementId, metalexDelegate), "Should signed by MetaLeX delegate"); + } + function test_GuardianCompensation() public { (address metavestAddressAlice, address metavestAddressBob) = _guardiansSignAndTppPass(); @@ -148,15 +178,15 @@ contract ZkSyncGuardianCompensationTest is // Guardian SAFE to delegate signing to an EOA vm.prank(address(guardianSafe)); - registry.setDelegation(delegate, block.timestamp + 60); - assertTrue(registry.isValidDelegate(address(guardianSafe), delegate), "delegate should be Guardian SAFE's delegate"); + registry.setDelegation(guardianDelegate, block.timestamp + 60); + assertTrue(registry.isValidDelegate(address(guardianSafe), guardianDelegate), "should be Guardian SAFE's delegate"); bytes32 contractIdChad = _proposeAndSignDeal( registry, controller, compTemplateId, block.timestamp, // salt - delegatePrivateKey, + guardianDelegatePrivateKey, chad, BaseAllocation.Allocation({ tokenContract: address(zkToken), @@ -188,8 +218,8 @@ contract ZkSyncGuardianCompensationTest is function _guardiansSignAndTppPass() internal returns(address, address) { // Guardian SAFE to delegate signing to an EOA vm.prank(address(guardianSafe)); - registry.setDelegation(delegate, block.timestamp + 60); - assertTrue(registry.isValidDelegate(address(guardianSafe), delegate), "delegate should be Guardian SAFE's delegate"); + registry.setDelegation(guardianDelegate, block.timestamp + 60); + assertTrue(registry.isValidDelegate(address(guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); // Guardian SAFE to propose deals on MetaVesTController @@ -199,7 +229,7 @@ contract ZkSyncGuardianCompensationTest is controller, compTemplateId, block.timestamp, // salt - delegatePrivateKey, + guardianDelegatePrivateKey, alice, BaseAllocation.Allocation({ tokenContract: address(zkToken), @@ -222,7 +252,7 @@ contract ZkSyncGuardianCompensationTest is controller, compTemplateId, block.timestamp, // salt - delegatePrivateKey, + guardianDelegatePrivateKey, bob, BaseAllocation.Allocation({ tokenContract: address(zkToken), From e50e43ca18873a55009e029a66e7ea658b3945a1 Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 25 Aug 2025 16:49:01 -0700 Subject: [PATCH 40/68] chore: unify naming --- scripts/deployZkSyncGuardianCompensation2024_2025.s.sol | 4 ++-- scripts/deployZkSyncGuardianCompensation2025_2026.s.sol | 2 +- scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol | 4 ++-- ...2025.sol => ZkSyncGuardianCompensationConfig2024_2025.sol} | 4 ++-- ...2026.sol => ZkSyncGuardianCompensationConfig2025_2026.sol} | 4 ++-- ...onfigBase.sol => ZkSyncGuardianCompensationConfigBase.sol} | 2 +- scripts/proposeServiceAgreement.s.sol | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) rename scripts/lib/{ZkSyncGuardianCompConfig2024_2025.sol => ZkSyncGuardianCompensationConfig2024_2025.sol} (91%) rename scripts/lib/{ZkSyncGuardianCompConfig2025_2026.sol => ZkSyncGuardianCompensationConfig2025_2026.sol} (90%) rename scripts/lib/{ZkSyncGuardianCompConfigBase.sol => ZkSyncGuardianCompensationConfigBase.sol} (99%) diff --git a/scripts/deployZkSyncGuardianCompensation2024_2025.s.sol b/scripts/deployZkSyncGuardianCompensation2024_2025.s.sol index 02fe268..62458c7 100644 --- a/scripts/deployZkSyncGuardianCompensation2024_2025.s.sol +++ b/scripts/deployZkSyncGuardianCompensation2024_2025.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompConfig2024_2025} from "./lib/ZkSyncGuardianCompConfig2024_2025.sol"; +import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -13,7 +13,7 @@ import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; -contract DeployZkSyncGuardianCompensation2024_2025Script is ZkSyncGuardianCompConfig2024_2025, SafeTxHelper, Script { +contract DeployZkSyncGuardianCompensation2024_2025Script is ZkSyncGuardianCompensationConfig2024_2025, SafeTxHelper, Script { // Assume zkSync Era mainnet @ 64166260 /// @dev For running from `forge script`. Provide the deployer private key through env var. diff --git a/scripts/deployZkSyncGuardianCompensation2025_2026.s.sol b/scripts/deployZkSyncGuardianCompensation2025_2026.s.sol index 5823a6f..00f33e3 100644 --- a/scripts/deployZkSyncGuardianCompensation2025_2026.s.sol +++ b/scripts/deployZkSyncGuardianCompensation2025_2026.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompConfig2024_2025} from "./lib/ZkSyncGuardianCompConfig2024_2025.sol"; +import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol index a32d74c..5a4f524 100644 --- a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol +++ b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompConfigBase} from "./lib/ZkSyncGuardianCompConfigBase.sol"; +import {ZkSyncGuardianCompensationConfigBase} from "./lib/ZkSyncGuardianCompensationConfigBase.sol"; import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -15,7 +15,7 @@ import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; -contract DeployZkSyncGuardianCompensationPrerequisitesScript is ZkSyncGuardianCompConfigBase, SafeTxHelper, Script { +contract DeployZkSyncGuardianCompensationPrerequisitesScript is ZkSyncGuardianCompensationConfigBase, SafeTxHelper, Script { // Assume zkSync Era mainnet @ 64166260 /// @dev For running from `forge script`. Provide the deployer private key through env var. diff --git a/scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol similarity index 91% rename from scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol rename to scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol index 1e50cfa..8abb7a1 100644 --- a/scripts/lib/ZkSyncGuardianCompConfig2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.28; -import {ZkSyncGuardianCompConfigBase} from "./ZkSyncGuardianCompConfigBase.sol"; +import {ZkSyncGuardianCompensationConfigBase} from "./ZkSyncGuardianCompensationConfigBase.sol"; import {CommonBase} from "forge-std/Base.sol"; import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -10,7 +10,7 @@ import {IGnosisSafe} from "../../test/lib/safe.sol"; import {BaseAllocation} from "../../src/BaseAllocation.sol"; import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; -contract ZkSyncGuardianCompConfig2024_2025 is ZkSyncGuardianCompConfigBase { +contract ZkSyncGuardianCompensationConfig2024_2025 is ZkSyncGuardianCompensationConfigBase { // ZK Governance // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(0xE555FC98E45637D1B45e60E4fc05cF0F22836156); diff --git a/scripts/lib/ZkSyncGuardianCompConfig2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol similarity index 90% rename from scripts/lib/ZkSyncGuardianCompConfig2025_2026.sol rename to scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol index ec0575b..e890679 100644 --- a/scripts/lib/ZkSyncGuardianCompConfig2025_2026.sol +++ b/scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.28; -import {ZkSyncGuardianCompConfigBase} from "./ZkSyncGuardianCompConfigBase.sol"; +import {ZkSyncGuardianCompensationConfigBase} from "./ZkSyncGuardianCompensationConfigBase.sol"; import {CommonBase} from "forge-std/Base.sol"; import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -9,7 +9,7 @@ import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMin import {IGnosisSafe} from "../../test/lib/safe.sol"; import {BaseAllocation} from "../../src/BaseAllocation.sol"; -contract ZkSyncGuardianCompConfig2025_2026 is ZkSyncGuardianCompConfigBase { +contract ZkSyncGuardianCompensationConfig2025_2026 is ZkSyncGuardianCompensationConfigBase { // ZK Governance // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A); diff --git a/scripts/lib/ZkSyncGuardianCompConfigBase.sol b/scripts/lib/ZkSyncGuardianCompensationConfigBase.sol similarity index 99% rename from scripts/lib/ZkSyncGuardianCompConfigBase.sol rename to scripts/lib/ZkSyncGuardianCompensationConfigBase.sol index 1b52383..44c5c38 100644 --- a/scripts/lib/ZkSyncGuardianCompConfigBase.sol +++ b/scripts/lib/ZkSyncGuardianCompensationConfigBase.sol @@ -10,7 +10,7 @@ import {IGnosisSafe} from "../../test/lib/safe.sol"; import {BaseAllocation} from "../../src/BaseAllocation.sol"; import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; -contract ZkSyncGuardianCompConfigBase is CommonBase { +contract ZkSyncGuardianCompensationConfigBase is CommonBase { // Assume zkSync Era mainnet @ 64202885 // MetaLeX SAFE diff --git a/scripts/proposeServiceAgreement.s.sol b/scripts/proposeServiceAgreement.s.sol index 8031af4..8aaad43 100644 --- a/scripts/proposeServiceAgreement.s.sol +++ b/scripts/proposeServiceAgreement.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompConfig2024_2025} from "./lib/ZkSyncGuardianCompConfig2024_2025.sol"; +import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -17,7 +17,7 @@ import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; -contract ProposeServiceAgreementScript is ZkSyncGuardianCompConfig2024_2025, SafeTxHelper, Script { +contract ProposeServiceAgreementScript is ZkSyncGuardianCompensationConfig2024_2025, SafeTxHelper, Script { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { From 50dca57ecf41cc5986e92f9e4b61db13a5677a9f Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 25 Aug 2025 20:34:12 -0700 Subject: [PATCH 41/68] test: integrate deploy scripts to tests (full deal lifecycle) --- ...yncGuardianCompensationPrerequisites.s.sol | 4 +- ...yncGuardianCompensationConfig2024_2025.sol | 164 ++++++++- ...yncGuardianCompensationConfig2025_2026.sol | 16 +- .../ZkSyncGuardianCompensationConfigBase.sol | 143 +------- scripts/proposeMetavestDeal.s.sol | 139 ++++++++ scripts/proposeServiceAgreement.s.sol | 2 +- scripts/signDealAndCreateMetavest.s.sol | 94 ++++++ test/ZkSyncGuardianCompensation.t.sol | 319 ++++-------------- 8 files changed, 460 insertions(+), 421 deletions(-) create mode 100644 scripts/proposeMetavestDeal.s.sol create mode 100644 scripts/signDealAndCreateMetavest.s.sol diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol index 5a4f524..720bddc 100644 --- a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol +++ b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompensationConfigBase} from "./lib/ZkSyncGuardianCompensationConfigBase.sol"; +import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -15,7 +15,7 @@ import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; -contract DeployZkSyncGuardianCompensationPrerequisitesScript is ZkSyncGuardianCompensationConfigBase, SafeTxHelper, Script { +contract DeployZkSyncGuardianCompensationPrerequisitesScript is ZkSyncGuardianCompensationConfig2024_2025, SafeTxHelper, Script { // Assume zkSync Era mainnet @ 64166260 /// @dev For running from `forge script`. Provide the deployer private key through env var. diff --git a/scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol index 8abb7a1..1b61aa5 100644 --- a/scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol @@ -9,20 +9,95 @@ import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMin import {IGnosisSafe} from "../../test/lib/safe.sol"; import {BaseAllocation} from "../../src/BaseAllocation.sol"; import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; +import {metavestController} from "../../src/MetaVesTController.sol"; contract ZkSyncGuardianCompensationConfig2024_2025 is ZkSyncGuardianCompensationConfigBase { // ZK Governance // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(0xE555FC98E45637D1B45e60E4fc05cF0F22836156); - // MetaVesT deployment - CyberAgreementRegistry registry = CyberAgreementRegistry(address(0)); // TODO TBD - VestingAllocationFactory vestingAllocationFactory = VestingAllocationFactory(address(0)); // TODO TBD + // MetaLeX <> zkSync Guardian BORG Service Agreement template + + string serviceAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP + string serviceTemplateName = "MetaLeX <> zkSync Guardian BORG Service Agreement"; // TODO WIP + bytes32 serviceTemplateId = bytes32(uint256(200)); // TODO TBD + + string[] serviceGlobalFields; + string[] servicePartyFields; + + // zkSync Guardian Compensation Agreement template + + string compAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP + string compTemplateName = "zkSync Guardian Compensation Agreement"; // TODO WIP + bytes32 compTemplateId = bytes32(uint256(201)); // TODO TBD + + string[] compGlobalFields; + string[] compPartyFields; // MetaLeX <> zkSync Guardian BORG Service Agreement parameters uint256 serviceAgreementExpiry = 1788220800; + // Vesting parameters + + // TODO TBD + PartyInfo guardianSafeInfo = PartyInfo({ + name: "Guardian BORG", + evmAddress: address(guardianSafe), + contactDetails: "inbox@guardian.borg", + _type: "Foundation" + }); + + address[] guardians; + uint256 fixedAnnualCompensation = 625e3 ether; + uint48 metavestVestingAndUnlockStartTime = 1725148800; // 2024/09/01 00:00 UTC (means by the deployment @ 2025/09/01 it would've been fully unlocked) + BaseAllocation.Milestone[] milestones = new BaseAllocation.Milestone[](0); + + // Deployments + + metavestController controller = metavestController(address(0)); // TODO TBD + + // Support data structures + + struct PartyInfo { + string name; + address evmAddress; + string contactDetails; + string _type; + } + constructor() { + // zkSync Guardian Compensation Agreement template + + compGlobalFields = new string[](11); + compGlobalFields[0] = "metavestType"; + compGlobalFields[1] = "grantor"; + compGlobalFields[2] = "grantee"; + compGlobalFields[3] = "tokenContract"; + compGlobalFields[4] = "tokenStreamTotal"; + compGlobalFields[5] = "vestingCliffCredit"; + compGlobalFields[6] = "unlockingCliffCredit"; + compGlobalFields[7] = "vestingRate"; + compGlobalFields[8] = "vestingStartTime"; + compGlobalFields[9] = "unlockRate"; + compGlobalFields[10] = "unlockStartTime"; + + compPartyFields = new string[](4); + compPartyFields[0] = "name"; + compPartyFields[1] = "evmAddress"; + compPartyFields[2] = "contactDetails"; + compPartyFields[3] = "type"; + + // MetaLeX <> zkSync Guardian BORG Service Agreement template + + serviceGlobalFields = new string[](1); + serviceGlobalFields[0] = "expiryDate"; + + servicePartyFields = new string[](4); + servicePartyFields[0] = "name"; + servicePartyFields[1] = "evmAddress"; + servicePartyFields[2] = "contactDetails"; + servicePartyFields[3] = "type"; + // Vesting parameters guardians = new address[](6); @@ -32,8 +107,87 @@ contract ZkSyncGuardianCompensationConfig2024_2025 is ZkSyncGuardianCompensation guardians[3] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD guardians[4] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD guardians[5] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD + } + + function _parseAllocation(address token, uint48 startTime) internal returns(BaseAllocation.Allocation memory) { + return BaseAllocation.Allocation({ + tokenContract: token, + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: fixedAnnualCompensation, + vestingCliffCredit: 0e3 ether, + unlockingCliffCredit: 0e3 ether, + vestingRate: uint160(fixedAnnualCompensation / 365 days), + vestingStartTime: startTime, // start along with capped minter + unlockRate: uint160(fixedAnnualCompensation / 365 days), + unlockStartTime: startTime // start along with capped minter + }); + } + + function _compFormatGlobalValues( + address grantor, + address grantee, + address token, + uint48 startTime + ) internal returns(string[] memory) { + BaseAllocation.Allocation memory allocation = _parseAllocation(token, startTime); + + string[] memory globalValues = new string[](11); + globalValues[0] = "0"; // metavestType: Vesting + globalValues[1] = vm.toString(grantor); // grantor + globalValues[2] = vm.toString(grantee); // grantee + globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract + globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) + globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) + globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) + globalValues[7] = vm.toString(fixedAnnualCompensation / 1 ether); // vestingRate (annually) (human-readable) + globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime + globalValues[9] = vm.toString(fixedAnnualCompensation / 1 ether); // unlockRate (annually) (human-readable) + globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime + return globalValues; + } + + function _compFormatPartyValues( + PartyInfo memory partyInfo + ) internal returns(string[] memory) { + string[] memory partyValues = new string[](4); + partyValues[0] = partyInfo.name; + partyValues[1] = vm.toString(partyInfo.evmAddress); + partyValues[2] = partyInfo.contactDetails; + partyValues[3] = partyInfo._type; + return partyValues; + } + + function _compFormatPartyValues( + PartyInfo memory guardianSafeInfo, + PartyInfo memory guardianInfo + ) internal returns(string[][] memory) { + string[][] memory partyValues = new string[][](2); + partyValues[0] = _compFormatPartyValues(guardianSafeInfo); + partyValues[1] = _compFormatPartyValues(guardianInfo); + return partyValues; + } + + function _serviceFormatGlobalValues(uint256 expiry) internal returns(string[] memory) { + string[] memory globalValues = new string[](1); + globalValues[0] = vm.toString(expiry); + return globalValues; + } - fixedAnnualCompensation = 625e3 ether; - metavestVestingAndUnlockStartTime = 1725148800; // 2024/09/01 00:00 UTC (means by the deployment @ 2025/09/01 it would've been fully unlocked) + function _serviceFormatPartyValues( + address metalex, + address guardianBorg + ) internal returns(string[][] memory) { + string[][] memory partyValues = new string[][](2); + partyValues[0] = new string[](4); + partyValues[0][0] = "MetaLeX"; + partyValues[0][1] = vm.toString(metalex); + partyValues[0][2] = "test@metalex.tech"; + partyValues[0][3] = "Corporation"; + partyValues[1] = new string[](4); + partyValues[1][0] = "Guardian BORG"; + partyValues[1][1] = vm.toString(guardianBorg); + partyValues[1][2] = "inbox@guardian.borg"; + partyValues[1][3] = "Foundation"; + return partyValues; } } diff --git a/scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol index e890679..ba1a44f 100644 --- a/scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol +++ b/scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.28; -import {ZkSyncGuardianCompensationConfigBase} from "./ZkSyncGuardianCompensationConfigBase.sol"; +import {ZkSyncGuardianCompensationConfig2024_2025} from "./ZkSyncGuardianCompensationConfig2024_2025.sol"; import {CommonBase} from "forge-std/Base.sol"; import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -9,22 +9,14 @@ import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMin import {IGnosisSafe} from "../../test/lib/safe.sol"; import {BaseAllocation} from "../../src/BaseAllocation.sol"; -contract ZkSyncGuardianCompensationConfig2025_2026 is ZkSyncGuardianCompensationConfigBase { +contract ZkSyncGuardianCompensationConfig2025_2026 is ZkSyncGuardianCompensationConfig2024_2025 { // ZK Governance // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A); - // MetaVesT - CyberAgreementRegistry registry = CyberAgreementRegistry(0); // TODO TBD - VestingAllocationFactory vestingAllocationFactory = VestingAllocationFactory(0); // TODO TBD - - // Vesting parameters - - uint256 fixedAnnualCompensation = 625e3 ether; - uint48 metavestVestingAndUnlockStartTime = 1756684800; // 2025/09/01 00:00 UTC - constructor() { - super(); + fixedAnnualCompensation = 625e3 ether; + metavestVestingAndUnlockStartTime = 1756684800; // 2025/09/01 00:00 UTC guardians = new string[](6); guardians[0] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD diff --git a/scripts/lib/ZkSyncGuardianCompensationConfigBase.sol b/scripts/lib/ZkSyncGuardianCompensationConfigBase.sol index 44c5c38..46b02dd 100644 --- a/scripts/lib/ZkSyncGuardianCompensationConfigBase.sol +++ b/scripts/lib/ZkSyncGuardianCompensationConfigBase.sol @@ -25,145 +25,8 @@ contract ZkSyncGuardianCompensationConfigBase is CommonBase { IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); - // MetaLeX <> zkSync Guardian BORG Service Agreement template + // Deployments - string serviceAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP - string serviceTemplateName = "MetaLeX <> zkSync Guardian BORG Service Agreement"; // TODO WIP - bytes32 serviceTemplateId = bytes32(uint256(200)); // TODO TBD - - string[] serviceGlobalFields; - string[] servicePartyFields; - - // zkSync Guardian Compensation Agreement template - - string compAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP - string compTemplateName = "zkSync Guardian Compensation Agreement"; // TODO WIP - bytes32 compTemplateId = bytes32(uint256(201)); // TODO TBD - - string[] compGlobalFields; - string[] compPartyFields; - - BaseAllocation.Milestone[] milestones = new BaseAllocation.Milestone[](0); - - // Vesting parameters (should be overridden by child deploy configs) - - address[] guardians; - uint256 fixedAnnualCompensation; - uint48 metavestVestingAndUnlockStartTime; - - constructor() { - - // zkSync Guardian Compensation Agreement template - - compGlobalFields = new string[](11); - compGlobalFields[0] = "metavestType"; - compGlobalFields[1] = "grantor"; - compGlobalFields[2] = "grantee"; - compGlobalFields[3] = "tokenContract"; - compGlobalFields[4] = "tokenStreamTotal"; - compGlobalFields[5] = "vestingCliffCredit"; - compGlobalFields[6] = "unlockingCliffCredit"; - compGlobalFields[7] = "vestingRate"; - compGlobalFields[8] = "vestingStartTime"; - compGlobalFields[9] = "unlockRate"; - compGlobalFields[10] = "unlockStartTime"; - - compPartyFields = new string[](4); - compPartyFields[0] = "name"; - compPartyFields[1] = "evmAddress"; - compPartyFields[2] = "contactDetails"; - compPartyFields[3] = "type"; - - // MetaLeX <> zkSync Guardian BORG Service Agreement template - - serviceGlobalFields = new string[](1); - serviceGlobalFields[0] = "expiryDate"; - - servicePartyFields = new string[](4); - servicePartyFields[0] = "name"; - servicePartyFields[1] = "evmAddress"; - servicePartyFields[2] = "contactDetails"; - servicePartyFields[3] = "type"; - } - - function _parseAllocation(address token, uint48 startTime) internal returns(BaseAllocation.Allocation memory) { - return BaseAllocation.Allocation({ - tokenContract: token, - // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: fixedAnnualCompensation, - vestingCliffCredit: 0e3 ether, - unlockingCliffCredit: 0e3 ether, - vestingRate: uint160(fixedAnnualCompensation / 365 days), - vestingStartTime: startTime, // start along with capped minter - unlockRate: uint160(fixedAnnualCompensation / 365 days), - unlockStartTime: startTime // start along with capped minter - }); - } - - function _compFormatGlobalValues( - address grantor, - address grantee, - address token, - uint48 startTime - ) internal returns(string[] memory) { - BaseAllocation.Allocation memory allocation = _parseAllocation(token, startTime); - - string[] memory globalValues = new string[](11); - globalValues[0] = "0"; // metavestType: Vesting - globalValues[1] = vm.toString(grantor); // grantor - globalValues[2] = vm.toString(grantee); // grantee - globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract - globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) - globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) - globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) - globalValues[7] = vm.toString(fixedAnnualCompensation / 1 ether); // vestingRate (annually) (human-readable) - globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime - globalValues[9] = vm.toString(fixedAnnualCompensation / 1 ether); // unlockRate (annually) (human-readable) - globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime - return globalValues; - } - - function _compFormatPartyValues( - address grantor, - address grantee, - string memory granteeName - ) internal returns(string[][] memory) { - // TODO WIP - string[][] memory partyValues = new string[][](2); - partyValues[0] = new string[](4); - partyValues[0][0] = "Guardian BORG"; - partyValues[0][1] = vm.toString(grantor); - partyValues[0][2] = "inbox@guardian.borg"; - partyValues[0][3] = "Foundation"; - partyValues[1] = new string[](4); - partyValues[1][0] = granteeName; - partyValues[1][1] = vm.toString(grantee); - partyValues[1][2] = "email@company.com"; - partyValues[1][3] = "individual"; - return partyValues; - } - - function _serviceFormatGlobalValues(uint256 expiry) internal returns(string[] memory) { - string[] memory globalValues = new string[](1); - globalValues[0] = vm.toString(expiry); - return globalValues; - } - - function _serviceFormatPartyValues( - address metalex, - address guardianBorg - ) internal returns(string[][] memory) { - string[][] memory partyValues = new string[][](2); - partyValues[0] = new string[](4); - partyValues[0][0] = "MetaLeX"; - partyValues[0][1] = vm.toString(metalex); - partyValues[0][2] = "test@metalex.tech"; - partyValues[0][3] = "Corporation"; - partyValues[1] = new string[](4); - partyValues[1][0] = "Guardian BORG"; - partyValues[1][1] = vm.toString(guardianBorg); - partyValues[1][2] = "inbox@guardian.borg"; - partyValues[1][3] = "Foundation"; - return partyValues; - } + CyberAgreementRegistry registry = CyberAgreementRegistry(address(0)); // TODO TBD + VestingAllocationFactory vestingAllocationFactory = VestingAllocationFactory(address(0)); // TODO TBD } diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol new file mode 100644 index 0000000..9248a31 --- /dev/null +++ b/scripts/proposeMetavestDeal.s.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreementUtils.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console} from "forge-std/console.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract ProposeMetaVestDealScript is ZkSyncGuardianCompensationConfig2024_2025, SafeTxHelper, Script { + + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), + registry, + controller, + guardianSafeInfo, + PartyInfo({ + name: "Alice", + evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b, + contactDetails: "email@company.com", + _type: "individual" + }) + ); + } + + /// @dev For running in tests + function run( + uint256 proposerPrivateKey, + CyberAgreementRegistry registry, + metavestController controller, + PartyInfo memory guardianSafeInfo, + PartyInfo memory guardianInfo + ) public virtual returns(bytes32) { + return run( + proposerPrivateKey, registry, controller, guardianSafeInfo, guardianInfo, + // Default guardian allocations + _parseAllocation(address(zkToken), metavestVestingAndUnlockStartTime) + ); + } + + /// @dev For running in tests + function run( + uint256 proposerPrivateKey, + CyberAgreementRegistry registry, + metavestController controller, + PartyInfo memory guardianSafeInfo, + PartyInfo memory guardianInfo, + BaseAllocation.Allocation memory allocation + ) public virtual returns(bytes32) { + + address proposer = vm.addr(proposerPrivateKey); + + // Assume Guardian SAFE already delegate signing to the deployer + + // Propose a new deal + + uint48 startTime = metavestVestingAndUnlockStartTime; + + address[] memory parties = new address[](2); + parties[0] = address(guardianSafe); + parties[1] = guardianInfo.evmAddress; + + string[] memory globalValues = _compFormatGlobalValues( + address(guardianSafe), + guardianInfo.evmAddress, + address(zkToken), + startTime + ); + string[][] memory partyValues = _compFormatPartyValues(guardianSafeInfo, guardianInfo); + + uint256 agreementSalt = block.timestamp; + + bytes32 expectedContractId = keccak256( + abi.encode( + compTemplateId, + agreementSalt, // salt, + globalValues, + parties + ) + ); + + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + expectedContractId, + compAgreementUri, + compGlobalFields, + compPartyFields, + globalValues, + partyValues[0], + proposerPrivateKey + ); + + vm.startBroadcast(proposerPrivateKey); + + bytes32 contractId = controller.proposeAndSignDeal( + compTemplateId, + agreementSalt, + metavestController.metavestType.Vesting, + guardianInfo.evmAddress, + allocation, + milestones, + globalValues, + parties, + partyValues, + signature, + bytes32(0), // no secrets + block.timestamp + 30 days * 2 + ); + + vm.stopBroadcast(); + + console.log("Proposer: ", proposer); + console.log("Guardian Safe: ", address(guardianSafe)); + console.log("ZK token: ", address(zkToken)); + console.log("CyberAgreementRegistry: ", address(registry)); + console.log("VestingAllocationFactory: ", address(vestingAllocationFactory)); + console.log("MetavesTController: ", address(controller)); + console.log("ZkCappedMinterV2: ", address(zkCappedMinter)); + console.log("Created:"); + console.log(" Agreement ID:"); + console.logBytes32(contractId); + + return contractId; + } +} diff --git a/scripts/proposeServiceAgreement.s.sol b/scripts/proposeServiceAgreement.s.sol index 8aaad43..74b0af1 100644 --- a/scripts/proposeServiceAgreement.s.sol +++ b/scripts/proposeServiceAgreement.s.sol @@ -22,7 +22,7 @@ contract ProposeServiceAgreementScript is ZkSyncGuardianCompensationConfig2024_2 /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { run( - vm.envUint("METALEX_PROPOSER_PRIVATE_KEY"), + vm.envUint("METALEX_SAFE_DELEGATE_PRIVATE_KEY"), registry ); } diff --git a/scripts/signDealAndCreateMetavest.s.sol b/scripts/signDealAndCreateMetavest.s.sol new file mode 100644 index 0000000..b1c7de0 --- /dev/null +++ b/scripts/signDealAndCreateMetavest.s.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreementUtils.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console} from "forge-std/console.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract SignDealAndCreateMetavestScript is ZkSyncGuardianCompensationConfig2024_2025, SafeTxHelper, Script { + + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + uint256 granteePrivateKey = vm.envUint("GRANTEE_PRIVATE_KEY"); + run( + granteePrivateKey, + registry, + controller, + 0x0000000000000000000000000000000000000000000000000000000000000000, // TODO TBD + PartyInfo({ // TODO TBD + name: "Alice", + evmAddress: vm.addr(granteePrivateKey), + contactDetails: "email@company.com", + _type: "individual" + }) + ); + } + + /// @dev For running in tests + function run( + uint256 granteePrivateKey, + CyberAgreementRegistry registry, + metavestController controller, + bytes32 agreementId, + PartyInfo memory granteeInfo + ) public virtual returns(address) { + + // Sign the deal and create MetaVesT + + string[] memory granteePartyValues = _compFormatPartyValues(granteeInfo); + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + agreementId, + compAgreementUri, + compGlobalFields, + compPartyFields, + _compFormatGlobalValues( + address(guardianSafe), + granteeInfo.evmAddress, + address(zkToken), + metavestVestingAndUnlockStartTime + ), + granteePartyValues, + granteePrivateKey + ); + + vm.startBroadcast(granteePrivateKey); + + address metavest = controller.signDealAndCreateMetavest( + granteeInfo.evmAddress, + granteeInfo.evmAddress, + agreementId, + granteePartyValues, + signature, + "" // no secrets + ); + + vm.stopBroadcast(); + + console.log("Grantee: ", granteeInfo.evmAddress); + console.log("Grantee Name: ", granteeInfo.name); + console.log("Guardian Safe: ", address(guardianSafe)); + console.log("CyberAgreementRegistry: ", address(registry)); + console.log("MetavesTController: ", address(controller)); + console.log("Agreement ID:"); + console.logBytes32(agreementId); + console.log("Created:"); + console.log(" MetavesT: ", address(metavest)); + + return address(metavest); + } +} diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 50459da..9fa51b8 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -12,6 +12,9 @@ import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementReg import {DeployZkSyncGuardianCompensationPrerequisitesScript} from "../scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol"; import {DeployZkSyncGuardianCompensation2024_2025Script} from "../scripts/deployZkSyncGuardianCompensation2024_2025.s.sol"; import {ProposeServiceAgreementScript} from "../scripts/proposeServiceAgreement.s.sol"; +import {ProposeMetaVestDealScript} from "../scripts/proposeMetavestDeal.s.sol"; +import {SignDealAndCreateMetavestScript} from "../scripts/signDealAndCreateMetavest.s.sol"; +import {ZkSyncGuardianCompensationConfig2024_2025} from "../scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; import {GnosisTransaction} from "./lib/safe.sol"; // Test by forge test --zksync --via-ir @@ -19,6 +22,8 @@ contract ZkSyncGuardianCompensationTest is DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensation2024_2025Script, ProposeServiceAgreementScript, + ProposeMetaVestDealScript, + SignDealAndCreateMetavestScript, Test { // zkSync Era mainnet @ 63631890 @@ -43,13 +48,15 @@ contract ZkSyncGuardianCompensationTest is IZkCappedMinterV2 masterMinter; BorgAuth auth; - metavestController controller; function setUp() public { // Prepare funds for accounts used by the actual deployment scripts deal(deployer, 1 ether); deal(metalexDelegate, 1 ether); deal(guardianDelegate, 1 ether); + deal(alice, 1 ether); + deal(bob, 1 ether); + deal(chad, 1 ether); // Run deploy scripts GnosisTransaction[] memory safeTxs; @@ -78,7 +85,9 @@ contract ZkSyncGuardianCompensationTest is function run() public override( DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensation2024_2025Script, - ProposeServiceAgreementScript + ProposeServiceAgreementScript, + ProposeMetaVestDealScript, + SignDealAndCreateMetavestScript ) { // No-op, we don't use this part of the scripts } @@ -144,7 +153,7 @@ contract ZkSyncGuardianCompensationTest is } function test_GuardianCompensation() public { - (address metavestAddressAlice, address metavestAddressBob) = _guardiansSignAndTppPass(); + (address metavestAddressAlice, address metavestAddressBob) = _proposeAndFinalizeAllGuardianDeals(); VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddressAlice); VestingAllocation vestingAllocationBob = VestingAllocation(metavestAddressBob); @@ -152,7 +161,6 @@ contract ZkSyncGuardianCompensationTest is // Grantee should be able to withdraw all on 2025/09/01 because this compensation is for 2024~2025 vm.warp(1756684800 + 1); // 2025/09/01 00:00 UTC with some margin for precision error - console2.log("alice amount withdrawable: %d", vestingAllocationAlice.getAmountWithdrawable()); _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationAlice, 625e3 ether, "Alice full"); _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationBob, 615e3 ether, "Bob partial"); @@ -163,7 +171,7 @@ contract ZkSyncGuardianCompensationTest is } function test_AdminToolingCompensation() public { - (address metavestAddressAlice, address metavestAddressBob) = _guardiansSignAndTppPass(); + (address metavestAddressAlice, address metavestAddressBob) = _proposeAndFinalizeAllGuardianDeals(); VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddressAlice); // Vesting starts and a month has passed @@ -181,13 +189,18 @@ contract ZkSyncGuardianCompensationTest is registry.setDelegation(guardianDelegate, block.timestamp + 60); assertTrue(registry.isValidDelegate(address(guardianSafe), guardianDelegate), "should be Guardian SAFE's delegate"); - bytes32 contractIdChad = _proposeAndSignDeal( + ZkSyncGuardianCompensationConfig2024_2025.PartyInfo memory chadInfo = ZkSyncGuardianCompensationConfig2024_2025.PartyInfo({ + name: "Chad", + evmAddress: chad, + contactDetails: "chad@email.com", + _type: "individual" + }); + bytes32 contractIdChad = ProposeMetaVestDealScript.run( + guardianDelegatePrivateKey, registry, controller, - compTemplateId, - block.timestamp, // salt - guardianDelegatePrivateKey, - chad, + guardianSafeInfo, + chadInfo, BaseAllocation.Allocation({ tokenContract: address(zkToken), // 10k ZK total in one cliff @@ -198,98 +211,69 @@ contract ZkSyncGuardianCompensationTest is vestingStartTime: 0, unlockRate: 0, unlockStartTime: 0 - }), - new BaseAllocation.Milestone[](0), - "Chad", - block.timestamp + 60 + }) ); - VestingAllocation vestingAllocationChad = VestingAllocation(_granteeSignDeal( + VestingAllocation vestingAllocationChad = VestingAllocation(SignDealAndCreateMetavestScript.run( + chadPrivateKey, registry, controller, contractIdChad, - chad, // grantee - chad, // recipient - chadPrivateKey, - "Chad" + chadInfo )); _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationChad, 10e3 ether, "Chad cliff"); } - function _guardiansSignAndTppPass() internal returns(address, address) { + function _proposeAndFinalizeAllGuardianDeals() internal returns(address, address) { // Guardian SAFE to delegate signing to an EOA vm.prank(address(guardianSafe)); registry.setDelegation(guardianDelegate, block.timestamp + 60); assertTrue(registry.isValidDelegate(address(guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); - // Guardian SAFE to propose deals on MetaVesTController - - // TODO revise it to fit actual numbers - bytes32 contractIdAlice = _proposeAndSignDeal( + // Run scripts to propose deals + + ZkSyncGuardianCompensationConfig2024_2025.PartyInfo memory aliceInfo = ZkSyncGuardianCompensationConfig2024_2025.PartyInfo({ + name: "Alice", + evmAddress: alice, + contactDetails: "alice@email.com", + _type: "individual" + }); + ZkSyncGuardianCompensationConfig2024_2025.PartyInfo memory bobInfo = ZkSyncGuardianCompensationConfig2024_2025.PartyInfo({ + name: "Bob", + evmAddress: bob, + contactDetails: "bob@email.com", + _type: "individual" + }); + + bytes32 contractIdAlice = ProposeMetaVestDealScript.run( + guardianDelegatePrivateKey, registry, controller, - compTemplateId, - block.timestamp, // salt - guardianDelegatePrivateKey, - alice, - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: 625e3 ether, - vestingCliffCredit: 0e3 ether, - unlockingCliffCredit: 0e3 ether, - vestingRate: uint160(625e3 ether) / 365 days, - vestingStartTime: metavestVestingAndUnlockStartTime, - unlockRate: uint160(625e3 ether) / 365 days, - unlockStartTime: metavestVestingAndUnlockStartTime - }), - new BaseAllocation.Milestone[](0), - "Alice", - block.timestamp + 7 days + guardianSafeInfo, + aliceInfo ); - - bytes32 contractIdBob = _proposeAndSignDeal( + bytes32 contractIdBob = ProposeMetaVestDealScript.run( + guardianDelegatePrivateKey, registry, controller, - compTemplateId, - block.timestamp, // salt - guardianDelegatePrivateKey, - bob, - BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 80k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: 625e3 ether, - vestingCliffCredit: 0e3 ether, - unlockingCliffCredit: 0e3 ether, - vestingRate: uint160(625e3 ether) / 365 days, - vestingStartTime: metavestVestingAndUnlockStartTime, - unlockRate: uint160(625e3 ether) / 365 days, - unlockStartTime: metavestVestingAndUnlockStartTime - }), - new BaseAllocation.Milestone[](0), - "Bob", - block.timestamp + 7 days + guardianSafeInfo, + bobInfo ); - // Guardians to sign agreements + // Simulate guardian counter-sign and finalize the deal - address metavestAlice = _granteeSignDeal( + address metavestAlice = SignDealAndCreateMetavestScript.run( + alicePrivateKey, registry, controller, contractIdAlice, - alice, // grantee - alice, // recipient - alicePrivateKey, - "Alice" + aliceInfo ); - - address metavestBob = _granteeSignDeal( + address metavestBob = SignDealAndCreateMetavestScript.run( + bobPrivateKey, registry, controller, contractIdBob, - bob, // grantee - bob, // recipient - bobPrivateKey, - "Bob" + bobInfo ); return (metavestAlice, metavestBob); @@ -327,191 +311,4 @@ contract ZkSyncGuardianCompensationTest is assertEq(zkToken.balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); } - - function _proposeAndSignDeal( - CyberAgreementRegistry registry, - metavestController controller, - bytes32 templateId, - uint256 agreementSalt, - uint256 grantorOrDelegatePrivateKey, - address grantee, - BaseAllocation.Allocation memory allocation, - BaseAllocation.Milestone[] memory milestones, - string memory partyName, - uint256 expiry - ) internal returns(bytes32) { - return _proposeAndSignDeal( - registry, controller, templateId, agreementSalt, grantorOrDelegatePrivateKey, grantee, allocation, milestones, partyName, expiry, - "" // Not expecting revert - ); - } - - function _proposeAndSignDeal( - CyberAgreementRegistry registry, - metavestController controller, - bytes32 templateId, - uint256 agreementSalt, - uint256 grantorOrDelegatePrivateKey, - address grantee, - BaseAllocation.Allocation memory allocation, - BaseAllocation.Milestone[] memory milestones, - string memory partyName, - uint256 expiry, - bytes memory expectRevertData - ) internal returns(bytes32) { - string[] memory globalValues = new string[](11); - globalValues[0] = "0"; // metavestType: Vesting - globalValues[1] = vm.toString(address(guardianSafe)); // grantor - globalValues[2] = vm.toString(grantee); // grantee - globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract - globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) - globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) - globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) - globalValues[7] = vm.toString(allocation.vestingRate * 365 days / 1 ether); // vestingRate (annually) (human-readable) - globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime - globalValues[9] = vm.toString(allocation.unlockRate * 365 days / 1 ether); // unlockRate (annually) (human-readable) - globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime - - // TODO what to do with milestones, which could be of dynamic lengths - - string[][] memory partyValues = new string[][](2); - partyValues[0] = new string[](4); - partyValues[0][0] = "Guardian BORG"; - partyValues[0][1] = vm.toString(address(guardianSafe)); - partyValues[0][2] = "guardian-safe@company.com"; - partyValues[0][3] = "Foundation"; - partyValues[1] = new string[](4); - partyValues[1][0] = partyName; - partyValues[1][1] = vm.toString(grantee); // evmAddress - partyValues[1][2] = "email@company.com"; - partyValues[1][3] = "individual"; - - address[] memory parties = new address[](2); - parties[0] = address(guardianSafe); - parties[1] = grantee; - bytes32 expectedContractId = keccak256( - abi.encode( - templateId, - agreementSalt, - globalValues, - parties - ) - ); - - bytes memory signature = CyberAgreementUtils.signAgreementTypedData( - vm, - registry.DOMAIN_SEPARATOR(), - registry.SIGNATUREDATA_TYPEHASH(), - expectedContractId, - compAgreementUri, - compGlobalFields, - compPartyFields, - globalValues, - partyValues[0], - grantorOrDelegatePrivateKey - ); - - if (expectRevertData.length > 0) { - vm.expectRevert(expectRevertData); - } - bytes32 contractId = controller.proposeAndSignDeal( - templateId, - agreementSalt, - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - globalValues, - parties, - partyValues, - signature, - bytes32(0), // no secrets - expiry - ); - - if (expectRevertData.length == 0) { - assertEq(contractId, expectedContractId, "Unexpected contract ID"); - return contractId; - } else { - return 0; - } - } - - function _granteeSignDeal( - CyberAgreementRegistry registry, - metavestController controller, - bytes32 contractId, - address grantee, - address recipient, - uint256 granteePrivateKey, - string memory partyName - ) internal returns(address) { - return _granteeSignDeal( - registry, controller, contractId, grantee, recipient, granteePrivateKey, partyName, - "" // Not expecting revert - ); - } - - function _granteeSignDeal( - CyberAgreementRegistry registry, - metavestController controller, - bytes32 contractId, - address grantee, - address recipient, - uint256 granteePrivateKey, - string memory partyName, - bytes memory expectRevertData - ) internal returns(address) { - metavestController.DealData memory deal = controller.getDeal(contractId); - - string[] memory globalValues = new string[](11); - globalValues[0] = "0"; // metavestType: Vesting - globalValues[1] = vm.toString(address(guardianSafe)); // grantor - globalValues[2] = vm.toString(grantee); // grantee - globalValues[3] = vm.toString(deal.allocation.tokenContract); // tokenContract - globalValues[4] = vm.toString(deal.allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) - globalValues[5] = vm.toString(deal.allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) - globalValues[6] = vm.toString(deal.allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) - globalValues[7] = vm.toString(deal.allocation.vestingRate * 365 days / 1 ether); // vestingRate (annually) (human-readable) - globalValues[8] = vm.toString(deal.allocation.vestingStartTime); // vestingStartTime - globalValues[9] = vm.toString(deal.allocation.unlockRate * 365 days / 1 ether); // unlockRate (annually) (human-readable) - globalValues[10] = vm.toString(deal.allocation.unlockStartTime); // unlockStartTime - - string[] memory partyValues = new string[](4); - partyValues[0] = partyName; - partyValues[1] = vm.toString(grantee); // evmAddress - partyValues[2] = "email@company.com"; // Make sure it matches the proposed deal - partyValues[3] = "individual"; // Make sure it matches the proposed deal - - bytes memory signature = CyberAgreementUtils.signAgreementTypedData( - vm, - registry.DOMAIN_SEPARATOR(), - registry.SIGNATUREDATA_TYPEHASH(), - contractId, - compAgreementUri, - compGlobalFields, - compPartyFields, - globalValues, - partyValues, - granteePrivateKey - ); - - if (expectRevertData.length > 0) { - vm.expectRevert(expectRevertData); - } - address metavest = controller.signDealAndCreateMetavest( - grantee, - recipient, - contractId, - partyValues, - signature, - "" // no secrets - ); - - if (expectRevertData.length == 0) { - return metavest; - } else { - return address(0); - } - } } From 7745b5d9b0e2a11c2e9e3daa69fcab9be744f51a Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 26 Aug 2025 11:15:23 -0700 Subject: [PATCH 42/68] chore: refactor deploy scripts and use dynamic configs --- ...=> deployZkSyncGuardianCompensation.s.sol} | 42 ++- ...yZkSyncGuardianCompensation2025_2026.s.sol | 87 ------ ...yncGuardianCompensationPrerequisites.s.sol | 44 +-- .../ZkSyncGuardianCompensation2024_2025.sol | 225 ++++++++++++++++ .../ZkSyncGuardianCompensation2025_2026.sol | 89 ++++++ ...yncGuardianCompensationConfig2024_2025.sol | 193 ------------- ...yncGuardianCompensationConfig2025_2026.sol | 29 -- .../ZkSyncGuardianCompensationConfigBase.sol | 32 --- scripts/proposeMetavestDeal.s.sol | 79 +++--- scripts/proposeServiceAgreement.s.sol | 42 +-- scripts/signDealAndCreateMetavest.s.sol | 48 ++-- test/ZkSyncGuardianCompensation.t.sol | 255 ++++++++++-------- 12 files changed, 594 insertions(+), 571 deletions(-) rename scripts/{deployZkSyncGuardianCompensation2024_2025.s.sol => deployZkSyncGuardianCompensation.s.sol} (71%) delete mode 100644 scripts/deployZkSyncGuardianCompensation2025_2026.s.sol create mode 100644 scripts/lib/ZkSyncGuardianCompensation2024_2025.sol create mode 100644 scripts/lib/ZkSyncGuardianCompensation2025_2026.sol delete mode 100644 scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol delete mode 100644 scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol delete mode 100644 scripts/lib/ZkSyncGuardianCompensationConfigBase.sol diff --git a/scripts/deployZkSyncGuardianCompensation2024_2025.s.sol b/scripts/deployZkSyncGuardianCompensation.s.sol similarity index 71% rename from scripts/deployZkSyncGuardianCompensation2024_2025.s.sol rename to scripts/deployZkSyncGuardianCompensation.s.sol index 62458c7..a12d914 100644 --- a/scripts/deployZkSyncGuardianCompensation2024_2025.s.sol +++ b/scripts/deployZkSyncGuardianCompensation.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -13,32 +13,27 @@ import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; -contract DeployZkSyncGuardianCompensation2024_2025Script is ZkSyncGuardianCompensationConfig2024_2025, SafeTxHelper, Script { - // Assume zkSync Era mainnet @ 64166260 - +contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { - run( + deployCompensation( + "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2024-2025", vm.envUint("DEPLOYER_PRIVATE_KEY"), - registry, - vestingAllocationFactory + ZkSyncGuardianCompensation2024_2025.getDefault() ); } /// @dev For running in tests - function run( + function deployCompensation( + string memory saltStr, uint256 deployerPrivateKey, - CyberAgreementRegistry _registry, - VestingAllocationFactory _vestingAllocationFactory + ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns( metavestController, GnosisTransaction[] memory ) { address deployer = vm.addr(deployerPrivateKey); - registry = _registry; - vestingAllocationFactory = _vestingAllocationFactory; - string memory saltStr = "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2024-2025"; bytes32 salt = keccak256(bytes(saltStr)); vm.startBroadcast(deployerPrivateKey); @@ -49,10 +44,10 @@ contract DeployZkSyncGuardianCompensation2024_2025Script is ZkSyncGuardianCompen address(new metavestController{salt: salt}()), abi.encodeWithSelector( metavestController.initialize.selector, - address(guardianSafe), - address(guardianSafe), - address(registry), - address(vestingAllocationFactory) + address(config.guardianSafe), + address(config.guardianSafe), + address(config.registry), + address(config.vestingAllocationFactory) ) ))); @@ -63,11 +58,11 @@ contract DeployZkSyncGuardianCompensation2024_2025Script is ZkSyncGuardianCompen // 2. Set MetaVesT Controller's ZK Capped Minter GnosisTransaction[] memory safeTxs = new GnosisTransaction[](2); safeTxs[0] = GnosisTransaction({ - to: address(zkCappedMinter), + to: address(config.zkCappedMinter), value: 0, data: abi.encodeWithSelector( IZkCappedMinterV2.grantRole.selector, - zkCappedMinter.MINTER_ROLE(), + config.zkCappedMinter.MINTER_ROLE(), address(controller) ) }); @@ -76,7 +71,7 @@ contract DeployZkSyncGuardianCompensation2024_2025Script is ZkSyncGuardianCompen value: 0, data: abi.encodeWithSelector( controller.setZkCappedMinter.selector, - address(zkCappedMinter) + address(config.zkCappedMinter) ) }); @@ -84,9 +79,10 @@ contract DeployZkSyncGuardianCompensation2024_2025Script is ZkSyncGuardianCompen console2.log("Deployer: ", deployer); console2.log("salt: ", saltStr); - console2.log("Guardian Safe: ", address(guardianSafe)); - console2.log("CyberAgreementRegistry: ", address(registry)); - console2.log("VestingAllocationFactory: ", address(vestingAllocationFactory)); + console2.log("ZkCappedMinterV2: ", address(config.zkCappedMinter)); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); console2.log(""); console2.log("Deployed addresses:"); diff --git a/scripts/deployZkSyncGuardianCompensation2025_2026.s.sol b/scripts/deployZkSyncGuardianCompensation2025_2026.s.sol deleted file mode 100644 index 00f33e3..0000000 --- a/scripts/deployZkSyncGuardianCompensation2025_2026.s.sol +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.24; - -import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; -import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; -import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; -import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; -import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; -import {Script} from "forge-std/Script.sol"; -import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; -import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; -import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import {console2} from "forge-std/console2.sol"; -import {metavestController} from "../src/MetaVesTController.sol"; - -contract DeployZkSyncGuardianCompensation2025_2026Script is ZkSyncGuardianCompConfig2025_2026, SafeTxHelper, Script { - // Assume zkSync Era mainnet @ 64166260 - - function run() public { - uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - - string memory saltStr = "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2025-2026"; - bytes32 salt = keccak256(saltStr); - - vm.startBroadcast(deployerPrivateKey); - - // Deploy MetaVesT Controller - - metavestController controller = metavestController(address(new ERC1967Proxy{salt: salt}( - address(new metavestController{salt: salt}()), - abi.encodeWithSelector( - metavestController.initialize.selector, - address(guardianSafe), - address(guardianSafe), - address(registry), - address(vestingAllocationFactory) - ) - ))); - - vm.stopBroadcast(); - - // Prepare Guardian SAFE txs to set MetaVesT Controller's ZK Capped Minter - GnosisTransaction[] memory safeTxs = new GnosisTransaction[](1); - safeTxs[0] = GnosisTransaction({ - to: address(controller), - value: 0, - data: abi.encodeWithSelector( - controller.setZkCappedMinter.selector, - address(zkCappedMinter) - ) - }); - - // Post-deployment verifications - - vm.assertEq(controller.authority(), address(guardianSafe), "MetaVesTController's authority should be Guardian SAFE"); - vm.assertEq(controller.dao(), address(guardianSafe), "MetaVesTController's DAO should be Guardian SAFE"); - vm.assertEq(controller.registry(), address(registry), "Unexpected MetaVesTController registry"); - vm.assertEq(controller.vestingFactory(), address(vestingAllocationFactory), "Unexpected MetaVesTController vesting allocation factory"); - - // Output logs - - console2.log("Deployer: ", deployer); - console2.log("salt: ", saltStr); - console2.log("Guardian Safe: ", address(guardianSafe)); - console2.log("CyberAgreementRegistry: ", address(registry)); - console2.log("VestingAllocationFactory: ", address(vestingAllocationFactory)); - console2.log(""); - - console2.log("Deployed addresses:"); - console2.log(" MetavesTController: ", address(controller)); - console2.log(" ZkCappedMinterV2: ", address(zkCappedMinter)); - console2.log(""); - - console2.log("Safe TXs:"); - for (uint256 i = 0 ; i < safeTxs.length ; i++) { - console2.log(" #", i); - console2.log(" to:", safeTxs[i].to); - console2.log(" value:", safeTxs[i].value); - console2.log(" data:"); - console2.logBytes(safeTxs[i].data); - console2.log(""); - } - } -} diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol index 720bddc..225e3a1 100644 --- a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol +++ b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -15,23 +15,31 @@ import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; -contract DeployZkSyncGuardianCompensationPrerequisitesScript is ZkSyncGuardianCompensationConfig2024_2025, SafeTxHelper, Script { - // Assume zkSync Era mainnet @ 64166260 +contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Script { + using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { - run(vm.envUint("DEPLOYER_PRIVATE_KEY")); + deployPrerequisites( + "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0", + vm.envUint("DEPLOYER_PRIVATE_KEY"), + ZkSyncGuardianCompensation2024_2025.getDefault() + ); } /// @dev For running in tests - function run(uint256 deployerPrivateKey) public virtual returns( + function deployPrerequisites( + string memory saltStr, + uint256 deployerPrivateKey, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns( BorgAuth, CyberAgreementRegistry, VestingAllocationFactory ) { address deployer = vm.addr(deployerPrivateKey); - bytes32 salt = keccak256("MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0"); + bytes32 salt = keccak256(bytes(saltStr)); vm.startBroadcast(deployerPrivateKey); @@ -49,25 +57,25 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is ZkSyncGuardianCo // Create zkSync Guardian Compensation Agreement template registry.createTemplate( - compTemplateId, - compTemplateName, - compAgreementUri, - compGlobalFields, - compPartyFields + config.compTemplateId, + config.compTemplateName, + config.compAgreementUri, + config.compGlobalFields, + config.compPartyFields ); // Create MetaLeX <> zkSync Guardian BORG Service Agreement template registry.createTemplate( - serviceTemplateId, - serviceTemplateName, - serviceAgreementUri, - serviceGlobalFields, - servicePartyFields + config.serviceTemplateId, + config.serviceTemplateName, + config.serviceAgreementUri, + config.serviceGlobalFields, + config.servicePartyFields ); // Transfer CyberAgreementRegistry ownership to MetaLeX SAFE - auth.updateRole(address(metalexSafe), auth.OWNER_ROLE()); + auth.updateRole(address(config.metalexSafe), auth.OWNER_ROLE()); auth.zeroOwner(); // Deploy MetaVesT pre-requisites @@ -79,7 +87,7 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is ZkSyncGuardianCo // Output logs console2.log("Deployer: ", deployer); - console2.log("Guardian Safe: ", address(guardianSafe)); + console2.log("Guardian Safe: ", address(config.guardianSafe)); console2.log(""); console2.log("Deployed addresses:"); diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol new file mode 100644 index 0000000..734adc4 --- /dev/null +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.28; + +import {Vm} from "forge-std/Vm.sol"; +import {CommonBase} from "forge-std/Base.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {IZkTokenV1} from "../../src/interfaces/zk-governance/IZkTokenV1.sol"; +import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {IGnosisSafe} from "../../test/lib/safe.sol"; +import {BaseAllocation} from "../../src/BaseAllocation.sol"; +import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; +import {metavestController} from "../../src/MetaVesTController.sol"; + +library ZkSyncGuardianCompensation2024_2025 { + + struct Config { + + // ZK Governance + + IZkTokenV1 zkToken; + IZkCappedMinterV2 zkCappedMinter; + + // zkSync Guardian SAFE + + IGnosisSafe guardianSafe; + + // MetaLeX + + IGnosisSafe metalexSafe; + CyberAgreementRegistry registry; + VestingAllocationFactory vestingAllocationFactory; + metavestController controller; + + // MetaLeX <> zkSync Guardian BORG Service Agreement + + string serviceAgreementUri; + string serviceTemplateName; + bytes32 serviceTemplateId; + string[] serviceGlobalFields; + string[] servicePartyFields; + + uint256 serviceAgreementExpiry; + + // zkSync Guardian Compensation Agreement + + string compAgreementUri; + string compTemplateName; + bytes32 compTemplateId; + string[] compGlobalFields; + string[] compPartyFields; + + PartyInfo guardianSafeInfo; + address[] guardians; + uint256 fixedAnnualCompensation; + uint48 metavestVestingAndUnlockStartTime; + BaseAllocation.Milestone[] milestones; + } + + struct PartyInfo { + string name; + address evmAddress; + string contactDetails; + string _type; + } + + function getDefault() internal view returns(Config memory) { + string[] memory serviceGlobalFields = new string[](1); + serviceGlobalFields[0] = "expiryDate"; + + string[] memory servicePartyFields = new string[](4); + servicePartyFields[0] = "name"; + servicePartyFields[1] = "evmAddress"; + servicePartyFields[2] = "contactDetails"; + servicePartyFields[3] = "type"; + + string[] memory compGlobalFields = new string[](11); + compGlobalFields[0] = "metavestType"; + compGlobalFields[1] = "grantor"; + compGlobalFields[2] = "grantee"; + compGlobalFields[3] = "tokenContract"; + compGlobalFields[4] = "tokenStreamTotal"; + compGlobalFields[5] = "vestingCliffCredit"; + compGlobalFields[6] = "unlockingCliffCredit"; + compGlobalFields[7] = "vestingRate"; + compGlobalFields[8] = "vestingStartTime"; + compGlobalFields[9] = "unlockRate"; + compGlobalFields[10] = "unlockStartTime"; + + string[] memory compPartyFields = new string[](4); + compPartyFields[0] = "name"; + compPartyFields[1] = "evmAddress"; + compPartyFields[2] = "contactDetails"; + compPartyFields[3] = "type"; + + IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); + + address[] memory guardians = new address[](0); // TODO TBD + + return Config({ + zkToken: IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E), + // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 + zkCappedMinter: IZkCappedMinterV2(0xE555FC98E45637D1B45e60E4fc05cF0F22836156), + + guardianSafe: guardianSafe, + + metalexSafe: IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc), + registry: CyberAgreementRegistry(address(0)), // TODO TBD + vestingAllocationFactory: VestingAllocationFactory(address(0)), // TODO TBD + controller: metavestController(address(0)), // TODO TBD + + serviceAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD + serviceTemplateName: "MetaLeX <> zkSync Guardian BORG Service Agreement", // TODO TBD + serviceTemplateId: bytes32(uint256(200)), + serviceGlobalFields: serviceGlobalFields, + servicePartyFields: servicePartyFields, + + serviceAgreementExpiry: 1788220800, + + compAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD + compTemplateName: "zkSync Guardian Compensation Agreement", // TODO TBD + compTemplateId: bytes32(uint256(201)), + compGlobalFields: compGlobalFields, + compPartyFields: compPartyFields, + + guardianSafeInfo: PartyInfo({ // TODO TBD + name: "Guardian BORG", + evmAddress: address(guardianSafe), + contactDetails: "inbox@guardian.borg", + _type: "Foundation" + }), + guardians: guardians, + fixedAnnualCompensation: 625e3 ether, + metavestVestingAndUnlockStartTime: 1725148800, // 2024/09/01 00:00 UTC (means by the deployment @ 2025/09/01 it would've been fully unlocked) + milestones: new BaseAllocation.Milestone[](0) + }); + } + + function _parseAllocation(Config memory config, address token, uint48 startTime) internal view returns(BaseAllocation.Allocation memory) { + return BaseAllocation.Allocation({ + tokenContract: token, + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: config.fixedAnnualCompensation, + vestingCliffCredit: 0e3 ether, + unlockingCliffCredit: 0e3 ether, + vestingRate: uint160(config.fixedAnnualCompensation / 365 days), + vestingStartTime: startTime, // start along with capped minter + unlockRate: uint160(config.fixedAnnualCompensation / 365 days), + unlockStartTime: startTime // start along with capped minter + }); + } + + function _compFormatGlobalValues( + Config memory config, + Vm vm, + address grantor, + address grantee, + address token, + uint48 startTime + ) internal view returns(string[] memory) { + BaseAllocation.Allocation memory allocation = _parseAllocation(config, token, startTime); + + string[] memory globalValues = new string[](11); + globalValues[0] = "0"; // metavestType: Vesting + globalValues[1] = vm.toString(grantor); // grantor + globalValues[2] = vm.toString(grantee); // grantee + globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract + globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) + globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) + globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) + globalValues[7] = vm.toString(config.fixedAnnualCompensation / 1 ether); // vestingRate (annually) (human-readable) + globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime + globalValues[9] = vm.toString(config.fixedAnnualCompensation / 1 ether); // unlockRate (annually) (human-readable) + globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime + return globalValues; + } + + function _compFormatPartyValues( + Vm vm, + PartyInfo memory partyInfo + ) internal view returns(string[] memory) { + string[] memory partyValues = new string[](4); + partyValues[0] = partyInfo.name; + partyValues[1] = vm.toString(partyInfo.evmAddress); + partyValues[2] = partyInfo.contactDetails; + partyValues[3] = partyInfo._type; + return partyValues; + } + + function _compFormatPartyValues( + Vm vm, + PartyInfo memory guardianSafeInfo, + PartyInfo memory guardianInfo + ) internal view returns(string[][] memory) { + string[][] memory partyValues = new string[][](2); + partyValues[0] = _compFormatPartyValues(vm, guardianSafeInfo); + partyValues[1] = _compFormatPartyValues(vm, guardianInfo); + return partyValues; + } + + function _serviceFormatGlobalValues(Vm vm, uint256 expiry) internal view returns(string[] memory) { + string[] memory globalValues = new string[](1); + globalValues[0] = vm.toString(expiry); + return globalValues; + } + + function _serviceFormatPartyValues( + Vm vm, + address metalex, + address guardianBorg + ) internal view returns(string[][] memory) { + string[][] memory partyValues = new string[][](2); + partyValues[0] = new string[](4); + partyValues[0][0] = "MetaLeX"; + partyValues[0][1] = vm.toString(metalex); + partyValues[0][2] = "test@metalex.tech"; + partyValues[0][3] = "Corporation"; + partyValues[1] = new string[](4); + partyValues[1][0] = "Guardian BORG"; + partyValues[1][1] = vm.toString(guardianBorg); + partyValues[1][2] = "inbox@guardian.borg"; + partyValues[1][3] = "Foundation"; + return partyValues; + } +} diff --git a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol new file mode 100644 index 0000000..7911b5d --- /dev/null +++ b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.28; + +import {ZkSyncGuardianCompensation2024_2025} from "./ZkSyncGuardianCompensation2024_2025.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {CommonBase} from "forge-std/Base.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {IZkTokenV1} from "../../src/interfaces/zk-governance/IZkTokenV1.sol"; +import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {IGnosisSafe} from "../../test/lib/safe.sol"; +import {BaseAllocation} from "../../src/BaseAllocation.sol"; +import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; +import {metavestController} from "../../src/MetaVesTController.sol"; + +library ZkSyncGuardianCompensation2025_2026 { + + function getDefault() internal returns(ZkSyncGuardianCompensation2024_2025.Config memory) { + string[] memory serviceGlobalFields = new string[](1); + serviceGlobalFields[0] = "expiryDate"; + + string[] memory servicePartyFields = new string[](4); + servicePartyFields[0] = "name"; + servicePartyFields[1] = "evmAddress"; + servicePartyFields[2] = "contactDetails"; + servicePartyFields[3] = "type"; + + string[] memory compGlobalFields = new string[](11); + compGlobalFields[0] = "metavestType"; + compGlobalFields[1] = "grantor"; + compGlobalFields[2] = "grantee"; + compGlobalFields[3] = "tokenContract"; + compGlobalFields[4] = "tokenStreamTotal"; + compGlobalFields[5] = "vestingCliffCredit"; + compGlobalFields[6] = "unlockingCliffCredit"; + compGlobalFields[7] = "vestingRate"; + compGlobalFields[8] = "vestingStartTime"; + compGlobalFields[9] = "unlockRate"; + compGlobalFields[10] = "unlockStartTime"; + + string[] memory compPartyFields = new string[](4); + compPartyFields[0] = "name"; + compPartyFields[1] = "evmAddress"; + compPartyFields[2] = "contactDetails"; + compPartyFields[3] = "type"; + + IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); + + address[] memory guardians = new address[](0); // TODO TBD + + return ZkSyncGuardianCompensation2024_2025.Config({ + zkToken: IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E), + // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 + zkCappedMinter: IZkCappedMinterV2(0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A), + + guardianSafe: guardianSafe, + + metalexSafe: IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc), + registry: CyberAgreementRegistry(address(0)), // TODO TBD + vestingAllocationFactory: VestingAllocationFactory(address(0)), // TODO TBD + controller: metavestController(address(0)), // TODO TBD + + serviceAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD + serviceTemplateName: "MetaLeX <> zkSync Guardian BORG Service Agreement", // TODO TBD + serviceTemplateId: bytes32(uint256(200)), + serviceGlobalFields: serviceGlobalFields, + servicePartyFields: servicePartyFields, + + serviceAgreementExpiry: 1788220800, + + compAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD + compTemplateName: "zkSync Guardian Compensation Agreement", // TODO TBD + compTemplateId: bytes32(uint256(201)), + compGlobalFields: compGlobalFields, + compPartyFields: compPartyFields, + + guardianSafeInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD + name: "Guardian BORG", + evmAddress: address(guardianSafe), + contactDetails: "inbox@guardian.borg", + _type: "Foundation" + }), + guardians: guardians, + fixedAnnualCompensation: 625e3 ether, + metavestVestingAndUnlockStartTime: 1756684800, // 2025/09/01 00:00 UTC + milestones: new BaseAllocation.Milestone[](0) + }); + } +} diff --git a/scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol deleted file mode 100644 index 1b61aa5..0000000 --- a/scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.28; - -import {ZkSyncGuardianCompensationConfigBase} from "./ZkSyncGuardianCompensationConfigBase.sol"; -import {CommonBase} from "forge-std/Base.sol"; -import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; -import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; -import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; -import {IGnosisSafe} from "../../test/lib/safe.sol"; -import {BaseAllocation} from "../../src/BaseAllocation.sol"; -import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; -import {metavestController} from "../../src/MetaVesTController.sol"; - -contract ZkSyncGuardianCompensationConfig2024_2025 is ZkSyncGuardianCompensationConfigBase { - // ZK Governance - // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 - IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(0xE555FC98E45637D1B45e60E4fc05cF0F22836156); - - // MetaLeX <> zkSync Guardian BORG Service Agreement template - - string serviceAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP - string serviceTemplateName = "MetaLeX <> zkSync Guardian BORG Service Agreement"; // TODO WIP - bytes32 serviceTemplateId = bytes32(uint256(200)); // TODO TBD - - string[] serviceGlobalFields; - string[] servicePartyFields; - - // zkSync Guardian Compensation Agreement template - - string compAgreementUri = "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse"; // TODO WIP - string compTemplateName = "zkSync Guardian Compensation Agreement"; // TODO WIP - bytes32 compTemplateId = bytes32(uint256(201)); // TODO TBD - - string[] compGlobalFields; - string[] compPartyFields; - - // MetaLeX <> zkSync Guardian BORG Service Agreement parameters - uint256 serviceAgreementExpiry = 1788220800; - - // Vesting parameters - - // TODO TBD - PartyInfo guardianSafeInfo = PartyInfo({ - name: "Guardian BORG", - evmAddress: address(guardianSafe), - contactDetails: "inbox@guardian.borg", - _type: "Foundation" - }); - - address[] guardians; - uint256 fixedAnnualCompensation = 625e3 ether; - uint48 metavestVestingAndUnlockStartTime = 1725148800; // 2024/09/01 00:00 UTC (means by the deployment @ 2025/09/01 it would've been fully unlocked) - BaseAllocation.Milestone[] milestones = new BaseAllocation.Milestone[](0); - - // Deployments - - metavestController controller = metavestController(address(0)); // TODO TBD - - // Support data structures - - struct PartyInfo { - string name; - address evmAddress; - string contactDetails; - string _type; - } - - constructor() { - // zkSync Guardian Compensation Agreement template - - compGlobalFields = new string[](11); - compGlobalFields[0] = "metavestType"; - compGlobalFields[1] = "grantor"; - compGlobalFields[2] = "grantee"; - compGlobalFields[3] = "tokenContract"; - compGlobalFields[4] = "tokenStreamTotal"; - compGlobalFields[5] = "vestingCliffCredit"; - compGlobalFields[6] = "unlockingCliffCredit"; - compGlobalFields[7] = "vestingRate"; - compGlobalFields[8] = "vestingStartTime"; - compGlobalFields[9] = "unlockRate"; - compGlobalFields[10] = "unlockStartTime"; - - compPartyFields = new string[](4); - compPartyFields[0] = "name"; - compPartyFields[1] = "evmAddress"; - compPartyFields[2] = "contactDetails"; - compPartyFields[3] = "type"; - - // MetaLeX <> zkSync Guardian BORG Service Agreement template - - serviceGlobalFields = new string[](1); - serviceGlobalFields[0] = "expiryDate"; - - servicePartyFields = new string[](4); - servicePartyFields[0] = "name"; - servicePartyFields[1] = "evmAddress"; - servicePartyFields[2] = "contactDetails"; - servicePartyFields[3] = "type"; - - // Vesting parameters - - guardians = new address[](6); - guardians[0] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - guardians[1] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - guardians[2] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - guardians[3] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - guardians[4] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - guardians[5] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - } - - function _parseAllocation(address token, uint48 startTime) internal returns(BaseAllocation.Allocation memory) { - return BaseAllocation.Allocation({ - tokenContract: token, - // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year - tokenStreamTotal: fixedAnnualCompensation, - vestingCliffCredit: 0e3 ether, - unlockingCliffCredit: 0e3 ether, - vestingRate: uint160(fixedAnnualCompensation / 365 days), - vestingStartTime: startTime, // start along with capped minter - unlockRate: uint160(fixedAnnualCompensation / 365 days), - unlockStartTime: startTime // start along with capped minter - }); - } - - function _compFormatGlobalValues( - address grantor, - address grantee, - address token, - uint48 startTime - ) internal returns(string[] memory) { - BaseAllocation.Allocation memory allocation = _parseAllocation(token, startTime); - - string[] memory globalValues = new string[](11); - globalValues[0] = "0"; // metavestType: Vesting - globalValues[1] = vm.toString(grantor); // grantor - globalValues[2] = vm.toString(grantee); // grantee - globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract - globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) - globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) - globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) - globalValues[7] = vm.toString(fixedAnnualCompensation / 1 ether); // vestingRate (annually) (human-readable) - globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime - globalValues[9] = vm.toString(fixedAnnualCompensation / 1 ether); // unlockRate (annually) (human-readable) - globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime - return globalValues; - } - - function _compFormatPartyValues( - PartyInfo memory partyInfo - ) internal returns(string[] memory) { - string[] memory partyValues = new string[](4); - partyValues[0] = partyInfo.name; - partyValues[1] = vm.toString(partyInfo.evmAddress); - partyValues[2] = partyInfo.contactDetails; - partyValues[3] = partyInfo._type; - return partyValues; - } - - function _compFormatPartyValues( - PartyInfo memory guardianSafeInfo, - PartyInfo memory guardianInfo - ) internal returns(string[][] memory) { - string[][] memory partyValues = new string[][](2); - partyValues[0] = _compFormatPartyValues(guardianSafeInfo); - partyValues[1] = _compFormatPartyValues(guardianInfo); - return partyValues; - } - - function _serviceFormatGlobalValues(uint256 expiry) internal returns(string[] memory) { - string[] memory globalValues = new string[](1); - globalValues[0] = vm.toString(expiry); - return globalValues; - } - - function _serviceFormatPartyValues( - address metalex, - address guardianBorg - ) internal returns(string[][] memory) { - string[][] memory partyValues = new string[][](2); - partyValues[0] = new string[](4); - partyValues[0][0] = "MetaLeX"; - partyValues[0][1] = vm.toString(metalex); - partyValues[0][2] = "test@metalex.tech"; - partyValues[0][3] = "Corporation"; - partyValues[1] = new string[](4); - partyValues[1][0] = "Guardian BORG"; - partyValues[1][1] = vm.toString(guardianBorg); - partyValues[1][2] = "inbox@guardian.borg"; - partyValues[1][3] = "Foundation"; - return partyValues; - } -} diff --git a/scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol deleted file mode 100644 index ba1a44f..0000000 --- a/scripts/lib/ZkSyncGuardianCompensationConfig2025_2026.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.28; - -import {ZkSyncGuardianCompensationConfig2024_2025} from "./ZkSyncGuardianCompensationConfig2024_2025.sol"; -import {CommonBase} from "forge-std/Base.sol"; -import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; -import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; -import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; -import {IGnosisSafe} from "../../test/lib/safe.sol"; -import {BaseAllocation} from "../../src/BaseAllocation.sol"; - -contract ZkSyncGuardianCompensationConfig2025_2026 is ZkSyncGuardianCompensationConfig2024_2025 { - // ZK Governance - // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 - IZkCappedMinterV2 zkCappedMinter = IZkCappedMinterV2(0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A); - - constructor() { - fixedAnnualCompensation = 625e3 ether; - metavestVestingAndUnlockStartTime = 1756684800; // 2025/09/01 00:00 UTC - - guardians = new string[](6); - guardians[0] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - guardians[1] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - guardians[2] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - guardians[3] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - guardians[4] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - guardians[5] = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; // TODO TBD - } -} diff --git a/scripts/lib/ZkSyncGuardianCompensationConfigBase.sol b/scripts/lib/ZkSyncGuardianCompensationConfigBase.sol deleted file mode 100644 index 46b02dd..0000000 --- a/scripts/lib/ZkSyncGuardianCompensationConfigBase.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.28; - -import {CommonBase} from "forge-std/Base.sol"; -import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; -import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; -import {IZkTokenV1} from "../../src/interfaces/zk-governance/IZkTokenV1.sol"; -import {IZkCappedMinterV2Factory} from "../../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; -import {IGnosisSafe} from "../../test/lib/safe.sol"; -import {BaseAllocation} from "../../src/BaseAllocation.sol"; -import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; - -contract ZkSyncGuardianCompensationConfigBase is CommonBase { - // Assume zkSync Era mainnet @ 64202885 - - // MetaLeX SAFE - - IGnosisSafe metalexSafe = IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc); - - // zkSync Guardian SAFE - - IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); - - // ZK Governance - - IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); - - // Deployments - - CyberAgreementRegistry registry = CyberAgreementRegistry(address(0)); // TODO TBD - VestingAllocationFactory vestingAllocationFactory = VestingAllocationFactory(address(0)); // TODO TBD -} diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index 9248a31..162afcf 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -17,47 +17,43 @@ import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; import {console} from "forge-std/console.sol"; import {metavestController} from "../src/MetaVesTController.sol"; -contract ProposeMetaVestDealScript is ZkSyncGuardianCompensationConfig2024_2025, SafeTxHelper, Script { +contract ProposeMetaVestDealScript is SafeTxHelper, Script { + using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { run( vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), - registry, - controller, - guardianSafeInfo, - PartyInfo({ + ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Alice", evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b, contactDetails: "email@company.com", _type: "individual" - }) + }), + ZkSyncGuardianCompensation2024_2025.getDefault() ); } /// @dev For running in tests function run( uint256 proposerPrivateKey, - CyberAgreementRegistry registry, - metavestController controller, - PartyInfo memory guardianSafeInfo, - PartyInfo memory guardianInfo + ZkSyncGuardianCompensation2024_2025.PartyInfo memory guardianInfo, + ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { return run( - proposerPrivateKey, registry, controller, guardianSafeInfo, guardianInfo, + proposerPrivateKey, guardianInfo, // Default guardian allocations - _parseAllocation(address(zkToken), metavestVestingAndUnlockStartTime) + config._parseAllocation(address(config.zkToken), config.metavestVestingAndUnlockStartTime), + config ); } /// @dev For running in tests function run( uint256 proposerPrivateKey, - CyberAgreementRegistry registry, - metavestController controller, - PartyInfo memory guardianSafeInfo, - PartyInfo memory guardianInfo, - BaseAllocation.Allocation memory allocation + ZkSyncGuardianCompensation2024_2025.PartyInfo memory guardianInfo, + BaseAllocation.Allocation memory allocation, + ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { address proposer = vm.addr(proposerPrivateKey); @@ -66,25 +62,30 @@ contract ProposeMetaVestDealScript is ZkSyncGuardianCompensationConfig2024_2025, // Propose a new deal - uint48 startTime = metavestVestingAndUnlockStartTime; + uint48 startTime = config.metavestVestingAndUnlockStartTime; address[] memory parties = new address[](2); - parties[0] = address(guardianSafe); + parties[0] = address(config.guardianSafe); parties[1] = guardianInfo.evmAddress; - string[] memory globalValues = _compFormatGlobalValues( - address(guardianSafe), + string[] memory globalValues = config._compFormatGlobalValues( + vm, + address(config.guardianSafe), guardianInfo.evmAddress, - address(zkToken), + address(config.zkToken), startTime ); - string[][] memory partyValues = _compFormatPartyValues(guardianSafeInfo, guardianInfo); + string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025._compFormatPartyValues( + vm, + config.guardianSafeInfo, + guardianInfo + ); uint256 agreementSalt = block.timestamp; bytes32 expectedContractId = keccak256( abi.encode( - compTemplateId, + config.compTemplateId, agreementSalt, // salt, globalValues, parties @@ -93,12 +94,12 @@ contract ProposeMetaVestDealScript is ZkSyncGuardianCompensationConfig2024_2025, bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, - registry.DOMAIN_SEPARATOR(), - registry.SIGNATUREDATA_TYPEHASH(), + config.registry.DOMAIN_SEPARATOR(), + config.registry.SIGNATUREDATA_TYPEHASH(), expectedContractId, - compAgreementUri, - compGlobalFields, - compPartyFields, + config.compAgreementUri, + config.compGlobalFields, + config.compPartyFields, globalValues, partyValues[0], proposerPrivateKey @@ -106,13 +107,13 @@ contract ProposeMetaVestDealScript is ZkSyncGuardianCompensationConfig2024_2025, vm.startBroadcast(proposerPrivateKey); - bytes32 contractId = controller.proposeAndSignDeal( - compTemplateId, + bytes32 contractId = config.controller.proposeAndSignDeal( + config.compTemplateId, agreementSalt, metavestController.metavestType.Vesting, guardianInfo.evmAddress, allocation, - milestones, + config.milestones, globalValues, parties, partyValues, @@ -124,12 +125,12 @@ contract ProposeMetaVestDealScript is ZkSyncGuardianCompensationConfig2024_2025, vm.stopBroadcast(); console.log("Proposer: ", proposer); - console.log("Guardian Safe: ", address(guardianSafe)); - console.log("ZK token: ", address(zkToken)); - console.log("CyberAgreementRegistry: ", address(registry)); - console.log("VestingAllocationFactory: ", address(vestingAllocationFactory)); - console.log("MetavesTController: ", address(controller)); - console.log("ZkCappedMinterV2: ", address(zkCappedMinter)); + console.log("Guardian Safe: ", address(config.guardianSafe)); + console.log("ZK token: ", address(config.zkToken)); + console.log("CyberAgreementRegistry: ", address(config.registry)); + console.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); + console.log("MetavesTController: ", address(config.controller)); + console.log("ZkCappedMinterV2: ", address(config.zkCappedMinter)); console.log("Created:"); console.log(" Agreement ID:"); console.logBytes32(contractId); diff --git a/scripts/proposeServiceAgreement.s.sol b/scripts/proposeServiceAgreement.s.sol index 74b0af1..993be78 100644 --- a/scripts/proposeServiceAgreement.s.sol +++ b/scripts/proposeServiceAgreement.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -17,40 +17,40 @@ import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; -contract ProposeServiceAgreementScript is ZkSyncGuardianCompensationConfig2024_2025, SafeTxHelper, Script { +contract ProposeServiceAgreementScript is SafeTxHelper, Script { + using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { run( vm.envUint("METALEX_SAFE_DELEGATE_PRIVATE_KEY"), - registry + ZkSyncGuardianCompensation2024_2025.getDefault() ); } /// @dev For running in tests function run( uint256 proposerPrivateKey, - CyberAgreementRegistry _registry + ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { address metalexProposer = vm.addr(proposerPrivateKey); - registry = _registry; // Assume Guardian SAFE already delegate signing to the deployer // Propose a new deal address[] memory parties = new address[](2); - parties[0] = address(metalexSafe); - parties[1] = address(guardianSafe); + parties[0] = address(config.metalexSafe); + parties[1] = address(config.guardianSafe); - string[] memory globalValues = _serviceFormatGlobalValues(serviceAgreementExpiry); - string[][] memory partyValues = _serviceFormatPartyValues(address(metalexProposer), address(guardianSafe)); + string[] memory globalValues = ZkSyncGuardianCompensation2024_2025._serviceFormatGlobalValues(vm, config.serviceAgreementExpiry); + string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025._serviceFormatPartyValues(vm, address(metalexProposer), address(config.guardianSafe)); uint256 agreementSalt = block.timestamp; bytes32 expectedContractId = keccak256( abi.encode( - serviceTemplateId, + config.serviceTemplateId, agreementSalt, // salt, globalValues, parties @@ -59,12 +59,12 @@ contract ProposeServiceAgreementScript is ZkSyncGuardianCompensationConfig2024_2 bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, - registry.DOMAIN_SEPARATOR(), - registry.SIGNATUREDATA_TYPEHASH(), + config.registry.DOMAIN_SEPARATOR(), + config.registry.SIGNATUREDATA_TYPEHASH(), expectedContractId, - serviceAgreementUri, - serviceGlobalFields, - servicePartyFields, + config.serviceAgreementUri, + config.serviceGlobalFields, + config.servicePartyFields, globalValues, partyValues[0], proposerPrivateKey @@ -72,18 +72,18 @@ contract ProposeServiceAgreementScript is ZkSyncGuardianCompensationConfig2024_2 vm.startBroadcast(proposerPrivateKey); - bytes32 contractId = registry.createContract( - serviceTemplateId, + bytes32 contractId = config.registry.createContract( + config.serviceTemplateId, agreementSalt, globalValues, parties, partyValues, bytes32(0), // no secrets address(0), // no finalizer - serviceAgreementExpiry + config.serviceAgreementExpiry ); - registry.signContract( + config.registry.signContract( contractId, partyValues[0], signature, @@ -94,8 +94,8 @@ contract ProposeServiceAgreementScript is ZkSyncGuardianCompensationConfig2024_2 vm.stopBroadcast(); console2.log("MetaLeX proposer: ", address(metalexProposer)); - console2.log("Guardian Safe: ", address(guardianSafe)); - console2.log("CyberAgreementRegistry: ", address(registry)); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); console2.log("Created:"); console2.log(" Agreement ID:"); console2.logBytes32(contractId); diff --git a/scripts/signDealAndCreateMetavest.s.sol b/scripts/signDealAndCreateMetavest.s.sol index b1c7de0..41a39f4 100644 --- a/scripts/signDealAndCreateMetavest.s.sol +++ b/scripts/signDealAndCreateMetavest.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompensationConfig2024_2025} from "./lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -17,50 +17,50 @@ import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; import {console} from "forge-std/console.sol"; import {metavestController} from "../src/MetaVesTController.sol"; -contract SignDealAndCreateMetavestScript is ZkSyncGuardianCompensationConfig2024_2025, SafeTxHelper, Script { +contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { + using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { uint256 granteePrivateKey = vm.envUint("GRANTEE_PRIVATE_KEY"); run( granteePrivateKey, - registry, - controller, 0x0000000000000000000000000000000000000000000000000000000000000000, // TODO TBD - PartyInfo({ // TODO TBD + ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD name: "Alice", evmAddress: vm.addr(granteePrivateKey), contactDetails: "email@company.com", _type: "individual" - }) + }), + ZkSyncGuardianCompensation2024_2025.getDefault() ); } /// @dev For running in tests function run( - uint256 granteePrivateKey, - CyberAgreementRegistry registry, - metavestController controller, + uint256 granteePrivateKey, bytes32 agreementId, - PartyInfo memory granteeInfo + ZkSyncGuardianCompensation2024_2025.PartyInfo memory granteeInfo, + ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(address) { // Sign the deal and create MetaVesT - string[] memory granteePartyValues = _compFormatPartyValues(granteeInfo); + string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025._compFormatPartyValues(vm, granteeInfo); bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, - registry.DOMAIN_SEPARATOR(), - registry.SIGNATUREDATA_TYPEHASH(), + config.registry.DOMAIN_SEPARATOR(), + config.registry.SIGNATUREDATA_TYPEHASH(), agreementId, - compAgreementUri, - compGlobalFields, - compPartyFields, - _compFormatGlobalValues( - address(guardianSafe), + config.compAgreementUri, + config.compGlobalFields, + config.compPartyFields, + config._compFormatGlobalValues( + vm, + address(config.guardianSafe), granteeInfo.evmAddress, - address(zkToken), - metavestVestingAndUnlockStartTime + address(config.zkToken), + config.metavestVestingAndUnlockStartTime ), granteePartyValues, granteePrivateKey @@ -68,7 +68,7 @@ contract SignDealAndCreateMetavestScript is ZkSyncGuardianCompensationConfig2024 vm.startBroadcast(granteePrivateKey); - address metavest = controller.signDealAndCreateMetavest( + address metavest = config.controller.signDealAndCreateMetavest( granteeInfo.evmAddress, granteeInfo.evmAddress, agreementId, @@ -81,9 +81,9 @@ contract SignDealAndCreateMetavestScript is ZkSyncGuardianCompensationConfig2024 console.log("Grantee: ", granteeInfo.evmAddress); console.log("Grantee Name: ", granteeInfo.name); - console.log("Guardian Safe: ", address(guardianSafe)); - console.log("CyberAgreementRegistry: ", address(registry)); - console.log("MetavesTController: ", address(controller)); + console.log("Guardian Safe: ", address(config.guardianSafe)); + console.log("CyberAgreementRegistry: ", address(config.registry)); + console.log("MetavesTController: ", address(config.controller)); console.log("Agreement ID:"); console.logBytes32(agreementId); console.log("Created:"); diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 9fa51b8..ecde136 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -10,17 +10,18 @@ import {Test} from "forge-std/Test.sol"; import {console2} from "forge-std/console2.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; import {DeployZkSyncGuardianCompensationPrerequisitesScript} from "../scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol"; -import {DeployZkSyncGuardianCompensation2024_2025Script} from "../scripts/deployZkSyncGuardianCompensation2024_2025.s.sol"; +import {DeployZkSyncGuardianCompensationScript} from "../scripts/deployZkSyncGuardianCompensation.s.sol"; import {ProposeServiceAgreementScript} from "../scripts/proposeServiceAgreement.s.sol"; import {ProposeMetaVestDealScript} from "../scripts/proposeMetavestDeal.s.sol"; import {SignDealAndCreateMetavestScript} from "../scripts/signDealAndCreateMetavest.s.sol"; -import {ZkSyncGuardianCompensationConfig2024_2025} from "../scripts/lib/ZkSyncGuardianCompensationConfig2024_2025.sol"; +import {ZkSyncGuardianCompensation2024_2025} from "../scripts/lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "../scripts/lib/ZkSyncGuardianCompensation2025_2026.sol"; import {GnosisTransaction} from "./lib/safe.sol"; // Test by forge test --zksync --via-ir contract ZkSyncGuardianCompensationTest is DeployZkSyncGuardianCompensationPrerequisitesScript, - DeployZkSyncGuardianCompensation2024_2025Script, + DeployZkSyncGuardianCompensationScript, ProposeServiceAgreementScript, ProposeMetaVestDealScript, SignDealAndCreateMetavestScript, @@ -29,6 +30,8 @@ contract ZkSyncGuardianCompensationTest is // zkSync Era mainnet @ 63631890 address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; + string saltStr = "ZkSyncGuardianCompensationTest"; + // Randomly generated to avoid contaminated common test address uint256 privateKeySalt = 0x4425fdf88097e51c669a66d392c4019ad555544e966908af6ee3cec32f53ab77; @@ -47,7 +50,12 @@ contract ZkSyncGuardianCompensationTest is IZkCappedMinterV2 masterMinter; + ZkSyncGuardianCompensation2024_2025.Config config2024_2025; + ZkSyncGuardianCompensation2024_2025.Config config2025_2026; + BorgAuth auth; + metavestController controller2024_2025; + metavestController controller2025_2026; function setUp() public { // Prepare funds for accounts used by the actual deployment scripts @@ -58,23 +66,60 @@ contract ZkSyncGuardianCompensationTest is deal(bob, 1 ether); deal(chad, 1 ether); - // Run deploy scripts - GnosisTransaction[] memory safeTxs; - (auth, registry, vestingAllocationFactory) = DeployZkSyncGuardianCompensationPrerequisitesScript.run(deployerPrivateKey); - (controller, safeTxs) = DeployZkSyncGuardianCompensation2024_2025Script.run(deployerPrivateKey, registry, vestingAllocationFactory); + CyberAgreementRegistry registry; + VestingAllocationFactory vestingAllocationFactory; + metavestController controller; + GnosisTransaction[] memory safeTxs2024_2025; + GnosisTransaction[] memory safeTxs2025_2026; + + config2024_2025 = ZkSyncGuardianCompensation2024_2025.getDefault(); + config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(); + + (auth, registry, vestingAllocationFactory) = DeployZkSyncGuardianCompensationPrerequisitesScript.deployPrerequisites( + saltStr, + deployerPrivateKey, + config2024_2025 + ); + + // Update configs with deployed contracts + config2024_2025.registry = registry; + config2024_2025.vestingAllocationFactory = vestingAllocationFactory; + config2025_2026.registry = registry; + config2025_2026.vestingAllocationFactory = vestingAllocationFactory; + + // Deploy 2024-2025 compensation contracts + (controller, safeTxs2024_2025) = DeployZkSyncGuardianCompensationScript.deployCompensation( + string(abi.encodePacked(saltStr, ".2024-2025")), + deployerPrivateKey, + config2024_2025 + ); + config2024_2025.controller = controller; // Update configs with deployed contracts + + // Deploy 2025-2026 compensation contracts + (controller, safeTxs2025_2026) = DeployZkSyncGuardianCompensationScript.deployCompensation( + string(abi.encodePacked(saltStr, ".2025-2026")), + deployerPrivateKey, + config2025_2026 + ); + config2025_2026.controller = controller; // Update configs with deployed contracts // Simulate Guardian SAFE to execute txs as instructed - for (uint256 i = 0; i < safeTxs.length; i++) { - vm.prank(address(guardianSafe)); - (safeTxs[i].to).call{value: safeTxs[i].value}(safeTxs[i].data); + for (uint256 i = 0; i < safeTxs2024_2025.length; i++) { + vm.prank(address(config2024_2025.guardianSafe)); + (safeTxs2024_2025[i].to).call{value: safeTxs2024_2025[i].value}(safeTxs2024_2025[i].data); + } + for (uint256 i = 0; i < safeTxs2025_2026.length; i++) { + vm.prank(address(config2025_2026.guardianSafe)); + (safeTxs2025_2026[i].to).call{value: safeTxs2025_2026[i].value}(safeTxs2025_2026[i].data); } // Simulate vote pass (https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746) vm.startPrank(zkTokenAdmin); - masterMinter = IZkCappedMinterV2(zkCappedMinter.MINTABLE()); - masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(zkCappedMinter)); + masterMinter = IZkCappedMinterV2(config2024_2025.zkCappedMinter.MINTABLE()); + masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(config2024_2025.zkCappedMinter)); + masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(config2025_2026.zkCappedMinter)); IZkCappedMinterV2 grandMasterMinter = IZkCappedMinterV2(masterMinter.MINTABLE()); grandMasterMinter.grantRole(grandMasterMinter.MINTER_ROLE(), address(masterMinter)); @@ -84,7 +129,7 @@ contract ZkSyncGuardianCompensationTest is function run() public override( DeployZkSyncGuardianCompensationPrerequisitesScript, - DeployZkSyncGuardianCompensation2024_2025Script, + DeployZkSyncGuardianCompensationScript, ProposeServiceAgreementScript, ProposeMetaVestDealScript, SignDealAndCreateMetavestScript @@ -95,89 +140,104 @@ contract ZkSyncGuardianCompensationTest is function test_metadata() public { // ZK governance pre-requisites - assertTrue(masterMinter.hasRole(masterMinter.MINTER_ROLE(), address(zkCappedMinter)), "Master Minter should grant this year's ZK Capped Minter access"); + assertTrue(masterMinter.hasRole(masterMinter.MINTER_ROLE(), address(config2024_2025.zkCappedMinter)), "Master Minter should grant this year's ZK Capped Minter access"); // MetaVesT pre-requisites - auth.onlyRole(auth.OWNER_ROLE(), address(metalexSafe)); // MetaLeX SAFE should own BorgAuth + auth.onlyRole(auth.OWNER_ROLE(), address(config2024_2025.metalexSafe)); // MetaLeX SAFE should own BorgAuth vm.assertEq(auth.userRoles(deployer), 0, "deployer should revoke BorgAuth ownership"); - vm.assertEq(address(registry.AUTH()), address(auth), "Unexpected CyberAgreementRegistry auth"); + vm.assertEq(address(config2024_2025.registry.AUTH()), address(auth), "Unexpected CyberAgreementRegistry auth"); _assertTemplate( - registry, - compTemplateId, - compAgreementUri, - compTemplateName, - compGlobalFields, - compPartyFields + config2024_2025.registry, + config2024_2025.compTemplateId, + config2024_2025.compAgreementUri, + config2024_2025.compTemplateName, + config2024_2025.compGlobalFields, + config2024_2025.compPartyFields ); _assertTemplate( - registry, - serviceTemplateId, - serviceAgreementUri, - serviceTemplateName, - serviceGlobalFields, - servicePartyFields + config2024_2025.registry, + config2024_2025.serviceTemplateId, + config2024_2025.serviceAgreementUri, + config2024_2025.serviceTemplateName, + config2024_2025.serviceGlobalFields, + config2024_2025.servicePartyFields ); // MetaVesT deployments - vm.assertEq(controller.authority(), address(guardianSafe), "MetaVesTController's authority should be Guardian SAFE"); - vm.assertEq(controller.dao(), address(guardianSafe), "MetaVesTController's DAO should be Guardian SAFE"); - vm.assertEq(controller.registry(), address(registry), "Unexpected MetaVesTController registry"); - vm.assertEq(controller.vestingFactory(), address(vestingAllocationFactory), "Unexpected MetaVesTController vesting allocation factory"); - vm.assertEq(controller.zkCappedMinter(), address(zkCappedMinter), "MetaVesTController should have ZK Capped Minter should set"); - vm.assertTrue(zkCappedMinter.hasRole(zkCappedMinter.MINTER_ROLE(), address(controller)), "ZK Capped Minter should grant MetaVesTController MINTER role"); + vm.assertEq(config2024_2025.controller.authority(), address(config2024_2025.guardianSafe), "2024-2025 MetaVesTController's authority should be Guardian SAFE"); + vm.assertEq(config2024_2025.controller.dao(), address(config2024_2025.guardianSafe), "2024-2025 MetaVesTController's DAO should be Guardian SAFE"); + vm.assertEq(config2024_2025.controller.registry(), address(config2024_2025.registry), "2024-2025 Unexpected MetaVesTController registry"); + vm.assertEq(config2024_2025.controller.vestingFactory(), address(config2024_2025.vestingAllocationFactory), "2024-2025 Unexpected MetaVesTController vesting allocation factory"); + vm.assertEq(config2024_2025.controller.zkCappedMinter(), address(config2024_2025.zkCappedMinter), "2024-2025 MetaVesTController should have ZK Capped Minter should set"); + vm.assertTrue(config2024_2025.zkCappedMinter.hasRole(config2024_2025.zkCappedMinter.MINTER_ROLE(), address(config2024_2025.controller)), "2024-2025 ZK Capped Minter should grant MetaVesTController MINTER role"); + + vm.assertEq(config2025_2026.controller.authority(), address(config2025_2026.guardianSafe), "2025-2026 MetaVesTController's authority should be Guardian SAFE"); + vm.assertEq(config2025_2026.controller.dao(), address(config2025_2026.guardianSafe), "2025-2026 MetaVesTController's DAO should be Guardian SAFE"); + vm.assertEq(config2025_2026.controller.registry(), address(config2025_2026.registry), "2025-2026 Unexpected MetaVesTController registry"); + vm.assertEq(config2025_2026.controller.vestingFactory(), address(config2025_2026.vestingAllocationFactory), "2025-2026 Unexpected MetaVesTController vesting allocation factory"); + vm.assertEq(config2025_2026.controller.zkCappedMinter(), address(config2025_2026.zkCappedMinter), "2025-2026 MetaVesTController should have ZK Capped Minter should set"); + vm.assertTrue(config2025_2026.zkCappedMinter.hasRole(config2025_2026.zkCappedMinter.MINTER_ROLE(), address(config2025_2026.controller)), "2025-2026 ZK Capped Minter should grant MetaVesTController MINTER role"); } function test_ProposeServiceAgreement() public { // Simulate MetaLeX SAFE delegation - vm.prank(address(metalexSafe)); - registry.setDelegation(metalexDelegate, block.timestamp + 60); - assertTrue(registry.isValidDelegate(address(metalexSafe), metalexDelegate), "should be MetaLeX SAFE's delegate"); + vm.prank(address(config2024_2025.metalexSafe)); + config2024_2025.registry.setDelegation(metalexDelegate, block.timestamp + 60); + assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.metalexSafe), metalexDelegate), "should be MetaLeX SAFE's delegate"); // Simulate MetaLeX delegate proposing and signing agreement - bytes32 agreementId = ProposeServiceAgreementScript.run(metalexDelegatePrivateKey, registry); + bytes32 agreementId = ProposeServiceAgreementScript.run( + metalexDelegatePrivateKey, + config2024_2025 + ); // Verify agreement - (bytes32 templateId, , , , , , uint256 expiry) = registry.agreements(agreementId); - assertEq(templateId, serviceTemplateId, "Unexpected service agreement template ID"); - assertEq(expiry, serviceAgreementExpiry, "Unexpected service agreement expiry"); + (bytes32 templateId, , , , , , uint256 expiry) = config2024_2025.registry.agreements(agreementId); + assertEq(templateId, config2024_2025.serviceTemplateId, "Unexpected service agreement template ID"); + assertEq(expiry, config2024_2025.serviceAgreementExpiry, "Unexpected service agreement expiry"); - (, , , , , address[] memory parties, , , , ) = registry.getContractDetails(agreementId); - vm.assertEq(parties[0], address(metalexSafe), "First party should be MetaLeX SAFE"); - vm.assertEq(parties[1], address(guardianSafe), "Second party should be Guardian SAFE"); + (, , , , , address[] memory parties, , , , ) = config2024_2025.registry.getContractDetails(agreementId); + vm.assertEq(parties[0], address(config2024_2025.metalexSafe), "First party should be MetaLeX SAFE"); + vm.assertEq(parties[1], address(config2024_2025.guardianSafe), "Second party should be Guardian SAFE"); - vm.assertTrue(registry.hasSigned(agreementId, metalexDelegate), "Should signed by MetaLeX delegate"); + vm.assertTrue(config2024_2025.registry.hasSigned(agreementId, metalexDelegate), "Should signed by MetaLeX delegate"); } function test_GuardianCompensation() public { - (address metavestAddressAlice, address metavestAddressBob) = _proposeAndFinalizeAllGuardianDeals(); + (address metavestAddressAlice2024_2025, address metavestAddressAlice2025_2026) = _proposeAndFinalizeAllGuardianDeals(); - VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddressAlice); - VestingAllocation vestingAllocationBob = VestingAllocation(metavestAddressBob); + VestingAllocation vestingAllocationAlice2024_2025 = VestingAllocation(metavestAddressAlice2024_2025); + VestingAllocation vestingAllocationAlice2025_2026 = VestingAllocation(metavestAddressAlice2025_2026); - // Grantee should be able to withdraw all on 2025/09/01 because this compensation is for 2024~2025 - vm.warp(1756684800 + 1); // 2025/09/01 00:00 UTC with some margin for precision error + // Alice should be able to withdraw all on 2025/09/01 because this compensation is for 2024~2025 + vm.warp(1756684800); // 2025/09/01 00:00 UTC + _granteeWithdrawAndAsserts(config2024_2025.zkToken, config2024_2025.zkCappedMinter, vestingAllocationAlice2024_2025, 615e3 ether, "Alice 2024-2025 partial"); - _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationAlice, 625e3 ether, "Alice full"); - _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationBob, 615e3 ether, "Bob partial"); + // Alice should be able to withdraw within the 2024-2025 grace period (set by ZK Capped Minter expiry) + vm.warp(1767225599); // 2025/12/31 23:59:59 UTC + _granteeWithdrawAndAsserts(config2024_2025.zkToken, config2024_2025.zkCappedMinter, vestingAllocationAlice2024_2025, 10e3 ether, "Alice 2024-2025 remaining"); - // Grantees should be able to withdraw within the grace period (set by ZK Capped Minter expiry) - skip(60 days); + // Alice should be able to withdraw half of her 2025-2026 compensation half way through the period + vm.warp(1772496000); // 2026/03/03 00:00 UTC + _granteeWithdrawAndAsserts(config2025_2026.zkToken, config2025_2026.zkCappedMinter, vestingAllocationAlice2025_2026, 312.5e3 ether, "Alice 2025-2026 half"); - _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationBob, 10e3 ether, "Bob remaining"); + // Alice should be able to withdraw within the 2025-2026 grace period (set by ZK Capped Minter expiry) + vm.warp(1793491199); // 2026/10/31 23:59:59 UTC + _granteeWithdrawAndAsserts(config2025_2026.zkToken, config2025_2026.zkCappedMinter, vestingAllocationAlice2025_2026, 312.5e3 ether, "Alice 2025-2026 remaining"); } function test_AdminToolingCompensation() public { - (address metavestAddressAlice, address metavestAddressBob) = _proposeAndFinalizeAllGuardianDeals(); - VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddressAlice); + (address metavestAddressAlice2024_2025, ) = _proposeAndFinalizeAllGuardianDeals(); + VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddressAlice2024_2025); - // Vesting starts and a month has passed - vm.warp(1756684800 + 1); // 2025/09/01 00:00 UTC with some margin for precision error + // 2024-2025 Vesting starts + vm.warp(1756684800); // 2025/09/01 00:00 UTC - _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationAlice, 300e3 ether, "Alice partial"); + _granteeWithdrawAndAsserts(config2024_2025.zkToken, config2024_2025.zkCappedMinter, vestingAllocationAlice, 300e3 ether, "Alice partial"); // A month has passed skip(30 days); @@ -185,11 +245,11 @@ contract ZkSyncGuardianCompensationTest is // Add new grantee for admin/tooling compensation // Guardian SAFE to delegate signing to an EOA - vm.prank(address(guardianSafe)); - registry.setDelegation(guardianDelegate, block.timestamp + 60); - assertTrue(registry.isValidDelegate(address(guardianSafe), guardianDelegate), "should be Guardian SAFE's delegate"); + vm.prank(address(config2024_2025.guardianSafe)); + config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); + assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "should be Guardian SAFE's delegate"); - ZkSyncGuardianCompensationConfig2024_2025.PartyInfo memory chadInfo = ZkSyncGuardianCompensationConfig2024_2025.PartyInfo({ + ZkSyncGuardianCompensation2024_2025.PartyInfo memory chadInfo = ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Chad", evmAddress: chad, contactDetails: "chad@email.com", @@ -197,12 +257,9 @@ contract ZkSyncGuardianCompensationTest is }); bytes32 contractIdChad = ProposeMetaVestDealScript.run( guardianDelegatePrivateKey, - registry, - controller, - guardianSafeInfo, chadInfo, BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(config2024_2025.zkToken), // 10k ZK total in one cliff tokenStreamTotal: 10e3 ether, vestingCliffCredit: 10e3 ether, @@ -211,72 +268,60 @@ contract ZkSyncGuardianCompensationTest is vestingStartTime: 0, unlockRate: 0, unlockStartTime: 0 - }) + }), + config2024_2025 ); VestingAllocation vestingAllocationChad = VestingAllocation(SignDealAndCreateMetavestScript.run( chadPrivateKey, - registry, - controller, contractIdChad, - chadInfo + chadInfo, + config2024_2025 )); - _granteeWithdrawAndAsserts(zkToken, zkCappedMinter, vestingAllocationChad, 10e3 ether, "Chad cliff"); + _granteeWithdrawAndAsserts(config2024_2025.zkToken, config2024_2025.zkCappedMinter, vestingAllocationChad, 10e3 ether, "Chad cliff"); } function _proposeAndFinalizeAllGuardianDeals() internal returns(address, address) { // Guardian SAFE to delegate signing to an EOA - vm.prank(address(guardianSafe)); - registry.setDelegation(guardianDelegate, block.timestamp + 60); - assertTrue(registry.isValidDelegate(address(guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); + vm.prank(address(config2024_2025.guardianSafe)); + config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); + assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); // Run scripts to propose deals - ZkSyncGuardianCompensationConfig2024_2025.PartyInfo memory aliceInfo = ZkSyncGuardianCompensationConfig2024_2025.PartyInfo({ + ZkSyncGuardianCompensation2024_2025.PartyInfo memory aliceInfo = ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Alice", evmAddress: alice, contactDetails: "alice@email.com", _type: "individual" }); - ZkSyncGuardianCompensationConfig2024_2025.PartyInfo memory bobInfo = ZkSyncGuardianCompensationConfig2024_2025.PartyInfo({ - name: "Bob", - evmAddress: bob, - contactDetails: "bob@email.com", - _type: "individual" - }); - bytes32 contractIdAlice = ProposeMetaVestDealScript.run( + bytes32 contractIdAlice2024_2025 = ProposeMetaVestDealScript.run( guardianDelegatePrivateKey, - registry, - controller, - guardianSafeInfo, - aliceInfo + aliceInfo, + config2024_2025 ); - bytes32 contractIdBob = ProposeMetaVestDealScript.run( + bytes32 contractIdAlice2025_2026 = ProposeMetaVestDealScript.run( guardianDelegatePrivateKey, - registry, - controller, - guardianSafeInfo, - bobInfo + aliceInfo, + config2025_2026 ); // Simulate guardian counter-sign and finalize the deal - address metavestAlice = SignDealAndCreateMetavestScript.run( + address metavestAlice2024_2025 = SignDealAndCreateMetavestScript.run( alicePrivateKey, - registry, - controller, - contractIdAlice, - aliceInfo + contractIdAlice2024_2025, + aliceInfo, + config2024_2025 ); - address metavestBob = SignDealAndCreateMetavestScript.run( - bobPrivateKey, - registry, - controller, - contractIdBob, - bobInfo + address metavestAlice2025_2026 = SignDealAndCreateMetavestScript.run( + alicePrivateKey, + contractIdAlice2025_2026, + aliceInfo, + config2025_2026 ); - return (metavestAlice, metavestBob); + return (metavestAlice2024_2025, metavestAlice2025_2026); } function _assertTemplate( From 7230df13d6e770163cfadb5729623c1965cab5c9 Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 26 Aug 2025 12:09:59 -0700 Subject: [PATCH 43/68] chore: simplify config helpers and logging --- .../deployZkSyncGuardianCompensation.s.sol | 18 ++-- ...yncGuardianCompensationPrerequisites.s.sol | 12 ++- .../ZkSyncGuardianCompensation2024_2025.sol | 97 +++++++++---------- .../ZkSyncGuardianCompensation2025_2026.sol | 71 ++------------ scripts/proposeMetavestDeal.s.sol | 41 ++++---- scripts/proposeServiceAgreement.s.sol | 16 ++- scripts/signDealAndCreateMetavest.s.sol | 40 ++++---- 7 files changed, 124 insertions(+), 171 deletions(-) diff --git a/scripts/deployZkSyncGuardianCompensation.s.sol b/scripts/deployZkSyncGuardianCompensation.s.sol index a12d914..3bf115f 100644 --- a/scripts/deployZkSyncGuardianCompensation.s.sol +++ b/scripts/deployZkSyncGuardianCompensation.s.sol @@ -34,6 +34,16 @@ contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { ) { address deployer = vm.addr(deployerPrivateKey); + console2.log(""); + console2.log("=== DeployZkSyncGuardianCompensationScript ==="); + console2.log("Deployer: ", deployer); + console2.log("Salt string: ", saltStr); + console2.log("ZkCappedMinterV2: ", address(config.zkCappedMinter)); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); + console2.log(""); + bytes32 salt = keccak256(bytes(saltStr)); vm.startBroadcast(deployerPrivateKey); @@ -77,14 +87,6 @@ contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { // Output logs - console2.log("Deployer: ", deployer); - console2.log("salt: ", saltStr); - console2.log("ZkCappedMinterV2: ", address(config.zkCappedMinter)); - console2.log("Guardian Safe: ", address(config.guardianSafe)); - console2.log("CyberAgreementRegistry: ", address(config.registry)); - console2.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); - console2.log(""); - console2.log("Deployed addresses:"); console2.log(" MetavesTController: ", address(controller)); console2.log(""); diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol index 225e3a1..216721a 100644 --- a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol +++ b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol @@ -39,6 +39,13 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Sc ) { address deployer = vm.addr(deployerPrivateKey); + console2.log(""); + console2.log("=== DeployZkSyncGuardianCompensationPrerequisitesScript ==="); + console2.log("Deployer: ", deployer); + console2.log("Salt string: ", saltStr); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log(""); + bytes32 salt = keccak256(bytes(saltStr)); vm.startBroadcast(deployerPrivateKey); @@ -86,14 +93,11 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Sc // Output logs - console2.log("Deployer: ", deployer); - console2.log("Guardian Safe: ", address(config.guardianSafe)); - console2.log(""); - console2.log("Deployed addresses:"); console2.log(" BorgAuth: ", address(auth)); console2.log(" CyberAgreementRegistry: ", address(registry)); console2.log(" VestingAllocationFactory: ", address(vestingAllocationFactory)); + console2.log(""); return (auth, registry, vestingAllocationFactory); } diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol index 734adc4..1eca8a3 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -21,13 +21,15 @@ library ZkSyncGuardianCompensation2024_2025 { IZkTokenV1 zkToken; IZkCappedMinterV2 zkCappedMinter; - // zkSync Guardian SAFE + // zkSync Guardians IGnosisSafe guardianSafe; + PartyInfo guardianSafeInfo; // MetaLeX IGnosisSafe metalexSafe; + PartyInfo metalexSafeInfo; CyberAgreementRegistry registry; VestingAllocationFactory vestingAllocationFactory; metavestController controller; @@ -50,7 +52,6 @@ library ZkSyncGuardianCompensation2024_2025 { string[] compGlobalFields; string[] compPartyFields; - PartyInfo guardianSafeInfo; address[] guardians; uint256 fixedAnnualCompensation; uint48 metavestVestingAndUnlockStartTime; @@ -94,21 +95,43 @@ library ZkSyncGuardianCompensation2024_2025 { compPartyFields[3] = "type"; IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); + IGnosisSafe metalexSafe = IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc); address[] memory guardians = new address[](0); // TODO TBD return Config({ + + // ZK Governance + zkToken: IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E), // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 zkCappedMinter: IZkCappedMinterV2(0xE555FC98E45637D1B45e60E4fc05cF0F22836156), - + + // zkSync Guardians + guardianSafe: guardianSafe, + guardianSafeInfo: PartyInfo({ // TODO TBD + name: "Guardian BORG", + evmAddress: address(guardianSafe), + contactDetails: "inbox@guardian.borg", + _type: "Foundation" + }), + + // MetaLeX - metalexSafe: IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc), + metalexSafe: metalexSafe, + metalexSafeInfo: PartyInfo({ // TODO TBD + name: "MetaLeX", + evmAddress: address(metalexSafe), + contactDetails: "test@metalex.tech", + _type: "Corporation" + }), registry: CyberAgreementRegistry(address(0)), // TODO TBD vestingAllocationFactory: VestingAllocationFactory(address(0)), // TODO TBD controller: metavestController(address(0)), // TODO TBD + // MetaLeX <> zkSync Guardian BORG Service Agreement + serviceAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD serviceTemplateName: "MetaLeX <> zkSync Guardian BORG Service Agreement", // TODO TBD serviceTemplateId: bytes32(uint256(200)), @@ -117,18 +140,14 @@ library ZkSyncGuardianCompensation2024_2025 { serviceAgreementExpiry: 1788220800, + // zkSync Guardian Compensation Agreement + compAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD compTemplateName: "zkSync Guardian Compensation Agreement", // TODO TBD compTemplateId: bytes32(uint256(201)), compGlobalFields: compGlobalFields, compPartyFields: compPartyFields, - guardianSafeInfo: PartyInfo({ // TODO TBD - name: "Guardian BORG", - evmAddress: address(guardianSafe), - contactDetails: "inbox@guardian.borg", - _type: "Foundation" - }), guardians: guardians, fixedAnnualCompensation: 625e3 ether, metavestVestingAndUnlockStartTime: 1725148800, // 2024/09/01 00:00 UTC (means by the deployment @ 2025/09/01 it would've been fully unlocked) @@ -136,33 +155,30 @@ library ZkSyncGuardianCompensation2024_2025 { }); } - function _parseAllocation(Config memory config, address token, uint48 startTime) internal view returns(BaseAllocation.Allocation memory) { + function parseAllocation(Config memory config) internal view returns(BaseAllocation.Allocation memory) { return BaseAllocation.Allocation({ - tokenContract: token, + tokenContract: address(config.zkToken), // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year tokenStreamTotal: config.fixedAnnualCompensation, vestingCliffCredit: 0e3 ether, unlockingCliffCredit: 0e3 ether, vestingRate: uint160(config.fixedAnnualCompensation / 365 days), - vestingStartTime: startTime, // start along with capped minter + vestingStartTime: config.metavestVestingAndUnlockStartTime, unlockRate: uint160(config.fixedAnnualCompensation / 365 days), - unlockStartTime: startTime // start along with capped minter + unlockStartTime: config.metavestVestingAndUnlockStartTime }); } - function _compFormatGlobalValues( + function formatCompGlobalValues( Config memory config, Vm vm, - address grantor, - address grantee, - address token, - uint48 startTime + address grantee ) internal view returns(string[] memory) { - BaseAllocation.Allocation memory allocation = _parseAllocation(config, token, startTime); + BaseAllocation.Allocation memory allocation = parseAllocation(config); string[] memory globalValues = new string[](11); globalValues[0] = "0"; // metavestType: Vesting - globalValues[1] = vm.toString(grantor); // grantor + globalValues[1] = vm.toString(config.guardianSafeInfo.evmAddress); // grantor globalValues[2] = vm.toString(grantee); // grantee globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) @@ -175,7 +191,13 @@ library ZkSyncGuardianCompensation2024_2025 { return globalValues; } - function _compFormatPartyValues( + function formatServiceGlobalValues(Vm vm, uint256 expiry) internal view returns(string[] memory) { + string[] memory globalValues = new string[](1); + globalValues[0] = vm.toString(expiry); + return globalValues; + } + + function formatPartyValues( Vm vm, PartyInfo memory partyInfo ) internal view returns(string[] memory) { @@ -187,39 +209,14 @@ library ZkSyncGuardianCompensation2024_2025 { return partyValues; } - function _compFormatPartyValues( + function formatPartyValues( Vm vm, PartyInfo memory guardianSafeInfo, PartyInfo memory guardianInfo ) internal view returns(string[][] memory) { string[][] memory partyValues = new string[][](2); - partyValues[0] = _compFormatPartyValues(vm, guardianSafeInfo); - partyValues[1] = _compFormatPartyValues(vm, guardianInfo); - return partyValues; - } - - function _serviceFormatGlobalValues(Vm vm, uint256 expiry) internal view returns(string[] memory) { - string[] memory globalValues = new string[](1); - globalValues[0] = vm.toString(expiry); - return globalValues; - } - - function _serviceFormatPartyValues( - Vm vm, - address metalex, - address guardianBorg - ) internal view returns(string[][] memory) { - string[][] memory partyValues = new string[][](2); - partyValues[0] = new string[](4); - partyValues[0][0] = "MetaLeX"; - partyValues[0][1] = vm.toString(metalex); - partyValues[0][2] = "test@metalex.tech"; - partyValues[0][3] = "Corporation"; - partyValues[1] = new string[](4); - partyValues[1][0] = "Guardian BORG"; - partyValues[1][1] = vm.toString(guardianBorg); - partyValues[1][2] = "inbox@guardian.borg"; - partyValues[1][3] = "Foundation"; + partyValues[0] = formatPartyValues(vm, guardianSafeInfo); + partyValues[1] = formatPartyValues(vm, guardianInfo); return partyValues; } } diff --git a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol index 7911b5d..71cb87a 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol @@ -16,74 +16,17 @@ import {metavestController} from "../../src/MetaVesTController.sol"; library ZkSyncGuardianCompensation2025_2026 { function getDefault() internal returns(ZkSyncGuardianCompensation2024_2025.Config memory) { - string[] memory serviceGlobalFields = new string[](1); - serviceGlobalFields[0] = "expiryDate"; + ZkSyncGuardianCompensation2024_2025.Config memory config = ZkSyncGuardianCompensation2024_2025.getDefault(); - string[] memory servicePartyFields = new string[](4); - servicePartyFields[0] = "name"; - servicePartyFields[1] = "evmAddress"; - servicePartyFields[2] = "contactDetails"; - servicePartyFields[3] = "type"; + // ZK Governance - string[] memory compGlobalFields = new string[](11); - compGlobalFields[0] = "metavestType"; - compGlobalFields[1] = "grantor"; - compGlobalFields[2] = "grantee"; - compGlobalFields[3] = "tokenContract"; - compGlobalFields[4] = "tokenStreamTotal"; - compGlobalFields[5] = "vestingCliffCredit"; - compGlobalFields[6] = "unlockingCliffCredit"; - compGlobalFields[7] = "vestingRate"; - compGlobalFields[8] = "vestingStartTime"; - compGlobalFields[9] = "unlockRate"; - compGlobalFields[10] = "unlockStartTime"; + // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 + config.zkCappedMinter = IZkCappedMinterV2(0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A); - string[] memory compPartyFields = new string[](4); - compPartyFields[0] = "name"; - compPartyFields[1] = "evmAddress"; - compPartyFields[2] = "contactDetails"; - compPartyFields[3] = "type"; + // zkSync Guardian Compensation Agreement - IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); + config.metavestVestingAndUnlockStartTime = 1756684800; // 2025/09/01 00:00 UTC - address[] memory guardians = new address[](0); // TODO TBD - - return ZkSyncGuardianCompensation2024_2025.Config({ - zkToken: IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E), - // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 - zkCappedMinter: IZkCappedMinterV2(0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A), - - guardianSafe: guardianSafe, - - metalexSafe: IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc), - registry: CyberAgreementRegistry(address(0)), // TODO TBD - vestingAllocationFactory: VestingAllocationFactory(address(0)), // TODO TBD - controller: metavestController(address(0)), // TODO TBD - - serviceAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD - serviceTemplateName: "MetaLeX <> zkSync Guardian BORG Service Agreement", // TODO TBD - serviceTemplateId: bytes32(uint256(200)), - serviceGlobalFields: serviceGlobalFields, - servicePartyFields: servicePartyFields, - - serviceAgreementExpiry: 1788220800, - - compAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD - compTemplateName: "zkSync Guardian Compensation Agreement", // TODO TBD - compTemplateId: bytes32(uint256(201)), - compGlobalFields: compGlobalFields, - compPartyFields: compPartyFields, - - guardianSafeInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD - name: "Guardian BORG", - evmAddress: address(guardianSafe), - contactDetails: "inbox@guardian.borg", - _type: "Foundation" - }), - guardians: guardians, - fixedAnnualCompensation: 625e3 ether, - metavestVestingAndUnlockStartTime: 1756684800, // 2025/09/01 00:00 UTC - milestones: new BaseAllocation.Milestone[](0) - }); + return config; } } diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index 162afcf..c026262 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -14,7 +14,7 @@ import {Script} from "forge-std/Script.sol"; import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import {console} from "forge-std/console.sol"; +import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; contract ProposeMetaVestDealScript is SafeTxHelper, Script { @@ -43,7 +43,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { return run( proposerPrivateKey, guardianInfo, // Default guardian allocations - config._parseAllocation(address(config.zkToken), config.metavestVestingAndUnlockStartTime), + config.parseAllocation(), config ); } @@ -58,6 +58,17 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { address proposer = vm.addr(proposerPrivateKey); + console2.log(""); + console2.log("=== ProposeMetaVestDealScript ==="); + console2.log("Proposer: ", proposer); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("ZK token: ", address(config.zkToken)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); + console2.log("MetavesTController: ", address(config.controller)); + console2.log("ZkCappedMinterV2: ", address(config.zkCappedMinter)); + console2.log(""); + // Assume Guardian SAFE already delegate signing to the deployer // Propose a new deal @@ -68,14 +79,8 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { parties[0] = address(config.guardianSafe); parties[1] = guardianInfo.evmAddress; - string[] memory globalValues = config._compFormatGlobalValues( - vm, - address(config.guardianSafe), - guardianInfo.evmAddress, - address(config.zkToken), - startTime - ); - string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025._compFormatPartyValues( + string[] memory globalValues = config.formatCompGlobalValues(vm, guardianInfo.evmAddress); + string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues( vm, config.guardianSafeInfo, guardianInfo @@ -123,17 +128,11 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { ); vm.stopBroadcast(); - - console.log("Proposer: ", proposer); - console.log("Guardian Safe: ", address(config.guardianSafe)); - console.log("ZK token: ", address(config.zkToken)); - console.log("CyberAgreementRegistry: ", address(config.registry)); - console.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); - console.log("MetavesTController: ", address(config.controller)); - console.log("ZkCappedMinterV2: ", address(config.zkCappedMinter)); - console.log("Created:"); - console.log(" Agreement ID:"); - console.logBytes32(contractId); + + console2.log("Created:"); + console2.log(" Agreement ID:"); + console2.logBytes32(contractId); + console2.log(""); return contractId; } diff --git a/scripts/proposeServiceAgreement.s.sol b/scripts/proposeServiceAgreement.s.sol index 993be78..e55a5e3 100644 --- a/scripts/proposeServiceAgreement.s.sol +++ b/scripts/proposeServiceAgreement.s.sol @@ -33,8 +33,16 @@ contract ProposeServiceAgreementScript is SafeTxHelper, Script { uint256 proposerPrivateKey, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { + address metalexProposer = vm.addr(proposerPrivateKey); + console2.log(""); + console2.log("=== ProposeServiceAgreementScript ==="); + console2.log("MetaLeX proposer: ", address(metalexProposer)); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log(""); + // Assume Guardian SAFE already delegate signing to the deployer // Propose a new deal @@ -43,8 +51,8 @@ contract ProposeServiceAgreementScript is SafeTxHelper, Script { parties[0] = address(config.metalexSafe); parties[1] = address(config.guardianSafe); - string[] memory globalValues = ZkSyncGuardianCompensation2024_2025._serviceFormatGlobalValues(vm, config.serviceAgreementExpiry); - string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025._serviceFormatPartyValues(vm, address(metalexProposer), address(config.guardianSafe)); + string[] memory globalValues = ZkSyncGuardianCompensation2024_2025.formatServiceGlobalValues(vm, config.serviceAgreementExpiry); + string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, config.metalexSafeInfo, config.guardianSafeInfo); uint256 agreementSalt = block.timestamp; @@ -93,12 +101,10 @@ contract ProposeServiceAgreementScript is SafeTxHelper, Script { vm.stopBroadcast(); - console2.log("MetaLeX proposer: ", address(metalexProposer)); - console2.log("Guardian Safe: ", address(config.guardianSafe)); - console2.log("CyberAgreementRegistry: ", address(config.registry)); console2.log("Created:"); console2.log(" Agreement ID:"); console2.logBytes32(contractId); + console2.log(""); return contractId; } diff --git a/scripts/signDealAndCreateMetavest.s.sol b/scripts/signDealAndCreateMetavest.s.sol index 41a39f4..37e49ea 100644 --- a/scripts/signDealAndCreateMetavest.s.sol +++ b/scripts/signDealAndCreateMetavest.s.sol @@ -14,7 +14,7 @@ import {Script} from "forge-std/Script.sol"; import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import {console} from "forge-std/console.sol"; +import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { @@ -44,9 +44,23 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(address) { + address signer = vm.addr(granteePrivateKey); + + console2.log(""); + console2.log("=== SignDealAndCreateMetavestScript ==="); + console2.log("Signer: ", signer); + console2.log("Grantee: ", granteeInfo.evmAddress); + console2.log("Grantee Name: ", granteeInfo.name); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log("MetavesTController: ", address(config.controller)); + console2.log("Agreement ID:"); + console2.logBytes32(agreementId); + console2.log(""); + // Sign the deal and create MetaVesT - string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025._compFormatPartyValues(vm, granteeInfo); + string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, granteeInfo); bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, config.registry.DOMAIN_SEPARATOR(), @@ -55,13 +69,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { config.compAgreementUri, config.compGlobalFields, config.compPartyFields, - config._compFormatGlobalValues( - vm, - address(config.guardianSafe), - granteeInfo.evmAddress, - address(config.zkToken), - config.metavestVestingAndUnlockStartTime - ), + config.formatCompGlobalValues(vm, granteeInfo.evmAddress), granteePartyValues, granteePrivateKey ); @@ -78,16 +86,10 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { ); vm.stopBroadcast(); - - console.log("Grantee: ", granteeInfo.evmAddress); - console.log("Grantee Name: ", granteeInfo.name); - console.log("Guardian Safe: ", address(config.guardianSafe)); - console.log("CyberAgreementRegistry: ", address(config.registry)); - console.log("MetavesTController: ", address(config.controller)); - console.log("Agreement ID:"); - console.logBytes32(agreementId); - console.log("Created:"); - console.log(" MetavesT: ", address(metavest)); + + console2.log("Created:"); + console2.log(" MetavesT: ", address(metavest)); + console2.log(""); return address(metavest); } From 66a8c61e2330212b07f37f853c4db0a710e2b1be Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 26 Aug 2025 16:03:07 -0700 Subject: [PATCH 44/68] chore: add configs and scripts for testnet deployment --- scripts/deployTestZkCappedMinter.s.sol | 87 ++++++++++++++++++ .../deployZkSyncGuardianCompensation.s.sol | 22 ++++- ...yncGuardianCompensationPrerequisites.s.sol | 16 +++- scripts/executeSafeTx.s.sol | 78 ++++++++++++++++ .../ZkSyncGuardianCompensation2024_2025.sol | 4 +- ...ncGuardianCompensationSepolia2024_2025.sol | 92 +++++++++++++++++++ .../proposeAllGuardiansMetavestDeals.s.sol | 75 +++++++++++++++ test/ZkSyncGuardianCompensation.t.sol | 6 +- 8 files changed, 368 insertions(+), 12 deletions(-) create mode 100644 scripts/deployTestZkCappedMinter.s.sol create mode 100644 scripts/executeSafeTx.s.sol create mode 100644 scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol create mode 100644 scripts/proposeAllGuardiansMetavestDeals.s.sol diff --git a/scripts/deployTestZkCappedMinter.s.sol b/scripts/deployTestZkCappedMinter.s.sol new file mode 100644 index 0000000..72583e6 --- /dev/null +++ b/scripts/deployTestZkCappedMinter.s.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract DeployTestZkCappedMinterScript is SafeTxHelper, Script { + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + vm.envUint("DEPLOYER_PRIVATE_KEY"), + + // zkSync Sepolia for 2024-2025 + "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.2024-2025", + IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E), + ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + + // zkSync Sepolia for 2025-2026 +// "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.2025-2026", +// IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E), +// ZkSyncGuardianCompensationSepolia2025_2026.getDefault() + ); + } + + /// @dev For running in tests + function run( + uint256 deployerPrivateKey, + string memory saltStr, + IZkCappedMinterV2Factory zkCappedMinterFactory, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns( + address + ) { + address deployer = vm.addr(deployerPrivateKey); + + uint256 startTime = block.timestamp + 5 minutes; + + console2.log(""); + console2.log("=== DeployTestZkCappedMinterScript ==="); + console2.log("Deployer: ", deployer); + console2.log("Salt string: ", saltStr); + console2.log("Start time: ", startTime); + console2.log("ZK Token: ", address(config.zkToken)); + console2.log("Guardian SAFE: ", address(config.guardianSafe)); + console2.log(""); + + bytes32 salt = keccak256(bytes(saltStr)); + + vm.startBroadcast(deployerPrivateKey); + + // Deploy MetaVesT Controller + + ZkCappedMinterV2 zkCappedMinter = ZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( + address(config.zkToken), + address(config.guardianSafe), + 8.5e6 ether, + uint48(startTime), + uint48(startTime + 365 days * 2), + uint256(salt) + )); + + // Grant capped minter permission + + config.zkToken.grantRole(config.zkToken.MINTER_ROLE(), address(zkCappedMinter)); + + vm.stopBroadcast(); + + // Output logs + + console2.log("Deployed addresses:"); + console2.log(" ZK Capped Minter v2: ", address(zkCappedMinter)); + console2.log(""); + + return address(zkCappedMinter); + } +} diff --git a/scripts/deployZkSyncGuardianCompensation.s.sol b/scripts/deployZkSyncGuardianCompensation.s.sol index 3bf115f..ccd416e 100644 --- a/scripts/deployZkSyncGuardianCompensation.s.sol +++ b/scripts/deployZkSyncGuardianCompensation.s.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.24; import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -17,16 +19,30 @@ contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { deployCompensation( - "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2024-2025", vm.envUint("DEPLOYER_PRIVATE_KEY"), - ZkSyncGuardianCompensation2024_2025.getDefault() + + // zkSync Era for 2024-2025 +// "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2024-2025", +// ZkSyncGuardianCompensation2024_2025.getDefault() + + // zkSync Era for 2025-2026 +// "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2025-2026", +// ZkSyncGuardianCompensation2025_2026.getDefault() + + // zkSync Sepolia for 2024-2025 + "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.1.2024-2025", + ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + + // zkSync Sepolia for 2025-2026 +// "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.2025-2026", +// ZkSyncGuardianCompensationSepolia2025_2026.getDefault() ); } /// @dev For running in tests function deployCompensation( - string memory saltStr, uint256 deployerPrivateKey, + string memory saltStr, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns( metavestController, diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol index 216721a..23bbf32 100644 --- a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol +++ b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.24; import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -21,16 +23,22 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Sc /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { deployPrerequisites( - "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0", vm.envUint("DEPLOYER_PRIVATE_KEY"), - ZkSyncGuardianCompensation2024_2025.getDefault() + + // zkSync Era +// "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0", +// ZkSyncGuardianCompensation2024_2025.getDefault() + + // zkSync Sepolia + "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.1", + ZkSyncGuardianCompensationSepolia2024_2025.getDefault() ); } /// @dev For running in tests function deployPrerequisites( - string memory saltStr, uint256 deployerPrivateKey, + string memory saltStr, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns( BorgAuth, @@ -43,7 +51,7 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Sc console2.log("=== DeployZkSyncGuardianCompensationPrerequisitesScript ==="); console2.log("Deployer: ", deployer); console2.log("Salt string: ", saltStr); - console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("MetaLeX SAFE: ", address(config.metalexSafe)); console2.log(""); bytes32 salt = keccak256(bytes(saltStr)); diff --git a/scripts/executeSafeTx.s.sol b/scripts/executeSafeTx.s.sol new file mode 100644 index 0000000..35aa5ac --- /dev/null +++ b/scripts/executeSafeTx.s.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract ExecuteSafeTxScript is SafeTxHelper, Script { + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + vm.envUint("DEPLOYER_PRIVATE_KEY"), + +// ZkSyncGuardianCompensationSepolia2024_2025.getDefault().guardianSafe, // safe +// 0x6F26e588f28bf67C016EEA19CA90c4E41B70d499, // to +// 0, // value +// hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000856a8aea8a37a338e2490384bb790cd87b5caae4" // data + +// ZkSyncGuardianCompensationSepolia2024_2025.getDefault().guardianSafe, // safe +// 0x856A8Aea8a37A338e2490384Bb790cD87b5CaaE4, // to +// 0, // value +// hex"66e261840000000000000000000000006f26e588f28bf67c016eea19ca90c4e41b70d499" // data + + ZkSyncGuardianCompensationSepolia2024_2025.getDefault().guardianSafe, // safe + address(ZkSyncGuardianCompensationSepolia2024_2025.getDefault().registry), // to + 0, // value + abi.encodeWithSelector( + CyberAgreementRegistry.setDelegation.selector, + 0x5ff4e90Efa2B88cf3cA92D63d244a78a88219Abf, + block.timestamp + 365 days * 3 // This is a hack, one should not delegate signing for this long + ) // data + ); + } + + /// @dev For running in tests + function run( + uint256 deployerPrivateKey, + IGnosisSafe safe, + address to, + uint256 value, + bytes memory data + ) public virtual { + address deployer = vm.addr(deployerPrivateKey); + + console2.log(""); + console2.log("=== ExecuteSafeTxScript ==="); + console2.log("Deployer: ", deployer); + console2.log("SAFE: ", address(safe)); + console2.log("to: ", to); + console2.log("value: ", value); + console2.log("data: "); + console2.logBytes(data); + console2.log(""); + + vm.startBroadcast(deployerPrivateKey); + + // Guardian SAFE to set MetaVesT Controller's ZK Capped Minter + _signAndExecSafeTransaction( + deployerPrivateKey, + address(safe), + to, + value, + data + ); + + vm.stopBroadcast(); + } +} diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol index 1eca8a3..8ab127e 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -52,7 +52,7 @@ library ZkSyncGuardianCompensation2024_2025 { string[] compGlobalFields; string[] compPartyFields; - address[] guardians; + PartyInfo[] guardians; uint256 fixedAnnualCompensation; uint48 metavestVestingAndUnlockStartTime; BaseAllocation.Milestone[] milestones; @@ -97,7 +97,7 @@ library ZkSyncGuardianCompensation2024_2025 { IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); IGnosisSafe metalexSafe = IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc); - address[] memory guardians = new address[](0); // TODO TBD + PartyInfo[] memory guardians = new PartyInfo[](0); // TODO TBD return Config({ diff --git a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol new file mode 100644 index 0000000..ab95752 --- /dev/null +++ b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.28; + +import {ZkSyncGuardianCompensation2024_2025} from "./ZkSyncGuardianCompensation2024_2025.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {CommonBase} from "forge-std/Base.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {IZkTokenV1} from "../../src/interfaces/zk-governance/IZkTokenV1.sol"; +import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {IGnosisSafe} from "../../test/lib/safe.sol"; +import {BaseAllocation} from "../../src/BaseAllocation.sol"; +import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; +import {metavestController} from "../../src/MetaVesTController.sol"; + +library ZkSyncGuardianCompensationSepolia2024_2025 { + + function getDefault() internal returns(ZkSyncGuardianCompensation2024_2025.Config memory) { + ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(); + + IGnosisSafe guardianSafe = IGnosisSafe(0x3C785F96864002eB47bDe32d597476a3D97fCd15); + IGnosisSafe metalexSafe = IGnosisSafe(0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe); // This is faked by EOA + + ZkSyncGuardianCompensation2024_2025.PartyInfo[] memory guardians = new ZkSyncGuardianCompensation2024_2025.PartyInfo[](2); + guardians[0] = ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: "Alice", + evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b, + contactDetails: "alice@email.com", + _type: "individual" + }); + guardians[1] = ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: "Bob", + evmAddress: 0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe, + contactDetails: "bob@email.com", + _type: "individual" + }); + + return ZkSyncGuardianCompensation2024_2025.Config({ + + // ZK Governance + + zkToken: IZkTokenV1(0x384278020767ed975618b94DA36EC54Da362812A), + zkCappedMinter: IZkCappedMinterV2(0x6F26e588f28bf67C016EEA19CA90c4E41B70d499), + + // zkSync Guardians + + guardianSafe: guardianSafe, + guardianSafeInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: defaultConfig.guardianSafeInfo.name, + evmAddress: address(guardianSafe), + contactDetails: defaultConfig.guardianSafeInfo.contactDetails, + _type: defaultConfig.guardianSafeInfo._type + }), + + // MetaLeX + + metalexSafe: metalexSafe, + metalexSafeInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: defaultConfig.metalexSafeInfo.name, + evmAddress: address(metalexSafe), + contactDetails: defaultConfig.metalexSafeInfo.contactDetails, + _type: defaultConfig.metalexSafeInfo._type + }), + registry: CyberAgreementRegistry(0x7BD5EBE57e64AA6D9904caE90A192E76d818b49e), + vestingAllocationFactory: VestingAllocationFactory(0x3fFd990dB0E398235456A720501E6007003a6cdf), + controller: metavestController(0x856A8Aea8a37A338e2490384Bb790cD87b5CaaE4), + + // MetaLeX <> zkSync Guardian BORG Service Agreement + + serviceAgreementUri: defaultConfig.serviceAgreementUri, + serviceTemplateName: defaultConfig.serviceTemplateName, + serviceTemplateId: defaultConfig.serviceTemplateId, + serviceGlobalFields: defaultConfig.serviceGlobalFields, + servicePartyFields: defaultConfig.servicePartyFields, + + serviceAgreementExpiry: defaultConfig.serviceAgreementExpiry, + + // zkSync Guardian Compensation Agreement + + compAgreementUri: defaultConfig.compAgreementUri, + compTemplateName: defaultConfig.compTemplateName, + compTemplateId: defaultConfig.compTemplateId, + compGlobalFields: defaultConfig.compGlobalFields, + compPartyFields: defaultConfig.compPartyFields, + + guardians: guardians, + fixedAnnualCompensation: defaultConfig.fixedAnnualCompensation, + metavestVestingAndUnlockStartTime: defaultConfig.metavestVestingAndUnlockStartTime, + milestones: defaultConfig.milestones + }); + } +} diff --git a/scripts/proposeAllGuardiansMetavestDeals.s.sol b/scripts/proposeAllGuardiansMetavestDeals.s.sol new file mode 100644 index 0000000..512e017 --- /dev/null +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ProposeMetaVestDealScript} from "./proposeMetavestDeal.s.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreementUtils.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public override { + run( + vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), + + // zkSync Sepolia for 2024-2025 + ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + ); + } + + /// @dev For running in tests + function run( + uint256 proposerPrivateKey, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns(bytes32[] memory) { + + address proposer = vm.addr(proposerPrivateKey); + + console2.log(""); + console2.log("=== ProposeAllGuardiansMetaVestDealScript ==="); + console2.log("Proposer: ", proposer); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); + console2.log("MetavesTController: ", address(config.controller)); + console2.log(""); + + bytes32[] memory agreementIds = new bytes32[](config.guardians.length); + + for (uint256 i = 0; i < config.guardians.length; i++) { + console2.log("Proposing to Guardian #%d", i + 1); + console2.log(" name:", config.guardians[i].name); + console2.log(" address:", config.guardians[i].evmAddress); + console2.log(" contact:", config.guardians[i].contactDetails); + console2.log(" type:", config.guardians[i]._type); + console2.log(""); + + agreementIds[i] = run( + proposerPrivateKey, + config.guardians[i], + config + ); + } + + console2.log("Created:"); + for (uint256 i = 0; i < agreementIds.length; i++) { + console2.log(" Agreement ID #%d:", i + 1); + console2.logBytes32(agreementIds[i]); + console2.log(""); + } + + return agreementIds; + } +} diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index ecde136..789e235 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -76,8 +76,8 @@ contract ZkSyncGuardianCompensationTest is config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(); (auth, registry, vestingAllocationFactory) = DeployZkSyncGuardianCompensationPrerequisitesScript.deployPrerequisites( - saltStr, deployerPrivateKey, + saltStr, config2024_2025 ); @@ -89,16 +89,16 @@ contract ZkSyncGuardianCompensationTest is // Deploy 2024-2025 compensation contracts (controller, safeTxs2024_2025) = DeployZkSyncGuardianCompensationScript.deployCompensation( - string(abi.encodePacked(saltStr, ".2024-2025")), deployerPrivateKey, + string(abi.encodePacked(saltStr, ".2024-2025")), config2024_2025 ); config2024_2025.controller = controller; // Update configs with deployed contracts // Deploy 2025-2026 compensation contracts (controller, safeTxs2025_2026) = DeployZkSyncGuardianCompensationScript.deployCompensation( - string(abi.encodePacked(saltStr, ".2025-2026")), deployerPrivateKey, + string(abi.encodePacked(saltStr, ".2025-2026")), config2025_2026 ); config2025_2026.controller = controller; // Update configs with deployed contracts From e602a7a0b0d474a352a6a49750a8858b315edd5c Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 26 Aug 2025 18:01:34 -0700 Subject: [PATCH 45/68] chore: add scripts for proposing service agreements and void contracts in testnet --- remappings.txt | 3 +- scripts/lib/CyberAgreementUtils.sol | 124 ++++++++++++++++++++++++ scripts/proposeServiceAgreement.s.sol | 8 +- scripts/signDealAndCreateMetavest.s.sol | 18 +++- scripts/voidAgreement.s.sol | 73 ++++++++++++++ 5 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 scripts/lib/CyberAgreementUtils.sol create mode 100644 scripts/voidAgreement.s.sol diff --git a/remappings.txt b/remappings.txt index 217f5fe..8aeadcc 100644 --- a/remappings.txt +++ b/remappings.txt @@ -8,4 +8,5 @@ openzeppelin-contracts/=lib/zk-governance/l1-contracts/lib/openzeppelin-contract @openzeppelin/contracts-upgradeable=lib/zk-governance/l2-contracts/lib/openzeppelin-contracts-upgradeable/contracts @openzeppelin/contracts/=lib/zk-governance/l2-contracts/lib/openzeppelin-contracts/contracts/ solmate/=lib/zk-governance/l2-contracts/lib/flexible-voting/lib/solmate/src/ -zk-governance/=lib/zk-governance/ \ No newline at end of file +zk-governance/=lib/zk-governance/ +cybercorps-contracts/=lib/cybercorps-contracts/ diff --git a/scripts/lib/CyberAgreementUtils.sol b/scripts/lib/CyberAgreementUtils.sol new file mode 100644 index 0000000..7c83107 --- /dev/null +++ b/scripts/lib/CyberAgreementUtils.sol @@ -0,0 +1,124 @@ +/* .o. + .888. + .8"888. + .8' `888. + .88ooo8888. + .8' `888. +o88o o8888o + + + +ooo ooooo . ooooo ooooooo ooooo +`88. .888' .o8 `888' `8888 d8' + 888b d'888 .ooooo. .o888oo .oooo. 888 .ooooo. Y888..8P + 8 Y88. .P 888 d88' `88b 888 `P )88b 888 d88' `88b `8888' + 8 `888' 888 888ooo888 888 .oP"888 888 888ooo888 .8PY888. + 8 Y 888 888 .o 888 . d8( 888 888 o 888 .o d8' `888b +o8o o888o `Y8bod8P' "888" `Y888""8o o888ooooood8 `Y8bod8P' o888o o88888o + + + + .oooooo. .o8 .oooooo. + d8P' `Y8b "888 d8P' `Y8b +888 oooo ooo 888oooo. .ooooo. oooo d8b 888 .ooooo. oooo d8b oo.ooooo. +888 `88. .8' d88' `88b d88' `88b `888""8P 888 d88' `88b `888""8P 888' `88b +888 `88..8' 888 888 888ooo888 888 888 888 888 888 888 888 +`88b ooo `888' 888 888 888 .o 888 `88b ooo 888 888 888 888 888 .o. + `Y8bood8P' .8' `Y8bod8P' `Y8bod8P' d888b `Y8bood8P' `Y8bod8P' d888b 888bod8P' Y8P + .o..P' 888 + `Y8P' o888o +_______________________________________________________________________________________________________ + +All software, documentation and other files and information in this repository (collectively, the "Software") +are copyright MetaLeX Labs, Inc., a Delaware corporation. + +All rights reserved. + +The Software is proprietary and shall not, in part or in whole, be used, copied, modified, merged, published, +distributed, transmitted, sublicensed, sold, or otherwise used in any form or by any means, electronic or +mechanical, including photocopying, recording, or by any information storage and retrieval system, +except with the express prior written permission of the copyright holder.*/ + +pragma solidity 0.8.28; + +import {Vm} from "forge-std/Test.sol"; + +library CyberAgreementUtils { + function signAgreementTypedData( + Vm vm, + bytes32 _domainSeparator, + bytes32 _typeHash, + bytes32 contractId, + string memory contractUri, + string[] memory globalFields, + string[] memory partyFields, + string[] memory globalValues, + string[] memory partyValues, + uint256 privKey + ) internal pure returns (bytes memory signature) { + // Hash string arrays the same way as the contract + bytes32 contractUriHash = keccak256(bytes(contractUri)); + bytes32 globalFieldsHash = _hashStringArray(globalFields); + bytes32 partyFieldsHash = _hashStringArray(partyFields); + bytes32 globalValuesHash = _hashStringArray(globalValues); + bytes32 partyValuesHash = _hashStringArray(partyValues); + + // Create the message hash using the same approach as the contract + bytes32 structHash = keccak256( + abi.encode( + _typeHash, + contractId, + contractUriHash, + globalFieldsHash, + partyFieldsHash, + globalValuesHash, + partyValuesHash + ) + ); + + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", _domainSeparator, structHash) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, digest); + signature = abi.encodePacked(r, s, v); + return signature; + } + + function signVoidTypedData( + Vm vm, + bytes32 _domainSeparator, + bytes32 _typeHash, + bytes32 contractId, + address party, + uint256 privKey + ) internal pure returns (bytes memory signature) { + // Create the message hash using the same approach as the contract + bytes32 structHash = keccak256( + abi.encode( + _typeHash, + contractId, + party + ) + ); + + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", _domainSeparator, structHash) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, digest); + signature = abi.encodePacked(r, s, v); + return signature; + } + + // Add this helper function to your test contract + function _hashStringArray( + string[] memory array + ) internal pure returns (bytes32) { + bytes32[] memory hashes = new bytes32[](array.length); + for (uint256 i = 0; i < array.length; i++) { + hashes[i] = keccak256(bytes(array[i])); + } + return keccak256(abi.encodePacked(hashes)); + } +} diff --git a/scripts/proposeServiceAgreement.s.sol b/scripts/proposeServiceAgreement.s.sol index e55a5e3..730aab5 100644 --- a/scripts/proposeServiceAgreement.s.sol +++ b/scripts/proposeServiceAgreement.s.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -24,7 +25,12 @@ contract ProposeServiceAgreementScript is SafeTxHelper, Script { function run() public virtual { run( vm.envUint("METALEX_SAFE_DELEGATE_PRIVATE_KEY"), - ZkSyncGuardianCompensation2024_2025.getDefault() + + // zkSync Era +// ZkSyncGuardianCompensation2024_2025.getDefault() + + // zkSync Sepolia + ZkSyncGuardianCompensationSepolia2024_2025.getDefault() ); } diff --git a/scripts/signDealAndCreateMetavest.s.sol b/scripts/signDealAndCreateMetavest.s.sol index 37e49ea..35bfe1d 100644 --- a/scripts/signDealAndCreateMetavest.s.sol +++ b/scripts/signDealAndCreateMetavest.s.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -24,15 +25,26 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { function run() public virtual { uint256 granteePrivateKey = vm.envUint("GRANTEE_PRIVATE_KEY"); run( +// granteePrivateKey, +// 0x0000000000000000000000000000000000000000000000000000000000000000, // TODO TBD +// ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD +// name: "Alice", +// evmAddress: vm.addr(granteePrivateKey), +// contactDetails: "email@company.com", +// _type: "individual" +// }), +// ZkSyncGuardianCompensation2024_2025.getDefault() + + // zkSync Sepolia granteePrivateKey, - 0x0000000000000000000000000000000000000000000000000000000000000000, // TODO TBD + 0xd0d7610ca18b8a76a36c7e1241929641c06cce69ddc1161beebe69b72dae6cbf, ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD name: "Alice", evmAddress: vm.addr(granteePrivateKey), - contactDetails: "email@company.com", + contactDetails: "alice@email.com", _type: "individual" }), - ZkSyncGuardianCompensation2024_2025.getDefault() + ZkSyncGuardianCompensationSepolia2024_2025.getDefault() ); } diff --git a/scripts/voidAgreement.s.sol b/scripts/voidAgreement.s.sol new file mode 100644 index 0000000..b9072c9 --- /dev/null +++ b/scripts/voidAgreement.s.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {CyberAgreementUtils} from "./lib/CyberAgreementUtils.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract VoidAgreementScript is SafeTxHelper, Script { + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + // zkSync Era +// vm.envUint("GRANTEE_PRIVATE_KEY"), +// ZkSyncGuardianCompensation2024_2025.getDefault() + + // zkSync Sepolia + vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), +// vm.envUint("GRANTEE_PRIVATE_KEY"), + 0xc519e4ce6730ae9167f4e080f47ac1544405756cf301f0c8316578fc90f95e0a, + ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + ); + } + + /// @dev For running in tests + function run( + uint256 signerPrivateKey, + bytes32 agreementId, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual { + + address signer = vm.addr(signerPrivateKey); + + console2.log(""); + console2.log("=== VoidAgreementScript ==="); + console2.log("Signer: ", address(signer)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log("Agreement ID:"); + console2.logBytes32(agreementId); + console2.log(""); + + bytes memory signature = CyberAgreementUtils.signVoidTypedData( + vm, + config.registry.DOMAIN_SEPARATOR(), + config.registry.VOIDSIGNATUREDATA_TYPEHASH(), + agreementId, + signer, + signerPrivateKey + ); + + vm.startBroadcast(signerPrivateKey); + + config.registry.voidContractFor( + agreementId, + signer, + signature + ); + + vm.stopBroadcast(); + } +} From e6d757807483281152aef1cdd1bd6e25f50e1507 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 27 Aug 2025 15:41:42 -0700 Subject: [PATCH 46/68] test: verify audit M-01 --- test/VestingAllocation.t.sol | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/VestingAllocation.t.sol b/test/VestingAllocation.t.sol index feea277..3c6f6e4 100644 --- a/test/VestingAllocation.t.sol +++ b/test/VestingAllocation.t.sol @@ -23,6 +23,13 @@ contract MockMetaVesTController { function mint(address recipient, uint256 amount) external { ZkCappedMinterV2(zkCappedMinter).mint(recipient, amount); } + + function updateMetavestVestingRate( + address _grant, + uint160 _vestingRate + ) external { + BaseAllocation(_grant).updateVestingRate(_vestingRate); + } } contract VestingAllocationTest is Test { @@ -143,4 +150,26 @@ contract VestingAllocationTest is Test { vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_OnlyController.selector)); vestingAllocation.terminate(); } + + function test_GetGoverningPowerAfterVestingRateReduction() public { + // Withdraw cliff amount first + vm.prank(grantee); + VestingAllocation(vestingAllocation).withdraw(100 ether); + + skip(2 seconds); + assertEq(vestingAllocation.getAmountWithdrawable(), 10 ether * 2); + assertEq(vestingAllocation.getGoverningPower(), 10 ether * 2); + + vm.prank(grantee); + VestingAllocation(vestingAllocation).withdraw(10 ether); + + console2.log("getAmountWithdrawable: %d", vestingAllocation.getAmountWithdrawable()); // 10 ether + console2.log("getGoverningPower: %d", vestingAllocation.getGoverningPower()); // 10 ether + + mockController.updateMetavestVestingRate(address(vestingAllocation), 4 ether); + + // TODO this will fail because 4 ether/sec * 2 sec - 10 ether = -2 ether + console2.log("getAmountWithdrawable: %d", vestingAllocation.getAmountWithdrawable()); + console2.log("getGoverningPower: %d", vestingAllocation.getGoverningPower()); + } } From d206cc0b08d56d855002da0ba9a660517b4c4420 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 27 Aug 2025 16:24:47 -0700 Subject: [PATCH 47/68] fix: M-01: Governing power calculation may revert after vesting rate reduction --- src/VestingAllocation.sol | 20 ++++++++++++-------- test/VestingAllocation.t.sol | 10 +++++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/VestingAllocation.sol b/src/VestingAllocation.sol index f63bcdb..680eb0c 100644 --- a/src/VestingAllocation.sol +++ b/src/VestingAllocation.sol @@ -53,19 +53,23 @@ contract VestingAllocation is BaseAllocation { /// @notice returns the governing power of the VestingAllocation /// @return governingPower - the governing power of the VestingAllocation based on the governance setting function getGoverningPower() external view override returns (uint256 governingPower) { - if(govType==GovType.all) - { + if(govType==GovType.all) { uint256 totalMilestoneAward = 0; - for(uint256 i; i < milestones.length; ++i) - { + for(uint256 i; i < milestones.length; ++i) { totalMilestoneAward += milestones[i].milestoneAward; } governingPower = (allocation.tokenStreamTotal + totalMilestoneAward) - tokensWithdrawn; + } else if(govType==GovType.vested) { + uint256 amount = getVestedTokenAmount(); + governingPower = (amount > tokensWithdrawn) + ? amount - tokensWithdrawn + : 0; + } else { + uint256 amount = _min(getVestedTokenAmount(), getUnlockedTokenAmount()); + governingPower = (amount > tokensWithdrawn) + ? amount - tokensWithdrawn + : 0; } - else if(govType==GovType.vested) - governingPower = getVestedTokenAmount() - tokensWithdrawn; - else - governingPower = _min(getVestedTokenAmount(), getUnlockedTokenAmount()) - tokensWithdrawn; return governingPower; } diff --git a/test/VestingAllocation.t.sol b/test/VestingAllocation.t.sol index 3c6f6e4..76c1231 100644 --- a/test/VestingAllocation.t.sol +++ b/test/VestingAllocation.t.sol @@ -163,13 +163,13 @@ contract VestingAllocationTest is Test { vm.prank(grantee); VestingAllocation(vestingAllocation).withdraw(10 ether); - console2.log("getAmountWithdrawable: %d", vestingAllocation.getAmountWithdrawable()); // 10 ether - console2.log("getGoverningPower: %d", vestingAllocation.getGoverningPower()); // 10 ether + assertEq(vestingAllocation.getAmountWithdrawable(), 10 ether * 2 - 10 ether); + assertEq(vestingAllocation.getGoverningPower(), 10 ether * 2 - 10 ether); mockController.updateMetavestVestingRate(address(vestingAllocation), 4 ether); - // TODO this will fail because 4 ether/sec * 2 sec - 10 ether = -2 ether - console2.log("getAmountWithdrawable: %d", vestingAllocation.getAmountWithdrawable()); - console2.log("getGoverningPower: %d", vestingAllocation.getGoverningPower()); + // 4 ether/sec * 2 sec - 10 ether = -2 ether < 0 + assertEq(vestingAllocation.getAmountWithdrawable(), 0 ether); + assertEq(vestingAllocation.getGoverningPower(), 0 ether); } } From 34288544b0bdf22732b0d124785c27bbd4e21ba5 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 27 Aug 2025 21:36:54 -0700 Subject: [PATCH 48/68] chore: add script to create transactions for SAFE --- scripts/createSafeTx.s.sol | 68 +++++++++++++++++++ ...yncGuardianCompensationPrerequisites.s.sol | 35 +++++----- 2 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 scripts/createSafeTx.s.sol diff --git a/scripts/createSafeTx.s.sol b/scripts/createSafeTx.s.sol new file mode 100644 index 0000000..0d612f8 --- /dev/null +++ b/scripts/createSafeTx.s.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IZkCappedMinterV2} from "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract CreateSafeTxScript is SafeTxHelper, Script { + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + IGnosisSafe safe; + ZkSyncGuardianCompensation2024_2025.Config memory config; + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](2); + + // zkSync Sepolia + config = ZkSyncGuardianCompensationSepolia2024_2025.getDefault(); + safe = config.guardianSafe; + safeTxs[0] = GnosisTransaction({ + to: address(config.registry), + value: 0 ether, + data: abi.encodeWithSelector( + CyberAgreementRegistry.createTemplate.selector, + config.compTemplateId, + config.compTemplateName, + config.compAgreementUri, + config.compGlobalFields, + config.compPartyFields + ) + }); + safeTxs[1] = GnosisTransaction({ + to: address(config.registry), + value: 0 ether, + data: abi.encodeWithSelector( + CyberAgreementRegistry.createTemplate.selector, + config.serviceTemplateId, + config.serviceTemplateName, + config.serviceAgreementUri, + config.serviceGlobalFields, + config.servicePartyFields + ) + }); + + // Output logs + + console2.log(""); + console2.log("=== CreateSafeTxScript ==="); + console2.log("Safe: ", address(safe)); + console2.log("Safe TXs:"); + for (uint256 i = 0 ; i < safeTxs.length ; i++) { + console2.log(" #", i); + console2.log(" to:", safeTxs[i].to); + console2.log(" value:", safeTxs[i].value); + console2.log(" data:"); + console2.logBytes(safeTxs[i].data); + console2.log(""); + } + } +} diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol index 23bbf32..6a738fd 100644 --- a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol +++ b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol @@ -70,23 +70,24 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Sc ) ))); - // Create zkSync Guardian Compensation Agreement template - registry.createTemplate( - config.compTemplateId, - config.compTemplateName, - config.compAgreementUri, - config.compGlobalFields, - config.compPartyFields - ); - - // Create MetaLeX <> zkSync Guardian BORG Service Agreement template - registry.createTemplate( - config.serviceTemplateId, - config.serviceTemplateName, - config.serviceAgreementUri, - config.serviceGlobalFields, - config.servicePartyFields - ); + // We will create the templates later +// // Create zkSync Guardian Compensation Agreement template +// registry.createTemplate( +// config.compTemplateId, +// config.compTemplateName, +// config.compAgreementUri, +// config.compGlobalFields, +// config.compPartyFields +// ); +// +// // Create MetaLeX <> zkSync Guardian BORG Service Agreement template +// registry.createTemplate( +// config.serviceTemplateId, +// config.serviceTemplateName, +// config.serviceAgreementUri, +// config.serviceGlobalFields, +// config.servicePartyFields +// ); // Transfer CyberAgreementRegistry ownership to MetaLeX SAFE From 6168365089c7e68e4fc0605e0bc9e0f454bcc781 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 28 Aug 2025 11:19:19 -0700 Subject: [PATCH 49/68] feat: remove service-agreement and unused metavest-agreement party fields per request --- .../ZkSyncGuardianCompensation2024_2025.sol | 78 +++--------- .../ZkSyncGuardianCompensation2025_2026.sol | 37 ++++-- ...ncGuardianCompensationSepolia2024_2025.sol | 40 ++---- .../proposeAllGuardiansMetavestDeals.s.sol | 2 - scripts/proposeMetavestDeal.s.sol | 14 +-- scripts/proposeServiceAgreement.s.sol | 117 ------------------ scripts/signDealAndCreateMetavest.s.sol | 16 +-- test/ZkSyncGuardianCompensation.t.sol | 63 +++------- 8 files changed, 87 insertions(+), 280 deletions(-) delete mode 100644 scripts/proposeServiceAgreement.s.sol diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol index 8ab127e..48780ae 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -24,26 +24,15 @@ library ZkSyncGuardianCompensation2024_2025 { // zkSync Guardians IGnosisSafe guardianSafe; - PartyInfo guardianSafeInfo; + MetavestPartyInfo guardianSafeInfoForMetavest; // MetaLeX IGnosisSafe metalexSafe; - PartyInfo metalexSafeInfo; CyberAgreementRegistry registry; VestingAllocationFactory vestingAllocationFactory; metavestController controller; - // MetaLeX <> zkSync Guardian BORG Service Agreement - - string serviceAgreementUri; - string serviceTemplateName; - bytes32 serviceTemplateId; - string[] serviceGlobalFields; - string[] servicePartyFields; - - uint256 serviceAgreementExpiry; - // zkSync Guardian Compensation Agreement string compAgreementUri; @@ -52,29 +41,18 @@ library ZkSyncGuardianCompensation2024_2025 { string[] compGlobalFields; string[] compPartyFields; - PartyInfo[] guardians; + MetavestPartyInfo[] guardians; uint256 fixedAnnualCompensation; uint48 metavestVestingAndUnlockStartTime; BaseAllocation.Milestone[] milestones; } - struct PartyInfo { + struct MetavestPartyInfo { string name; address evmAddress; - string contactDetails; - string _type; } function getDefault() internal view returns(Config memory) { - string[] memory serviceGlobalFields = new string[](1); - serviceGlobalFields[0] = "expiryDate"; - - string[] memory servicePartyFields = new string[](4); - servicePartyFields[0] = "name"; - servicePartyFields[1] = "evmAddress"; - servicePartyFields[2] = "contactDetails"; - servicePartyFields[3] = "type"; - string[] memory compGlobalFields = new string[](11); compGlobalFields[0] = "metavestType"; compGlobalFields[1] = "grantor"; @@ -88,16 +66,14 @@ library ZkSyncGuardianCompensation2024_2025 { compGlobalFields[9] = "unlockRate"; compGlobalFields[10] = "unlockStartTime"; - string[] memory compPartyFields = new string[](4); + string[] memory compPartyFields = new string[](2); compPartyFields[0] = "name"; compPartyFields[1] = "evmAddress"; - compPartyFields[2] = "contactDetails"; - compPartyFields[3] = "type"; IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); IGnosisSafe metalexSafe = IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc); - PartyInfo[] memory guardians = new PartyInfo[](0); // TODO TBD + MetavestPartyInfo[] memory guardians = new MetavestPartyInfo[](0); // TODO TBD return Config({ @@ -110,41 +86,23 @@ library ZkSyncGuardianCompensation2024_2025 { // zkSync Guardians guardianSafe: guardianSafe, - guardianSafeInfo: PartyInfo({ // TODO TBD + guardianSafeInfoForMetavest: MetavestPartyInfo({ name: "Guardian BORG", - evmAddress: address(guardianSafe), - contactDetails: "inbox@guardian.borg", - _type: "Foundation" + evmAddress: address(guardianSafe) }), // MetaLeX metalexSafe: metalexSafe, - metalexSafeInfo: PartyInfo({ // TODO TBD - name: "MetaLeX", - evmAddress: address(metalexSafe), - contactDetails: "test@metalex.tech", - _type: "Corporation" - }), registry: CyberAgreementRegistry(address(0)), // TODO TBD vestingAllocationFactory: VestingAllocationFactory(address(0)), // TODO TBD controller: metavestController(address(0)), // TODO TBD - // MetaLeX <> zkSync Guardian BORG Service Agreement - - serviceAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD - serviceTemplateName: "MetaLeX <> zkSync Guardian BORG Service Agreement", // TODO TBD - serviceTemplateId: bytes32(uint256(200)), - serviceGlobalFields: serviceGlobalFields, - servicePartyFields: servicePartyFields, - - serviceAgreementExpiry: 1788220800, - // zkSync Guardian Compensation Agreement compAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD compTemplateName: "zkSync Guardian Compensation Agreement", // TODO TBD - compTemplateId: bytes32(uint256(201)), + compTemplateId: bytes32(uint256(200)), compGlobalFields: compGlobalFields, compPartyFields: compPartyFields, @@ -178,7 +136,7 @@ library ZkSyncGuardianCompensation2024_2025 { string[] memory globalValues = new string[](11); globalValues[0] = "0"; // metavestType: Vesting - globalValues[1] = vm.toString(config.guardianSafeInfo.evmAddress); // grantor + globalValues[1] = vm.toString(config.guardianSafeInfoForMetavest.evmAddress); // grantor globalValues[2] = vm.toString(grantee); // grantee globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) @@ -197,26 +155,24 @@ library ZkSyncGuardianCompensation2024_2025 { return globalValues; } - function formatPartyValues( + function formatMetaVestPartyValues( Vm vm, - PartyInfo memory partyInfo + MetavestPartyInfo memory partyInfo ) internal view returns(string[] memory) { - string[] memory partyValues = new string[](4); + string[] memory partyValues = new string[](2); partyValues[0] = partyInfo.name; partyValues[1] = vm.toString(partyInfo.evmAddress); - partyValues[2] = partyInfo.contactDetails; - partyValues[3] = partyInfo._type; return partyValues; } - function formatPartyValues( + function formatMetaVestPartyValues( Vm vm, - PartyInfo memory guardianSafeInfo, - PartyInfo memory guardianInfo + MetavestPartyInfo memory guardianSafeInfo, + MetavestPartyInfo memory guardianInfo ) internal view returns(string[][] memory) { string[][] memory partyValues = new string[][](2); - partyValues[0] = formatPartyValues(vm, guardianSafeInfo); - partyValues[1] = formatPartyValues(vm, guardianInfo); + partyValues[0] = formatMetaVestPartyValues(vm, guardianSafeInfo); + partyValues[1] = formatMetaVestPartyValues(vm, guardianInfo); return partyValues; } } diff --git a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol index 71cb87a..6bb6501 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol @@ -16,17 +16,40 @@ import {metavestController} from "../../src/MetaVesTController.sol"; library ZkSyncGuardianCompensation2025_2026 { function getDefault() internal returns(ZkSyncGuardianCompensation2024_2025.Config memory) { - ZkSyncGuardianCompensation2024_2025.Config memory config = ZkSyncGuardianCompensation2024_2025.getDefault(); + ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(); - // ZK Governance + return ZkSyncGuardianCompensation2024_2025.Config({ - // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 - config.zkCappedMinter = IZkCappedMinterV2(0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A); + // ZK Governance - // zkSync Guardian Compensation Agreement + zkToken: defaultConfig.zkToken, + // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 + zkCappedMinter: IZkCappedMinterV2(0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A), - config.metavestVestingAndUnlockStartTime = 1756684800; // 2025/09/01 00:00 UTC + // zkSync Guardians - return config; + guardianSafe: defaultConfig.guardianSafe, + guardianSafeInfoForMetavest: defaultConfig.guardianSafeInfoForMetavest, + + // MetaLeX + + metalexSafe: defaultConfig.metalexSafe, + registry: defaultConfig.registry, + vestingAllocationFactory: defaultConfig.vestingAllocationFactory, + controller: defaultConfig.controller, + + // zkSync Guardian Compensation Agreement + + compAgreementUri: defaultConfig.compAgreementUri, + compTemplateName: defaultConfig.compTemplateName, + compTemplateId: defaultConfig.compTemplateId, + compGlobalFields: defaultConfig.compGlobalFields, + compPartyFields: defaultConfig.compPartyFields, + + guardians: defaultConfig.guardians, + fixedAnnualCompensation: 625e3 ether, + metavestVestingAndUnlockStartTime: 1756684800, // 2025/09/01 00:00 UTC + milestones: defaultConfig.milestones + }); } } diff --git a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol index ab95752..1eb55b6 100644 --- a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol @@ -21,18 +21,14 @@ library ZkSyncGuardianCompensationSepolia2024_2025 { IGnosisSafe guardianSafe = IGnosisSafe(0x3C785F96864002eB47bDe32d597476a3D97fCd15); IGnosisSafe metalexSafe = IGnosisSafe(0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe); // This is faked by EOA - ZkSyncGuardianCompensation2024_2025.PartyInfo[] memory guardians = new ZkSyncGuardianCompensation2024_2025.PartyInfo[](2); - guardians[0] = ZkSyncGuardianCompensation2024_2025.PartyInfo({ + ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo[] memory guardians = new ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo[](2); + guardians[0] = ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ name: "Alice", - evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b, - contactDetails: "alice@email.com", - _type: "individual" + evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b }); - guardians[1] = ZkSyncGuardianCompensation2024_2025.PartyInfo({ + guardians[1] = ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ name: "Bob", - evmAddress: 0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe, - contactDetails: "bob@email.com", - _type: "individual" + evmAddress: 0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe }); return ZkSyncGuardianCompensation2024_2025.Config({ @@ -45,41 +41,23 @@ library ZkSyncGuardianCompensationSepolia2024_2025 { // zkSync Guardians guardianSafe: guardianSafe, - guardianSafeInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: defaultConfig.guardianSafeInfo.name, - evmAddress: address(guardianSafe), - contactDetails: defaultConfig.guardianSafeInfo.contactDetails, - _type: defaultConfig.guardianSafeInfo._type + guardianSafeInfoForMetavest: ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ + name: defaultConfig.guardianSafeInfoForMetavest.name, + evmAddress: address(guardianSafe) }), // MetaLeX metalexSafe: metalexSafe, - metalexSafeInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: defaultConfig.metalexSafeInfo.name, - evmAddress: address(metalexSafe), - contactDetails: defaultConfig.metalexSafeInfo.contactDetails, - _type: defaultConfig.metalexSafeInfo._type - }), registry: CyberAgreementRegistry(0x7BD5EBE57e64AA6D9904caE90A192E76d818b49e), vestingAllocationFactory: VestingAllocationFactory(0x3fFd990dB0E398235456A720501E6007003a6cdf), controller: metavestController(0x856A8Aea8a37A338e2490384Bb790cD87b5CaaE4), - // MetaLeX <> zkSync Guardian BORG Service Agreement - - serviceAgreementUri: defaultConfig.serviceAgreementUri, - serviceTemplateName: defaultConfig.serviceTemplateName, - serviceTemplateId: defaultConfig.serviceTemplateId, - serviceGlobalFields: defaultConfig.serviceGlobalFields, - servicePartyFields: defaultConfig.servicePartyFields, - - serviceAgreementExpiry: defaultConfig.serviceAgreementExpiry, - // zkSync Guardian Compensation Agreement compAgreementUri: defaultConfig.compAgreementUri, compTemplateName: defaultConfig.compTemplateName, - compTemplateId: defaultConfig.compTemplateId, + compTemplateId: bytes32(uint256(205)), compGlobalFields: defaultConfig.compGlobalFields, compPartyFields: defaultConfig.compPartyFields, diff --git a/scripts/proposeAllGuardiansMetavestDeals.s.sol b/scripts/proposeAllGuardiansMetavestDeals.s.sol index 512e017..6a05683 100644 --- a/scripts/proposeAllGuardiansMetavestDeals.s.sol +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -52,8 +52,6 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { console2.log("Proposing to Guardian #%d", i + 1); console2.log(" name:", config.guardians[i].name); console2.log(" address:", config.guardians[i].evmAddress); - console2.log(" contact:", config.guardians[i].contactDetails); - console2.log(" type:", config.guardians[i]._type); console2.log(""); agreementIds[i] = run( diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index c026262..1039c88 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -24,11 +24,9 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { function run() public virtual { run( vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), - ZkSyncGuardianCompensation2024_2025.PartyInfo({ + ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ name: "Alice", - evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b, - contactDetails: "email@company.com", - _type: "individual" + evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b }), ZkSyncGuardianCompensation2024_2025.getDefault() ); @@ -37,7 +35,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running in tests function run( uint256 proposerPrivateKey, - ZkSyncGuardianCompensation2024_2025.PartyInfo memory guardianInfo, + ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo memory guardianInfo, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { return run( @@ -51,7 +49,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running in tests function run( uint256 proposerPrivateKey, - ZkSyncGuardianCompensation2024_2025.PartyInfo memory guardianInfo, + ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo memory guardianInfo, BaseAllocation.Allocation memory allocation, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { @@ -80,9 +78,9 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { parties[1] = guardianInfo.evmAddress; string[] memory globalValues = config.formatCompGlobalValues(vm, guardianInfo.evmAddress); - string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues( + string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025.formatMetaVestPartyValues( vm, - config.guardianSafeInfo, + config.guardianSafeInfoForMetavest, guardianInfo ); diff --git a/scripts/proposeServiceAgreement.s.sol b/scripts/proposeServiceAgreement.s.sol deleted file mode 100644 index 730aab5..0000000 --- a/scripts/proposeServiceAgreement.s.sol +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.24; - -import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; -import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; -import {BaseAllocation} from "../src/BaseAllocation.sol"; -import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; -import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; -import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreementUtils.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; -import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; -import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; -import {Script} from "forge-std/Script.sol"; -import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; -import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; -import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import {console2} from "forge-std/console2.sol"; -import {metavestController} from "../src/MetaVesTController.sol"; - -contract ProposeServiceAgreementScript is SafeTxHelper, Script { - using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; - - /// @dev For running from `forge script`. Provide the deployer private key through env var. - function run() public virtual { - run( - vm.envUint("METALEX_SAFE_DELEGATE_PRIVATE_KEY"), - - // zkSync Era -// ZkSyncGuardianCompensation2024_2025.getDefault() - - // zkSync Sepolia - ZkSyncGuardianCompensationSepolia2024_2025.getDefault() - ); - } - - /// @dev For running in tests - function run( - uint256 proposerPrivateKey, - ZkSyncGuardianCompensation2024_2025.Config memory config - ) public virtual returns(bytes32) { - - address metalexProposer = vm.addr(proposerPrivateKey); - - console2.log(""); - console2.log("=== ProposeServiceAgreementScript ==="); - console2.log("MetaLeX proposer: ", address(metalexProposer)); - console2.log("Guardian Safe: ", address(config.guardianSafe)); - console2.log("CyberAgreementRegistry: ", address(config.registry)); - console2.log(""); - - // Assume Guardian SAFE already delegate signing to the deployer - - // Propose a new deal - - address[] memory parties = new address[](2); - parties[0] = address(config.metalexSafe); - parties[1] = address(config.guardianSafe); - - string[] memory globalValues = ZkSyncGuardianCompensation2024_2025.formatServiceGlobalValues(vm, config.serviceAgreementExpiry); - string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, config.metalexSafeInfo, config.guardianSafeInfo); - - uint256 agreementSalt = block.timestamp; - - bytes32 expectedContractId = keccak256( - abi.encode( - config.serviceTemplateId, - agreementSalt, // salt, - globalValues, - parties - ) - ); - - bytes memory signature = CyberAgreementUtils.signAgreementTypedData( - vm, - config.registry.DOMAIN_SEPARATOR(), - config.registry.SIGNATUREDATA_TYPEHASH(), - expectedContractId, - config.serviceAgreementUri, - config.serviceGlobalFields, - config.servicePartyFields, - globalValues, - partyValues[0], - proposerPrivateKey - ); - - vm.startBroadcast(proposerPrivateKey); - - bytes32 contractId = config.registry.createContract( - config.serviceTemplateId, - agreementSalt, - globalValues, - parties, - partyValues, - bytes32(0), // no secrets - address(0), // no finalizer - config.serviceAgreementExpiry - ); - - config.registry.signContract( - contractId, - partyValues[0], - signature, - false, // fillUnallocated - "" // no secrets - ); - - vm.stopBroadcast(); - - console2.log("Created:"); - console2.log(" Agreement ID:"); - console2.logBytes32(contractId); - console2.log(""); - - return contractId; - } -} diff --git a/scripts/signDealAndCreateMetavest.s.sol b/scripts/signDealAndCreateMetavest.s.sol index 35bfe1d..f5d6bef 100644 --- a/scripts/signDealAndCreateMetavest.s.sol +++ b/scripts/signDealAndCreateMetavest.s.sol @@ -27,22 +27,18 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { run( // granteePrivateKey, // 0x0000000000000000000000000000000000000000000000000000000000000000, // TODO TBD -// ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD +// ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ // TODO TBD // name: "Alice", -// evmAddress: vm.addr(granteePrivateKey), -// contactDetails: "email@company.com", -// _type: "individual" +// evmAddress: vm.addr(granteePrivateKey) // }), // ZkSyncGuardianCompensation2024_2025.getDefault() // zkSync Sepolia granteePrivateKey, 0xd0d7610ca18b8a76a36c7e1241929641c06cce69ddc1161beebe69b72dae6cbf, - ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD + ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ // TODO TBD name: "Alice", - evmAddress: vm.addr(granteePrivateKey), - contactDetails: "alice@email.com", - _type: "individual" + evmAddress: vm.addr(granteePrivateKey) }), ZkSyncGuardianCompensationSepolia2024_2025.getDefault() ); @@ -52,7 +48,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { function run( uint256 granteePrivateKey, bytes32 agreementId, - ZkSyncGuardianCompensation2024_2025.PartyInfo memory granteeInfo, + ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo memory granteeInfo, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(address) { @@ -72,7 +68,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { // Sign the deal and create MetaVesT - string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, granteeInfo); + string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025.formatMetaVestPartyValues(vm, granteeInfo); bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, config.registry.DOMAIN_SEPARATOR(), diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 789e235..c0ebbe4 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -11,7 +11,6 @@ import {console2} from "forge-std/console2.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; import {DeployZkSyncGuardianCompensationPrerequisitesScript} from "../scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol"; import {DeployZkSyncGuardianCompensationScript} from "../scripts/deployZkSyncGuardianCompensation.s.sol"; -import {ProposeServiceAgreementScript} from "../scripts/proposeServiceAgreement.s.sol"; import {ProposeMetaVestDealScript} from "../scripts/proposeMetavestDeal.s.sol"; import {SignDealAndCreateMetavestScript} from "../scripts/signDealAndCreateMetavest.s.sol"; import {ZkSyncGuardianCompensation2024_2025} from "../scripts/lib/ZkSyncGuardianCompensation2024_2025.sol"; @@ -22,7 +21,6 @@ import {GnosisTransaction} from "./lib/safe.sol"; contract ZkSyncGuardianCompensationTest is DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensationScript, - ProposeServiceAgreementScript, ProposeMetaVestDealScript, SignDealAndCreateMetavestScript, Test @@ -104,6 +102,7 @@ contract ZkSyncGuardianCompensationTest is config2025_2026.controller = controller; // Update configs with deployed contracts // Simulate Guardian SAFE to execute txs as instructed + for (uint256 i = 0; i < safeTxs2024_2025.length; i++) { vm.prank(address(config2024_2025.guardianSafe)); (safeTxs2024_2025[i].to).call{value: safeTxs2024_2025[i].value}(safeTxs2024_2025[i].data); @@ -113,6 +112,20 @@ contract ZkSyncGuardianCompensationTest is (safeTxs2025_2026[i].to).call{value: safeTxs2025_2026[i].value}(safeTxs2025_2026[i].data); } + // Simulate MetaLeX SAFE create templates for Guardian Compensation and Service Agreement + + vm.startPrank(address(config2024_2025.metalexSafe)); + + registry.createTemplate( + config2024_2025.compTemplateId, + config2024_2025.compTemplateName, + config2024_2025.compAgreementUri, + config2024_2025.compGlobalFields, + config2024_2025.compPartyFields + ); + + vm.stopPrank(); + // Simulate vote pass (https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746) vm.startPrank(zkTokenAdmin); @@ -130,7 +143,6 @@ contract ZkSyncGuardianCompensationTest is function run() public override( DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensationScript, - ProposeServiceAgreementScript, ProposeMetaVestDealScript, SignDealAndCreateMetavestScript ) { @@ -156,14 +168,6 @@ contract ZkSyncGuardianCompensationTest is config2024_2025.compGlobalFields, config2024_2025.compPartyFields ); - _assertTemplate( - config2024_2025.registry, - config2024_2025.serviceTemplateId, - config2024_2025.serviceAgreementUri, - config2024_2025.serviceTemplateName, - config2024_2025.serviceGlobalFields, - config2024_2025.servicePartyFields - ); // MetaVesT deployments @@ -182,31 +186,6 @@ contract ZkSyncGuardianCompensationTest is vm.assertTrue(config2025_2026.zkCappedMinter.hasRole(config2025_2026.zkCappedMinter.MINTER_ROLE(), address(config2025_2026.controller)), "2025-2026 ZK Capped Minter should grant MetaVesTController MINTER role"); } - function test_ProposeServiceAgreement() public { - // Simulate MetaLeX SAFE delegation - vm.prank(address(config2024_2025.metalexSafe)); - config2024_2025.registry.setDelegation(metalexDelegate, block.timestamp + 60); - assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.metalexSafe), metalexDelegate), "should be MetaLeX SAFE's delegate"); - - // Simulate MetaLeX delegate proposing and signing agreement - bytes32 agreementId = ProposeServiceAgreementScript.run( - metalexDelegatePrivateKey, - config2024_2025 - ); - - // Verify agreement - - (bytes32 templateId, , , , , , uint256 expiry) = config2024_2025.registry.agreements(agreementId); - assertEq(templateId, config2024_2025.serviceTemplateId, "Unexpected service agreement template ID"); - assertEq(expiry, config2024_2025.serviceAgreementExpiry, "Unexpected service agreement expiry"); - - (, , , , , address[] memory parties, , , , ) = config2024_2025.registry.getContractDetails(agreementId); - vm.assertEq(parties[0], address(config2024_2025.metalexSafe), "First party should be MetaLeX SAFE"); - vm.assertEq(parties[1], address(config2024_2025.guardianSafe), "Second party should be Guardian SAFE"); - - vm.assertTrue(config2024_2025.registry.hasSigned(agreementId, metalexDelegate), "Should signed by MetaLeX delegate"); - } - function test_GuardianCompensation() public { (address metavestAddressAlice2024_2025, address metavestAddressAlice2025_2026) = _proposeAndFinalizeAllGuardianDeals(); @@ -249,11 +228,9 @@ contract ZkSyncGuardianCompensationTest is config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "should be Guardian SAFE's delegate"); - ZkSyncGuardianCompensation2024_2025.PartyInfo memory chadInfo = ZkSyncGuardianCompensation2024_2025.PartyInfo({ + ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo memory chadInfo = ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ name: "Chad", - evmAddress: chad, - contactDetails: "chad@email.com", - _type: "individual" + evmAddress: chad }); bytes32 contractIdChad = ProposeMetaVestDealScript.run( guardianDelegatePrivateKey, @@ -288,11 +265,9 @@ contract ZkSyncGuardianCompensationTest is // Run scripts to propose deals - ZkSyncGuardianCompensation2024_2025.PartyInfo memory aliceInfo = ZkSyncGuardianCompensation2024_2025.PartyInfo({ + ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo memory aliceInfo = ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ name: "Alice", - evmAddress: alice, - contactDetails: "alice@email.com", - _type: "individual" + evmAddress: alice }); bytes32 contractIdAlice2024_2025 = ProposeMetaVestDealScript.run( From 6019fa8e796fd837599583424166dbf75871732a Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 28 Aug 2025 15:26:25 -0700 Subject: [PATCH 50/68] feat: adjust agreement expiry per request --- scripts/proposeMetavestDeal.s.sol | 2 +- .../zk-governance/IZkCappedMinterV2.sol | 1 + test/ZkSyncGuardianCompensation.t.sol | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index 1039c88..322b53a 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -122,7 +122,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { partyValues, signature, bytes32(0), // no secrets - block.timestamp + 30 days * 2 + block.timestamp + 365 days * 2 // 2 years after deployment ); vm.stopBroadcast(); diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2.sol b/src/interfaces/zk-governance/IZkCappedMinterV2.sol index 683550d..522fc8d 100644 --- a/src/interfaces/zk-governance/IZkCappedMinterV2.sol +++ b/src/interfaces/zk-governance/IZkCappedMinterV2.sol @@ -10,6 +10,7 @@ interface IZkCappedMinterV2 { function MINTER_ROLE() external returns (bytes32); function PAUSER_ROLE() external returns (bytes32); function START_TIME() external returns (uint48); + function EXPIRATION_TIME() external returns (uint48); function grantRole(bytes32 role, address account) external; function hasRole(bytes32 role, address account) external view returns (bool); diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index c0ebbe4..0430db2 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -186,6 +186,26 @@ contract ZkSyncGuardianCompensationTest is vm.assertTrue(config2025_2026.zkCappedMinter.hasRole(config2025_2026.zkCappedMinter.MINTER_ROLE(), address(config2025_2026.controller)), "2025-2026 ZK Capped Minter should grant MetaVesTController MINTER role"); } + function test_AgreementDeadline() public { + // Guardian SAFE to delegate signing to an EOA + vm.prank(address(config2024_2025.guardianSafe)); + config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); + assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); + + // Run scripts to propose deals + bytes32 agreementId = ProposeMetaVestDealScript.run( + guardianDelegatePrivateKey, + ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ + name: "Alice", + evmAddress: alice + }), + config2024_2025 + ); + + (, , , , , , uint256 agreementExpiry) = config2024_2025.registry.agreements(agreementId); + assertGt(agreementExpiry, config2024_2025.zkCappedMinter.EXPIRATION_TIME(), "Agreement expiry should be later than the minter's"); + } + function test_GuardianCompensation() public { (address metavestAddressAlice2024_2025, address metavestAddressAlice2025_2026) = _proposeAndFinalizeAllGuardianDeals(); From 7e0cfeb3ea31be2640afffbcd4d53e63a760de51 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 28 Aug 2025 16:19:00 -0700 Subject: [PATCH 51/68] chore: deploy production pre-requisites --- ...deployZkSyncGuardianCompensationPrerequisites.s.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol index 6a738fd..be2297d 100644 --- a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol +++ b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol @@ -26,12 +26,12 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Sc vm.envUint("DEPLOYER_PRIVATE_KEY"), // zkSync Era -// "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0", -// ZkSyncGuardianCompensation2024_2025.getDefault() + "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0", + ZkSyncGuardianCompensation2024_2025.getDefault() - // zkSync Sepolia - "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.1", - ZkSyncGuardianCompensationSepolia2024_2025.getDefault() +// // zkSync Sepolia +// "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.1", +// ZkSyncGuardianCompensationSepolia2024_2025.getDefault() ); } From 943992337d3b2b28a3e850b4ecf77faa44d2f140 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 28 Aug 2025 22:02:47 -0700 Subject: [PATCH 52/68] feat: add BORG Resolution proposal --- .../ZkSyncGuardianCompensation2024_2025.sol | 61 ++++++---- .../ZkSyncGuardianCompensation2025_2026.sol | 10 +- ...ncGuardianCompensationSepolia2024_2025.sol | 20 +++- scripts/proposeBorgResolution.s.sol | 109 ++++++++++++++++++ scripts/proposeMetavestDeal.s.sol | 10 +- scripts/signDealAndCreateMetavest.s.sol | 8 +- test/ZkSyncGuardianCompensation.t.sol | 49 +++++++- 7 files changed, 227 insertions(+), 40 deletions(-) create mode 100644 scripts/proposeBorgResolution.s.sol diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol index 48780ae..c9e9b2c 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -24,7 +24,7 @@ library ZkSyncGuardianCompensation2024_2025 { // zkSync Guardians IGnosisSafe guardianSafe; - MetavestPartyInfo guardianSafeInfoForMetavest; + PartyInfo guardianSafeInfo; // MetaLeX @@ -33,6 +33,14 @@ library ZkSyncGuardianCompensation2024_2025 { VestingAllocationFactory vestingAllocationFactory; metavestController controller; + // zkSync Guardian BORG Resolution + + string borgResolutionUri; + string borgResolutionTemplateName; + bytes32 borgResolutionTemplateId; + string[] borgResolutionGlobalFields; + string[] borgResolutionPartyFields; + // zkSync Guardian Compensation Agreement string compAgreementUri; @@ -41,18 +49,24 @@ library ZkSyncGuardianCompensation2024_2025 { string[] compGlobalFields; string[] compPartyFields; - MetavestPartyInfo[] guardians; + PartyInfo[] guardians; uint256 fixedAnnualCompensation; uint48 metavestVestingAndUnlockStartTime; BaseAllocation.Milestone[] milestones; } - struct MetavestPartyInfo { + struct PartyInfo { string name; address evmAddress; } function getDefault() internal view returns(Config memory) { + string[] memory borgResolutionGlobalFields = new string[](0); + + string[] memory borgResolutionPartyFields = new string[](2); + borgResolutionPartyFields[0] = "name"; + borgResolutionPartyFields[1] = "evmAddress"; + string[] memory compGlobalFields = new string[](11); compGlobalFields[0] = "metavestType"; compGlobalFields[1] = "grantor"; @@ -73,7 +87,7 @@ library ZkSyncGuardianCompensation2024_2025 { IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); IGnosisSafe metalexSafe = IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc); - MetavestPartyInfo[] memory guardians = new MetavestPartyInfo[](0); // TODO TBD + PartyInfo[] memory guardians = new PartyInfo[](0); // TODO TBD return Config({ @@ -86,8 +100,8 @@ library ZkSyncGuardianCompensation2024_2025 { // zkSync Guardians guardianSafe: guardianSafe, - guardianSafeInfoForMetavest: MetavestPartyInfo({ - name: "Guardian BORG", + guardianSafeInfo: PartyInfo({ + name: "ZKsync Guardians", evmAddress: address(guardianSafe) }), @@ -98,11 +112,20 @@ library ZkSyncGuardianCompensation2024_2025 { vestingAllocationFactory: VestingAllocationFactory(address(0)), // TODO TBD controller: metavestController(address(0)), // TODO TBD + // zkSync Guardian BORG Resolution + + borgResolutionUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD + borgResolutionTemplateName: "ZKsync Guardians Board Resolutions Compensation Amendments Transmission", + borgResolutionTemplateId: bytes32(uint256(200)), + borgResolutionGlobalFields: borgResolutionGlobalFields, + borgResolutionPartyFields: borgResolutionPartyFields, + // zkSync Guardian Compensation Agreement + // TODO WIP compAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD - compTemplateName: "zkSync Guardian Compensation Agreement", // TODO TBD - compTemplateId: bytes32(uint256(200)), + compTemplateName: "Guardians Compensation Amendment", + compTemplateId: bytes32(uint256(201)), compGlobalFields: compGlobalFields, compPartyFields: compPartyFields, @@ -136,7 +159,7 @@ library ZkSyncGuardianCompensation2024_2025 { string[] memory globalValues = new string[](11); globalValues[0] = "0"; // metavestType: Vesting - globalValues[1] = vm.toString(config.guardianSafeInfoForMetavest.evmAddress); // grantor + globalValues[1] = vm.toString(config.guardianSafeInfo.evmAddress); // grantor globalValues[2] = vm.toString(grantee); // grantee globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) @@ -149,15 +172,13 @@ library ZkSyncGuardianCompensation2024_2025 { return globalValues; } - function formatServiceGlobalValues(Vm vm, uint256 expiry) internal view returns(string[] memory) { - string[] memory globalValues = new string[](1); - globalValues[0] = vm.toString(expiry); - return globalValues; + function formatBorgResolutionGlobalValues(Vm vm) internal view returns(string[] memory) { + return new string[](0); } - function formatMetaVestPartyValues( + function formatPartyValues( Vm vm, - MetavestPartyInfo memory partyInfo + PartyInfo memory partyInfo ) internal view returns(string[] memory) { string[] memory partyValues = new string[](2); partyValues[0] = partyInfo.name; @@ -165,14 +186,14 @@ library ZkSyncGuardianCompensation2024_2025 { return partyValues; } - function formatMetaVestPartyValues( + function formatPartyValues( Vm vm, - MetavestPartyInfo memory guardianSafeInfo, - MetavestPartyInfo memory guardianInfo + PartyInfo memory guardianSafeInfo, + PartyInfo memory guardianInfo ) internal view returns(string[][] memory) { string[][] memory partyValues = new string[][](2); - partyValues[0] = formatMetaVestPartyValues(vm, guardianSafeInfo); - partyValues[1] = formatMetaVestPartyValues(vm, guardianInfo); + partyValues[0] = formatPartyValues(vm, guardianSafeInfo); + partyValues[1] = formatPartyValues(vm, guardianInfo); return partyValues; } } diff --git a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol index 6bb6501..690164b 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol @@ -29,7 +29,7 @@ library ZkSyncGuardianCompensation2025_2026 { // zkSync Guardians guardianSafe: defaultConfig.guardianSafe, - guardianSafeInfoForMetavest: defaultConfig.guardianSafeInfoForMetavest, + guardianSafeInfo: defaultConfig.guardianSafeInfo, // MetaLeX @@ -38,6 +38,14 @@ library ZkSyncGuardianCompensation2025_2026 { vestingAllocationFactory: defaultConfig.vestingAllocationFactory, controller: defaultConfig.controller, + // zkSync Guardian BORG Resolution + + borgResolutionUri: defaultConfig.borgResolutionUri, + borgResolutionTemplateName: defaultConfig.borgResolutionTemplateName, + borgResolutionTemplateId: defaultConfig.borgResolutionTemplateId, + borgResolutionGlobalFields: defaultConfig.borgResolutionGlobalFields, + borgResolutionPartyFields: defaultConfig.borgResolutionPartyFields, + // zkSync Guardian Compensation Agreement compAgreementUri: defaultConfig.compAgreementUri, diff --git a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol index 1eb55b6..8302ae0 100644 --- a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol @@ -21,12 +21,12 @@ library ZkSyncGuardianCompensationSepolia2024_2025 { IGnosisSafe guardianSafe = IGnosisSafe(0x3C785F96864002eB47bDe32d597476a3D97fCd15); IGnosisSafe metalexSafe = IGnosisSafe(0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe); // This is faked by EOA - ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo[] memory guardians = new ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo[](2); - guardians[0] = ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ + ZkSyncGuardianCompensation2024_2025.PartyInfo[] memory guardians = new ZkSyncGuardianCompensation2024_2025.PartyInfo[](2); + guardians[0] = ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Alice", evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b }); - guardians[1] = ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ + guardians[1] = ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Bob", evmAddress: 0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe }); @@ -41,8 +41,8 @@ library ZkSyncGuardianCompensationSepolia2024_2025 { // zkSync Guardians guardianSafe: guardianSafe, - guardianSafeInfoForMetavest: ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ - name: defaultConfig.guardianSafeInfoForMetavest.name, + guardianSafeInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: defaultConfig.guardianSafeInfo.name, evmAddress: address(guardianSafe) }), @@ -53,11 +53,19 @@ library ZkSyncGuardianCompensationSepolia2024_2025 { vestingAllocationFactory: VestingAllocationFactory(0x3fFd990dB0E398235456A720501E6007003a6cdf), controller: metavestController(0x856A8Aea8a37A338e2490384Bb790cD87b5CaaE4), + // zkSync Guardian BORG Resolution + + borgResolutionUri: defaultConfig.borgResolutionUri, + borgResolutionTemplateName: defaultConfig.borgResolutionTemplateName, + borgResolutionTemplateId: bytes32(uint256(206)), + borgResolutionGlobalFields: defaultConfig.borgResolutionGlobalFields, + borgResolutionPartyFields: defaultConfig.borgResolutionPartyFields, + // zkSync Guardian Compensation Agreement compAgreementUri: defaultConfig.compAgreementUri, compTemplateName: defaultConfig.compTemplateName, - compTemplateId: bytes32(uint256(205)), + compTemplateId: bytes32(uint256(207)), compGlobalFields: defaultConfig.compGlobalFields, compPartyFields: defaultConfig.compPartyFields, diff --git a/scripts/proposeBorgResolution.s.sol b/scripts/proposeBorgResolution.s.sol new file mode 100644 index 0000000..81ab868 --- /dev/null +++ b/scripts/proposeBorgResolution.s.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreementUtils.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract ProposeBorgResolutionScript is SafeTxHelper, Script { + using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; + + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), + + // zkSync Era +// ZkSyncGuardianCompensation2024_2025.getDefault() + + // zkSync Sepolia + ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + ); + } + + /// @dev For running in tests + function run( + uint256 proposerPrivateKey, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns(bytes32) { + + address metalexProposer = vm.addr(proposerPrivateKey); + + console2.log(""); + console2.log("=== ProposeServiceAgreementScript ==="); + console2.log("MetaLeX proposer: ", address(metalexProposer)); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log(""); + + // Assume Guardian SAFE already delegate signing to the deployer + + // Propose a new deal + + address[] memory parties = new address[](1); + parties[0] = address(config.guardianSafe); + + string[] memory globalValues = ZkSyncGuardianCompensation2024_2025.formatBorgResolutionGlobalValues(vm); + string[][] memory partyValues = new string[][](1); + partyValues[0] = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, config.guardianSafeInfo); + + uint256 agreementSalt = block.timestamp; + + bytes32 expectedContractId = keccak256( + abi.encode( + config.borgResolutionTemplateId, + agreementSalt, // salt, + globalValues, + parties + ) + ); + + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( + vm, + config.registry.DOMAIN_SEPARATOR(), + config.registry.SIGNATUREDATA_TYPEHASH(), + expectedContractId, + config.borgResolutionUri, + config.borgResolutionGlobalFields, + config.borgResolutionPartyFields, + globalValues, + partyValues[0], + proposerPrivateKey + ); + + vm.startBroadcast(proposerPrivateKey); + + bytes32 contractId = config.registry.createContract( + config.borgResolutionTemplateId, + agreementSalt, + globalValues, + parties, + partyValues, + bytes32(0), // no secrets + address(0), // no finalizer + block.timestamp + 365 days * 2 // 2 years after deployment + ); + + vm.stopBroadcast(); + + console2.log("Created:"); + console2.log(" Agreement ID:"); + console2.logBytes32(contractId); + console2.log(""); + + return contractId; + } +} diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index 322b53a..230ef62 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -24,7 +24,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { function run() public virtual { run( vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), - ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ + ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Alice", evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b }), @@ -35,7 +35,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running in tests function run( uint256 proposerPrivateKey, - ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo memory guardianInfo, + ZkSyncGuardianCompensation2024_2025.PartyInfo memory guardianInfo, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { return run( @@ -49,7 +49,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running in tests function run( uint256 proposerPrivateKey, - ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo memory guardianInfo, + ZkSyncGuardianCompensation2024_2025.PartyInfo memory guardianInfo, BaseAllocation.Allocation memory allocation, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { @@ -78,9 +78,9 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { parties[1] = guardianInfo.evmAddress; string[] memory globalValues = config.formatCompGlobalValues(vm, guardianInfo.evmAddress); - string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025.formatMetaVestPartyValues( + string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues( vm, - config.guardianSafeInfoForMetavest, + config.guardianSafeInfo, guardianInfo ); diff --git a/scripts/signDealAndCreateMetavest.s.sol b/scripts/signDealAndCreateMetavest.s.sol index f5d6bef..fdd124c 100644 --- a/scripts/signDealAndCreateMetavest.s.sol +++ b/scripts/signDealAndCreateMetavest.s.sol @@ -27,7 +27,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { run( // granteePrivateKey, // 0x0000000000000000000000000000000000000000000000000000000000000000, // TODO TBD -// ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ // TODO TBD +// ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD // name: "Alice", // evmAddress: vm.addr(granteePrivateKey) // }), @@ -36,7 +36,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { // zkSync Sepolia granteePrivateKey, 0xd0d7610ca18b8a76a36c7e1241929641c06cce69ddc1161beebe69b72dae6cbf, - ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ // TODO TBD + ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD name: "Alice", evmAddress: vm.addr(granteePrivateKey) }), @@ -48,7 +48,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { function run( uint256 granteePrivateKey, bytes32 agreementId, - ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo memory granteeInfo, + ZkSyncGuardianCompensation2024_2025.PartyInfo memory granteeInfo, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(address) { @@ -68,7 +68,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { // Sign the deal and create MetaVesT - string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025.formatMetaVestPartyValues(vm, granteeInfo); + string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, granteeInfo); bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, config.registry.DOMAIN_SEPARATOR(), diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 0430db2..69414ec 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -11,6 +11,7 @@ import {console2} from "forge-std/console2.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; import {DeployZkSyncGuardianCompensationPrerequisitesScript} from "../scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol"; import {DeployZkSyncGuardianCompensationScript} from "../scripts/deployZkSyncGuardianCompensation.s.sol"; +import {ProposeBorgResolutionScript} from "../scripts/proposeBorgResolution.s.sol"; import {ProposeMetaVestDealScript} from "../scripts/proposeMetavestDeal.s.sol"; import {SignDealAndCreateMetavestScript} from "../scripts/signDealAndCreateMetavest.s.sol"; import {ZkSyncGuardianCompensation2024_2025} from "../scripts/lib/ZkSyncGuardianCompensation2024_2025.sol"; @@ -21,6 +22,7 @@ import {GnosisTransaction} from "./lib/safe.sol"; contract ZkSyncGuardianCompensationTest is DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensationScript, + ProposeBorgResolutionScript, ProposeMetaVestDealScript, SignDealAndCreateMetavestScript, Test @@ -115,7 +117,15 @@ contract ZkSyncGuardianCompensationTest is // Simulate MetaLeX SAFE create templates for Guardian Compensation and Service Agreement vm.startPrank(address(config2024_2025.metalexSafe)); - + + registry.createTemplate( + config2024_2025.borgResolutionTemplateId, + config2024_2025.borgResolutionTemplateName, + config2024_2025.borgResolutionUri, + config2024_2025.borgResolutionGlobalFields, + config2024_2025.borgResolutionPartyFields + ); + registry.createTemplate( config2024_2025.compTemplateId, config2024_2025.compTemplateName, @@ -143,6 +153,7 @@ contract ZkSyncGuardianCompensationTest is function run() public override( DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensationScript, + ProposeBorgResolutionScript, ProposeMetaVestDealScript, SignDealAndCreateMetavestScript ) { @@ -168,6 +179,14 @@ contract ZkSyncGuardianCompensationTest is config2024_2025.compGlobalFields, config2024_2025.compPartyFields ); + _assertTemplate( + config2024_2025.registry, + config2024_2025.borgResolutionTemplateId, + config2024_2025.borgResolutionUri, + config2024_2025.borgResolutionTemplateName, + config2024_2025.borgResolutionGlobalFields, + config2024_2025.borgResolutionPartyFields + ); // MetaVesT deployments @@ -195,7 +214,7 @@ contract ZkSyncGuardianCompensationTest is // Run scripts to propose deals bytes32 agreementId = ProposeMetaVestDealScript.run( guardianDelegatePrivateKey, - ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ + ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Alice", evmAddress: alice }), @@ -206,6 +225,28 @@ contract ZkSyncGuardianCompensationTest is assertGt(agreementExpiry, config2024_2025.zkCappedMinter.EXPIRATION_TIME(), "Agreement expiry should be later than the minter's"); } + function test_ProposeBorgResolution() public { + // Simulate Guardian SAFE delegation + vm.prank(address(config2024_2025.guardianSafe)); + config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); + assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "should be MetaLeX SAFE's delegate"); + + // Simulate MetaLeX delegate proposing and signing agreement + bytes32 agreementId = ProposeBorgResolutionScript.run( + guardianDelegatePrivateKey, + config2024_2025 + ); + + // Verify agreement + + (bytes32 templateId, , , , , , uint256 expiry) = config2024_2025.registry.agreements(agreementId); + assertEq(templateId, config2024_2025.borgResolutionTemplateId, "Unexpected borg Resolution template ID"); + + (, , , , , address[] memory parties, , , , ) = config2024_2025.registry.getContractDetails(agreementId); + vm.assertEq(parties.length, 1, "Should be single-party"); + vm.assertEq(parties[0], address(config2024_2025.guardianSafe), "First party should be Guardian SAFE"); + } + function test_GuardianCompensation() public { (address metavestAddressAlice2024_2025, address metavestAddressAlice2025_2026) = _proposeAndFinalizeAllGuardianDeals(); @@ -248,7 +289,7 @@ contract ZkSyncGuardianCompensationTest is config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "should be Guardian SAFE's delegate"); - ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo memory chadInfo = ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ + ZkSyncGuardianCompensation2024_2025.PartyInfo memory chadInfo = ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Chad", evmAddress: chad }); @@ -285,7 +326,7 @@ contract ZkSyncGuardianCompensationTest is // Run scripts to propose deals - ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo memory aliceInfo = ZkSyncGuardianCompensation2024_2025.MetavestPartyInfo({ + ZkSyncGuardianCompensation2024_2025.PartyInfo memory aliceInfo = ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Alice", evmAddress: alice }); From af331ba2e7c3a9123459615066cbb8e363241ba1 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 28 Aug 2025 22:58:45 -0700 Subject: [PATCH 53/68] chore: handle guardian-specific template ID in deployment --- .../ZkSyncGuardianCompensation2024_2025.sol | 9 +++++-- ...ncGuardianCompensationSepolia2024_2025.sol | 20 +++++++++----- scripts/proposeMetavestDeal.s.sol | 25 ++++++++++------- scripts/signDealAndCreateMetavest.s.sol | 27 +++++++++++-------- test/ZkSyncGuardianCompensation.t.sol | 27 ++++++++++++------- 5 files changed, 69 insertions(+), 39 deletions(-) diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol index c9e9b2c..2b2a043 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -49,7 +49,7 @@ library ZkSyncGuardianCompensation2024_2025 { string[] compGlobalFields; string[] compPartyFields; - PartyInfo[] guardians; + GuardianCompInfo[] guardians; uint256 fixedAnnualCompensation; uint48 metavestVestingAndUnlockStartTime; BaseAllocation.Milestone[] milestones; @@ -59,6 +59,11 @@ library ZkSyncGuardianCompensation2024_2025 { string name; address evmAddress; } + + struct GuardianCompInfo { + bytes32 compTemplateId; + PartyInfo partyInfo; + } function getDefault() internal view returns(Config memory) { string[] memory borgResolutionGlobalFields = new string[](0); @@ -87,7 +92,7 @@ library ZkSyncGuardianCompensation2024_2025 { IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); IGnosisSafe metalexSafe = IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc); - PartyInfo[] memory guardians = new PartyInfo[](0); // TODO TBD + GuardianCompInfo[] memory guardians = new GuardianCompInfo[](6); // TBD return Config({ diff --git a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol index 8302ae0..76c7564 100644 --- a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol @@ -21,14 +21,20 @@ library ZkSyncGuardianCompensationSepolia2024_2025 { IGnosisSafe guardianSafe = IGnosisSafe(0x3C785F96864002eB47bDe32d597476a3D97fCd15); IGnosisSafe metalexSafe = IGnosisSafe(0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe); // This is faked by EOA - ZkSyncGuardianCompensation2024_2025.PartyInfo[] memory guardians = new ZkSyncGuardianCompensation2024_2025.PartyInfo[](2); - guardians[0] = ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Alice", - evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[] memory guardians = new ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[](2); + guardians[0] = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ + compTemplateId: defaultConfig.compTemplateId, + partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: "Alice", + evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b + }) }); - guardians[1] = ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Bob", - evmAddress: 0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe + guardians[1] = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ + compTemplateId: defaultConfig.compTemplateId, + partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: "Bob", + evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b + }) }); return ZkSyncGuardianCompensation2024_2025.Config({ diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index 230ef62..af4817e 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -24,9 +24,12 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { function run() public virtual { run( vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), - ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Alice", - evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ + compTemplateId: bytes32(uint256(201)), + partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: "Alice", + evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b + }) }), ZkSyncGuardianCompensation2024_2025.getDefault() ); @@ -35,7 +38,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running in tests function run( uint256 proposerPrivateKey, - ZkSyncGuardianCompensation2024_2025.PartyInfo memory guardianInfo, + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { return run( @@ -49,7 +52,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running in tests function run( uint256 proposerPrivateKey, - ZkSyncGuardianCompensation2024_2025.PartyInfo memory guardianInfo, + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, BaseAllocation.Allocation memory allocation, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { @@ -75,13 +78,13 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { address[] memory parties = new address[](2); parties[0] = address(config.guardianSafe); - parties[1] = guardianInfo.evmAddress; + parties[1] = guardianInfo.partyInfo.evmAddress; - string[] memory globalValues = config.formatCompGlobalValues(vm, guardianInfo.evmAddress); + string[] memory globalValues = config.formatCompGlobalValues(vm, guardianInfo.partyInfo.evmAddress); string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues( vm, config.guardianSafeInfo, - guardianInfo + guardianInfo.partyInfo ); uint256 agreementSalt = block.timestamp; @@ -95,12 +98,14 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { ) ); + (string memory agreementUri, ) = config.registry.templates(guardianInfo.compTemplateId); + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, config.registry.DOMAIN_SEPARATOR(), config.registry.SIGNATUREDATA_TYPEHASH(), expectedContractId, - config.compAgreementUri, + agreementUri, config.compGlobalFields, config.compPartyFields, globalValues, @@ -114,7 +119,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { config.compTemplateId, agreementSalt, metavestController.metavestType.Vesting, - guardianInfo.evmAddress, + guardianInfo.partyInfo.evmAddress, allocation, config.milestones, globalValues, diff --git a/scripts/signDealAndCreateMetavest.s.sol b/scripts/signDealAndCreateMetavest.s.sol index fdd124c..7dd69a7 100644 --- a/scripts/signDealAndCreateMetavest.s.sol +++ b/scripts/signDealAndCreateMetavest.s.sol @@ -36,9 +36,12 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { // zkSync Sepolia granteePrivateKey, 0xd0d7610ca18b8a76a36c7e1241929641c06cce69ddc1161beebe69b72dae6cbf, - ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD - name: "Alice", - evmAddress: vm.addr(granteePrivateKey) + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ + compTemplateId: bytes32(uint256(201)), // TODO TBD + partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: "Alice", + evmAddress: vm.addr(granteePrivateKey) + }) }), ZkSyncGuardianCompensationSepolia2024_2025.getDefault() ); @@ -48,7 +51,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { function run( uint256 granteePrivateKey, bytes32 agreementId, - ZkSyncGuardianCompensation2024_2025.PartyInfo memory granteeInfo, + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory granteeInfo, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(address) { @@ -57,8 +60,8 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { console2.log(""); console2.log("=== SignDealAndCreateMetavestScript ==="); console2.log("Signer: ", signer); - console2.log("Grantee: ", granteeInfo.evmAddress); - console2.log("Grantee Name: ", granteeInfo.name); + console2.log("Grantee: ", granteeInfo.partyInfo.evmAddress); + console2.log("Grantee Name: ", granteeInfo.partyInfo.name); console2.log("Guardian Safe: ", address(config.guardianSafe)); console2.log("CyberAgreementRegistry: ", address(config.registry)); console2.log("MetavesTController: ", address(config.controller)); @@ -68,16 +71,18 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { // Sign the deal and create MetaVesT - string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, granteeInfo); + (string memory agreementUri, ) = config.registry.templates(granteeInfo.compTemplateId); + + string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, granteeInfo.partyInfo); bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, config.registry.DOMAIN_SEPARATOR(), config.registry.SIGNATUREDATA_TYPEHASH(), agreementId, - config.compAgreementUri, + agreementUri, config.compGlobalFields, config.compPartyFields, - config.formatCompGlobalValues(vm, granteeInfo.evmAddress), + config.formatCompGlobalValues(vm, granteeInfo.partyInfo.evmAddress), granteePartyValues, granteePrivateKey ); @@ -85,8 +90,8 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { vm.startBroadcast(granteePrivateKey); address metavest = config.controller.signDealAndCreateMetavest( - granteeInfo.evmAddress, - granteeInfo.evmAddress, + granteeInfo.partyInfo.evmAddress, + granteeInfo.partyInfo.evmAddress, agreementId, granteePartyValues, signature, diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 69414ec..77afe91 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -214,9 +214,12 @@ contract ZkSyncGuardianCompensationTest is // Run scripts to propose deals bytes32 agreementId = ProposeMetaVestDealScript.run( guardianDelegatePrivateKey, - ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Alice", - evmAddress: alice + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ + compTemplateId: config2024_2025.compTemplateId, + partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: "Alice", + evmAddress: alice + }) }), config2024_2025 ); @@ -289,9 +292,12 @@ contract ZkSyncGuardianCompensationTest is config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "should be Guardian SAFE's delegate"); - ZkSyncGuardianCompensation2024_2025.PartyInfo memory chadInfo = ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Chad", - evmAddress: chad + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory chadInfo = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ + compTemplateId: config2024_2025.compTemplateId, + partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: "Chad", + evmAddress: chad + }) }); bytes32 contractIdChad = ProposeMetaVestDealScript.run( guardianDelegatePrivateKey, @@ -326,9 +332,12 @@ contract ZkSyncGuardianCompensationTest is // Run scripts to propose deals - ZkSyncGuardianCompensation2024_2025.PartyInfo memory aliceInfo = ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Alice", - evmAddress: alice + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory aliceInfo = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ + compTemplateId: config2024_2025.compTemplateId, + partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: "Alice", + evmAddress: alice + }) }); bytes32 contractIdAlice2024_2025 = ProposeMetaVestDealScript.run( From 6318cb803faf68806b65c8f51534a2dc4a24f639 Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 29 Aug 2025 08:36:01 -0700 Subject: [PATCH 54/68] chore: update production deployed address --- scripts/lib/ZkSyncGuardianCompensation2024_2025.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol index 2b2a043..a8d66f9 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -113,8 +113,8 @@ library ZkSyncGuardianCompensation2024_2025 { // MetaLeX metalexSafe: metalexSafe, - registry: CyberAgreementRegistry(address(0)), // TODO TBD - vestingAllocationFactory: VestingAllocationFactory(address(0)), // TODO TBD + registry: CyberAgreementRegistry(0x07E0a0BeC742f90f7879830bC917E783dA6a6357), + vestingAllocationFactory: VestingAllocationFactory(0xD1c48D8866d81CC0f6567f3E3fbbb8eF48E30D99), controller: metavestController(address(0)), // TODO TBD // zkSync Guardian BORG Resolution From 1f0a38954efb586e91702af014f7e20957356751 Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 29 Aug 2025 09:19:59 -0700 Subject: [PATCH 55/68] chore: update scripts for production template creation --- scripts/createSafeTx.s.sol | 71 +++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/scripts/createSafeTx.s.sol b/scripts/createSafeTx.s.sol index 0d612f8..2faa139 100644 --- a/scripts/createSafeTx.s.sol +++ b/scripts/createSafeTx.s.sol @@ -20,35 +20,66 @@ contract CreateSafeTxScript is SafeTxHelper, Script { function run() public virtual { IGnosisSafe safe; ZkSyncGuardianCompensation2024_2025.Config memory config; - GnosisTransaction[] memory safeTxs = new GnosisTransaction[](2); - // zkSync Sepolia - config = ZkSyncGuardianCompensationSepolia2024_2025.getDefault(); + // zkSync Era (zkSync Guardians) + config = ZkSyncGuardianCompensation2024_2025.getDefault(); safe = config.guardianSafe; + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](7); safeTxs[0] = GnosisTransaction({ to: address(config.registry), value: 0 ether, data: abi.encodeWithSelector( CyberAgreementRegistry.createTemplate.selector, - config.compTemplateId, - config.compTemplateName, - config.compAgreementUri, - config.compGlobalFields, - config.compPartyFields - ) - }); - safeTxs[1] = GnosisTransaction({ - to: address(config.registry), - value: 0 ether, - data: abi.encodeWithSelector( - CyberAgreementRegistry.createTemplate.selector, - config.serviceTemplateId, - config.serviceTemplateName, - config.serviceAgreementUri, - config.serviceGlobalFields, - config.servicePartyFields + vm.envUint("BORG_RESOLUTION_TEMPLATE_ID"), // template ID + vm.envString("BORG_RESOLUTION_TEMPLATE_NAME"), // template name + vm.envString("BORG_RESOLUTION_URI"), // agreement URI + config.borgResolutionGlobalFields, + config.borgResolutionPartyFields ) }); + for (uint i = 0; i < 6 ; i++) { + safeTxs[i + 1] = GnosisTransaction({ + to: address(config.registry), + value: 0 ether, + data: abi.encodeWithSelector( + CyberAgreementRegistry.createTemplate.selector, + vm.envUint(string(abi.encodePacked("GUARDIAN_TEMPLATE_ID_", vm.toString(i)))), // template ID + vm.envString(string(abi.encodePacked("GUARDIAN_TEMPLATE_NAME_", vm.toString(i)))), // template name + vm.envString(string(abi.encodePacked("GUARDIAN_AGREEMENT_URI_", vm.toString(i)))), // agreement URI + config.compGlobalFields, + config.compPartyFields + ) + }); + } + + // zkSync Sepolia +// config = ZkSyncGuardianCompensationSepolia2024_2025.getDefault(); +// safe = config.guardianSafe; +// GnosisTransaction[] memory safeTxs = new GnosisTransaction[](2); +// safeTxs[0] = GnosisTransaction({ +// to: address(config.registry), +// value: 0 ether, +// data: abi.encodeWithSelector( +// CyberAgreementRegistry.createTemplate.selector, +// config.compTemplateId, +// config.compTemplateName, +// config.compAgreementUri, +// config.compGlobalFields, +// config.compPartyFields +// ) +// }); +// safeTxs[1] = GnosisTransaction({ +// to: address(config.registry), +// value: 0 ether, +// data: abi.encodeWithSelector( +// CyberAgreementRegistry.createTemplate.selector, +// config.serviceTemplateId, +// config.serviceTemplateName, +// config.serviceAgreementUri, +// config.serviceGlobalFields, +// config.servicePartyFields +// ) +// }); // Output logs From bdbe3e2fdaf157db47d7848f2eea6cabbcf6d6f2 Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 2 Sep 2025 12:04:01 -0700 Subject: [PATCH 56/68] chore: load guardians and template info from env vars --- scripts/createAllTemplates.s.sol | 82 ++++++++++++ scripts/createSafeTx.s.sol | 4 +- scripts/deployTestZkCappedMinter.s.sol | 4 +- .../deployZkSyncGuardianCompensation.s.sol | 8 +- ...yncGuardianCompensationPrerequisites.s.sol | 4 +- scripts/executeSafeTx.s.sol | 8 +- .../ZkSyncGuardianCompensation2024_2025.sol | 125 ++++++++++-------- .../ZkSyncGuardianCompensation2025_2026.sol | 18 +-- ...ncGuardianCompensationSepolia2024_2025.sol | 34 +---- .../proposeAllGuardiansMetavestDeals.s.sol | 2 +- scripts/proposeBorgResolution.s.sol | 14 +- scripts/proposeMetavestDeal.s.sol | 21 ++- scripts/signDealAndCreateMetavest.s.sol | 19 +-- scripts/voidAgreement.s.sol | 4 +- test/ZkSyncGuardianCompensation.t.sol | 108 ++++++++------- 15 files changed, 262 insertions(+), 193 deletions(-) create mode 100644 scripts/createAllTemplates.s.sol diff --git a/scripts/createAllTemplates.s.sol b/scripts/createAllTemplates.s.sol new file mode 100644 index 0000000..052b90e --- /dev/null +++ b/scripts/createAllTemplates.s.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IZkCappedMinterV2} from "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract CreateAllTemplatesScript is SafeTxHelper, Script { + /// @dev For running from `forge script` + function run() public virtual { + // zkSync mainnet + run(ZkSyncGuardianCompensation2024_2025.getDefault(vm)); + } + + /// @dev For running in tests + function run( + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns(GnosisTransaction[] memory) { + IGnosisSafe safe; + ZkSyncGuardianCompensation2024_2025.Config memory config; + + // zkSync Era (zkSync Guardians) + config = ZkSyncGuardianCompensation2024_2025.getDefault(vm); + + safe = config.guardianSafe; + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](config.guardians.length + 1); + safeTxs[0] = GnosisTransaction({ + to: address(config.registry), + value: 0 ether, + data: abi.encodeWithSelector( + CyberAgreementRegistry.createTemplate.selector, + config.borgResolutionTemplate.id, + config.borgResolutionTemplate.name, + config.borgResolutionTemplate.agreementUri, + config.borgResolutionTemplate.globalFields, + config.borgResolutionTemplate.partyFields + ) + }); + for (uint i = 0; i < config.guardians.length ; i++) { + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config.guardians[i]; + safeTxs[i + 1] = GnosisTransaction({ + to: address(config.registry), + value: 0 ether, + data: abi.encodeWithSelector( + CyberAgreementRegistry.createTemplate.selector, + guardian.compTemplate.id, + guardian.compTemplate.name, + guardian.compTemplate.agreementUri, + guardian.compTemplate.globalFields, + guardian.compTemplate.partyFields + ) + }); + } + + // Output logs + + console2.log(""); + console2.log("=== CreateAllTemplatesScript ==="); + console2.log("Safe: ", address(safe)); + console2.log("Safe TXs:"); + for (uint256 i = 0 ; i < safeTxs.length ; i++) { + console2.log(" #", i); + console2.log(" to:", safeTxs[i].to); + console2.log(" value:", safeTxs[i].value); + console2.log(" data:"); + console2.logBytes(safeTxs[i].data); + console2.log(""); + } + + return safeTxs; + } +} diff --git a/scripts/createSafeTx.s.sol b/scripts/createSafeTx.s.sol index 2faa139..576f686 100644 --- a/scripts/createSafeTx.s.sol +++ b/scripts/createSafeTx.s.sol @@ -22,7 +22,7 @@ contract CreateSafeTxScript is SafeTxHelper, Script { ZkSyncGuardianCompensation2024_2025.Config memory config; // zkSync Era (zkSync Guardians) - config = ZkSyncGuardianCompensation2024_2025.getDefault(); + config = ZkSyncGuardianCompensation2024_2025.getDefault(vm); safe = config.guardianSafe; GnosisTransaction[] memory safeTxs = new GnosisTransaction[](7); safeTxs[0] = GnosisTransaction({ @@ -53,7 +53,7 @@ contract CreateSafeTxScript is SafeTxHelper, Script { } // zkSync Sepolia -// config = ZkSyncGuardianCompensationSepolia2024_2025.getDefault(); +// config = ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm); // safe = config.guardianSafe; // GnosisTransaction[] memory safeTxs = new GnosisTransaction[](2); // safeTxs[0] = GnosisTransaction({ diff --git a/scripts/deployTestZkCappedMinter.s.sol b/scripts/deployTestZkCappedMinter.s.sol index 72583e6..49b5e2e 100644 --- a/scripts/deployTestZkCappedMinter.s.sol +++ b/scripts/deployTestZkCappedMinter.s.sol @@ -24,12 +24,12 @@ contract DeployTestZkCappedMinterScript is SafeTxHelper, Script { // zkSync Sepolia for 2024-2025 "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.2024-2025", IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E), - ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) // zkSync Sepolia for 2025-2026 // "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.2025-2026", // IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E), -// ZkSyncGuardianCompensationSepolia2025_2026.getDefault() +// ZkSyncGuardianCompensationSepolia2025_2026.getDefault(vm) ); } diff --git a/scripts/deployZkSyncGuardianCompensation.s.sol b/scripts/deployZkSyncGuardianCompensation.s.sol index ccd416e..9edc0b0 100644 --- a/scripts/deployZkSyncGuardianCompensation.s.sol +++ b/scripts/deployZkSyncGuardianCompensation.s.sol @@ -23,19 +23,19 @@ contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { // zkSync Era for 2024-2025 // "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2024-2025", -// ZkSyncGuardianCompensation2024_2025.getDefault() +// ZkSyncGuardianCompensation2024_2025.getDefault(vm) // zkSync Era for 2025-2026 // "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2025-2026", -// ZkSyncGuardianCompensation2025_2026.getDefault() +// ZkSyncGuardianCompensation2025_2026.getDefault(vm) // zkSync Sepolia for 2024-2025 "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.1.2024-2025", - ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) // zkSync Sepolia for 2025-2026 // "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.2025-2026", -// ZkSyncGuardianCompensationSepolia2025_2026.getDefault() +// ZkSyncGuardianCompensationSepolia2025_2026.getDefault(vm) ); } diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol index be2297d..2c5b002 100644 --- a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol +++ b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol @@ -27,11 +27,11 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Sc // zkSync Era "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0", - ZkSyncGuardianCompensation2024_2025.getDefault() + ZkSyncGuardianCompensation2024_2025.getDefault(vm) // // zkSync Sepolia // "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.1", -// ZkSyncGuardianCompensationSepolia2024_2025.getDefault() +// ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) ); } diff --git a/scripts/executeSafeTx.s.sol b/scripts/executeSafeTx.s.sol index 35aa5ac..f16d1a2 100644 --- a/scripts/executeSafeTx.s.sol +++ b/scripts/executeSafeTx.s.sol @@ -21,18 +21,18 @@ contract ExecuteSafeTxScript is SafeTxHelper, Script { run( vm.envUint("DEPLOYER_PRIVATE_KEY"), -// ZkSyncGuardianCompensationSepolia2024_2025.getDefault().guardianSafe, // safe +// ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm).guardianSafe, // safe // 0x6F26e588f28bf67C016EEA19CA90c4E41B70d499, // to // 0, // value // hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000856a8aea8a37a338e2490384bb790cd87b5caae4" // data -// ZkSyncGuardianCompensationSepolia2024_2025.getDefault().guardianSafe, // safe +// ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm).guardianSafe, // safe // 0x856A8Aea8a37A338e2490384Bb790cD87b5CaaE4, // to // 0, // value // hex"66e261840000000000000000000000006f26e588f28bf67c016eea19ca90c4e41b70d499" // data - ZkSyncGuardianCompensationSepolia2024_2025.getDefault().guardianSafe, // safe - address(ZkSyncGuardianCompensationSepolia2024_2025.getDefault().registry), // to + ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm).guardianSafe, // safe + address(ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm).registry), // to 0, // value abi.encodeWithSelector( CyberAgreementRegistry.setDelegation.selector, diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol index a8d66f9..f826023 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -35,19 +35,9 @@ library ZkSyncGuardianCompensation2024_2025 { // zkSync Guardian BORG Resolution - string borgResolutionUri; - string borgResolutionTemplateName; - bytes32 borgResolutionTemplateId; - string[] borgResolutionGlobalFields; - string[] borgResolutionPartyFields; + TemplateInfo borgResolutionTemplate; - // zkSync Guardian Compensation Agreement - - string compAgreementUri; - string compTemplateName; - bytes32 compTemplateId; - string[] compGlobalFields; - string[] compPartyFields; + // zkSync Guardian Compensation Agreement (one template per guardian for now) GuardianCompInfo[] guardians; uint256 fixedAnnualCompensation; @@ -55,45 +45,28 @@ library ZkSyncGuardianCompensation2024_2025 { BaseAllocation.Milestone[] milestones; } + struct TemplateInfo { + bytes32 id; + string agreementUri; + string name; + string[] globalFields; + string[] partyFields; + } + struct PartyInfo { string name; address evmAddress; } struct GuardianCompInfo { - bytes32 compTemplateId; PartyInfo partyInfo; + TemplateInfo compTemplate; } - function getDefault() internal view returns(Config memory) { - string[] memory borgResolutionGlobalFields = new string[](0); - - string[] memory borgResolutionPartyFields = new string[](2); - borgResolutionPartyFields[0] = "name"; - borgResolutionPartyFields[1] = "evmAddress"; - - string[] memory compGlobalFields = new string[](11); - compGlobalFields[0] = "metavestType"; - compGlobalFields[1] = "grantor"; - compGlobalFields[2] = "grantee"; - compGlobalFields[3] = "tokenContract"; - compGlobalFields[4] = "tokenStreamTotal"; - compGlobalFields[5] = "vestingCliffCredit"; - compGlobalFields[6] = "unlockingCliffCredit"; - compGlobalFields[7] = "vestingRate"; - compGlobalFields[8] = "vestingStartTime"; - compGlobalFields[9] = "unlockRate"; - compGlobalFields[10] = "unlockStartTime"; - - string[] memory compPartyFields = new string[](2); - compPartyFields[0] = "name"; - compPartyFields[1] = "evmAddress"; - + function getDefault(Vm vm) internal view returns(Config memory) { IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); IGnosisSafe metalexSafe = IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc); - GuardianCompInfo[] memory guardians = new GuardianCompInfo[](6); // TBD - return Config({ // ZK Governance @@ -111,36 +84,80 @@ library ZkSyncGuardianCompensation2024_2025 { }), // MetaLeX - + metalexSafe: metalexSafe, registry: CyberAgreementRegistry(0x07E0a0BeC742f90f7879830bC917E783dA6a6357), vestingAllocationFactory: VestingAllocationFactory(0xD1c48D8866d81CC0f6567f3E3fbbb8eF48E30D99), - controller: metavestController(address(0)), // TODO TBD + controller: metavestController(0xD509349AF986E7202f2Bc4ae49C203E354faafCD), // zkSync Guardian BORG Resolution - borgResolutionUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD - borgResolutionTemplateName: "ZKsync Guardians Board Resolutions Compensation Amendments Transmission", - borgResolutionTemplateId: bytes32(uint256(200)), - borgResolutionGlobalFields: borgResolutionGlobalFields, - borgResolutionPartyFields: borgResolutionPartyFields, + borgResolutionTemplate: loadBorgResolutionTemplate(vm), // zkSync Guardian Compensation Agreement - // TODO WIP - compAgreementUri: "ipfs://bafybeiangqvqenqkvybrbxu2npv6mlqreunxxygsh3377mpwwjao64qpse", // TODO TBD - compTemplateName: "Guardians Compensation Amendment", - compTemplateId: bytes32(uint256(201)), - compGlobalFields: compGlobalFields, - compPartyFields: compPartyFields, - - guardians: guardians, + guardians: loadGuardianAndComps(vm), fixedAnnualCompensation: 625e3 ether, metavestVestingAndUnlockStartTime: 1725148800, // 2024/09/01 00:00 UTC (means by the deployment @ 2025/09/01 it would've been fully unlocked) milestones: new BaseAllocation.Milestone[](0) }); } + function loadBorgResolutionTemplate(Vm vm) internal view returns(TemplateInfo memory) { + string[] memory borgResolutionGlobalFields = new string[](0); + + string[] memory borgResolutionPartyFields = new string[](2); + borgResolutionPartyFields[0] = "name"; + borgResolutionPartyFields[1] = "evmAddress"; + + return TemplateInfo({ + id: bytes32(vm.envUint("BORG_RESOLUTION_TEMPLATE_ID")), + agreementUri: vm.envString("BORG_RESOLUTION_URI"), + name: vm.envString("BORG_RESOLUTION_TEMPLATE_NAME"), + globalFields: borgResolutionGlobalFields, + partyFields: borgResolutionPartyFields + }); + } + + function loadGuardianAndComps(Vm vm) internal view returns(GuardianCompInfo[] memory) { + string[] memory compGlobalFields = new string[](11); + compGlobalFields[0] = "metavestType"; + compGlobalFields[1] = "grantor"; + compGlobalFields[2] = "grantee"; + compGlobalFields[3] = "tokenContract"; + compGlobalFields[4] = "tokenStreamTotal"; + compGlobalFields[5] = "vestingCliffCredit"; + compGlobalFields[6] = "unlockingCliffCredit"; + compGlobalFields[7] = "vestingRate"; + compGlobalFields[8] = "vestingStartTime"; + compGlobalFields[9] = "unlockRate"; + compGlobalFields[10] = "unlockStartTime"; + + string[] memory compPartyFields = new string[](2); + compPartyFields[0] = "name"; + compPartyFields[1] = "evmAddress"; + + uint256 numGuardians = vm.envUint("NUM_GUARDIANS"); + + GuardianCompInfo[] memory guardians = new GuardianCompInfo[](numGuardians); + for (uint i = 0; i < guardians.length ; i++) { + guardians[i] = GuardianCompInfo({ + partyInfo: PartyInfo({ + name: vm.envString(string(abi.encodePacked("GUARDIAN_NAME_", vm.toString(i)))), + evmAddress: address(uint160(vm.envUint(string(abi.encodePacked("GUARDIAN_ADDR_", vm.toString(i)))))) + }), + compTemplate: TemplateInfo({ + id: bytes32(vm.envUint(string(abi.encodePacked("GUARDIAN_TEMPLATE_ID_", vm.toString(i))))), + agreementUri: vm.envString(string(abi.encodePacked("GUARDIAN_AGREEMENT_URI_", vm.toString(i)))), + name: vm.envString(string(abi.encodePacked("GUARDIAN_TEMPLATE_NAME_", vm.toString(i)))), + globalFields: compGlobalFields, + partyFields: compPartyFields + }) + }); + } + return guardians; + } + function parseAllocation(Config memory config) internal view returns(BaseAllocation.Allocation memory) { return BaseAllocation.Allocation({ tokenContract: address(config.zkToken), diff --git a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol index 690164b..7879143 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol @@ -15,8 +15,8 @@ import {metavestController} from "../../src/MetaVesTController.sol"; library ZkSyncGuardianCompensation2025_2026 { - function getDefault() internal returns(ZkSyncGuardianCompensation2024_2025.Config memory) { - ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(); + function getDefault(Vm vm) internal returns(ZkSyncGuardianCompensation2024_2025.Config memory) { + ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(vm); return ZkSyncGuardianCompensation2024_2025.Config({ @@ -36,24 +36,14 @@ library ZkSyncGuardianCompensation2025_2026 { metalexSafe: defaultConfig.metalexSafe, registry: defaultConfig.registry, vestingAllocationFactory: defaultConfig.vestingAllocationFactory, - controller: defaultConfig.controller, + controller: metavestController(0x570d31F59bD0C96a9e1CC889E7E4dBd585D6915b), // zkSync Guardian BORG Resolution - borgResolutionUri: defaultConfig.borgResolutionUri, - borgResolutionTemplateName: defaultConfig.borgResolutionTemplateName, - borgResolutionTemplateId: defaultConfig.borgResolutionTemplateId, - borgResolutionGlobalFields: defaultConfig.borgResolutionGlobalFields, - borgResolutionPartyFields: defaultConfig.borgResolutionPartyFields, + borgResolutionTemplate: defaultConfig.borgResolutionTemplate, // zkSync Guardian Compensation Agreement - compAgreementUri: defaultConfig.compAgreementUri, - compTemplateName: defaultConfig.compTemplateName, - compTemplateId: defaultConfig.compTemplateId, - compGlobalFields: defaultConfig.compGlobalFields, - compPartyFields: defaultConfig.compPartyFields, - guardians: defaultConfig.guardians, fixedAnnualCompensation: 625e3 ether, metavestVestingAndUnlockStartTime: 1756684800, // 2025/09/01 00:00 UTC diff --git a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol index 76c7564..fe69efd 100644 --- a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol @@ -15,28 +15,12 @@ import {metavestController} from "../../src/MetaVesTController.sol"; library ZkSyncGuardianCompensationSepolia2024_2025 { - function getDefault() internal returns(ZkSyncGuardianCompensation2024_2025.Config memory) { - ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(); + function getDefault(Vm vm) internal returns(ZkSyncGuardianCompensation2024_2025.Config memory) { + ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(vm); IGnosisSafe guardianSafe = IGnosisSafe(0x3C785F96864002eB47bDe32d597476a3D97fCd15); IGnosisSafe metalexSafe = IGnosisSafe(0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe); // This is faked by EOA - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[] memory guardians = new ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[](2); - guardians[0] = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ - compTemplateId: defaultConfig.compTemplateId, - partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Alice", - evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b - }) - }); - guardians[1] = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ - compTemplateId: defaultConfig.compTemplateId, - partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Bob", - evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b - }) - }); - return ZkSyncGuardianCompensation2024_2025.Config({ // ZK Governance @@ -61,21 +45,11 @@ library ZkSyncGuardianCompensationSepolia2024_2025 { // zkSync Guardian BORG Resolution - borgResolutionUri: defaultConfig.borgResolutionUri, - borgResolutionTemplateName: defaultConfig.borgResolutionTemplateName, - borgResolutionTemplateId: bytes32(uint256(206)), - borgResolutionGlobalFields: defaultConfig.borgResolutionGlobalFields, - borgResolutionPartyFields: defaultConfig.borgResolutionPartyFields, + borgResolutionTemplate: ZkSyncGuardianCompensation2024_2025.loadBorgResolutionTemplate(vm), // zkSync Guardian Compensation Agreement - compAgreementUri: defaultConfig.compAgreementUri, - compTemplateName: defaultConfig.compTemplateName, - compTemplateId: bytes32(uint256(207)), - compGlobalFields: defaultConfig.compGlobalFields, - compPartyFields: defaultConfig.compPartyFields, - - guardians: guardians, + guardians: ZkSyncGuardianCompensation2024_2025.loadGuardianAndComps(vm), fixedAnnualCompensation: defaultConfig.fixedAnnualCompensation, metavestVestingAndUnlockStartTime: defaultConfig.metavestVestingAndUnlockStartTime, milestones: defaultConfig.milestones diff --git a/scripts/proposeAllGuardiansMetavestDeals.s.sol b/scripts/proposeAllGuardiansMetavestDeals.s.sol index 6a05683..5eff84d 100644 --- a/scripts/proposeAllGuardiansMetavestDeals.s.sol +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -26,7 +26,7 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), // zkSync Sepolia for 2024-2025 - ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) ); } diff --git a/scripts/proposeBorgResolution.s.sol b/scripts/proposeBorgResolution.s.sol index 81ab868..9f51941 100644 --- a/scripts/proposeBorgResolution.s.sol +++ b/scripts/proposeBorgResolution.s.sol @@ -27,10 +27,10 @@ contract ProposeBorgResolutionScript is SafeTxHelper, Script { vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), // zkSync Era -// ZkSyncGuardianCompensation2024_2025.getDefault() +// ZkSyncGuardianCompensation2024_2025.getDefault(vm) // zkSync Sepolia - ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) ); } @@ -64,7 +64,7 @@ contract ProposeBorgResolutionScript is SafeTxHelper, Script { bytes32 expectedContractId = keccak256( abi.encode( - config.borgResolutionTemplateId, + config.borgResolutionTemplate.id, agreementSalt, // salt, globalValues, parties @@ -76,9 +76,9 @@ contract ProposeBorgResolutionScript is SafeTxHelper, Script { config.registry.DOMAIN_SEPARATOR(), config.registry.SIGNATUREDATA_TYPEHASH(), expectedContractId, - config.borgResolutionUri, - config.borgResolutionGlobalFields, - config.borgResolutionPartyFields, + config.borgResolutionTemplate.agreementUri, + config.borgResolutionTemplate.globalFields, + config.borgResolutionTemplate.partyFields, globalValues, partyValues[0], proposerPrivateKey @@ -87,7 +87,7 @@ contract ProposeBorgResolutionScript is SafeTxHelper, Script { vm.startBroadcast(proposerPrivateKey); bytes32 contractId = config.registry.createContract( - config.borgResolutionTemplateId, + config.borgResolutionTemplate.id, agreementSalt, globalValues, parties, diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index af4817e..722d463 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -22,16 +22,11 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { + ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(vm); run( vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ - compTemplateId: bytes32(uint256(201)), - partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Alice", - evmAddress: 0x48d206948C366396a86A449DdD085FDbfC280B4b - }) - }), - ZkSyncGuardianCompensation2024_2025.getDefault() + defaultConfig.guardians[0], + defaultConfig ); } @@ -91,14 +86,14 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { bytes32 expectedContractId = keccak256( abi.encode( - config.compTemplateId, + guardianInfo.compTemplate.id, agreementSalt, // salt, globalValues, parties ) ); - (string memory agreementUri, ) = config.registry.templates(guardianInfo.compTemplateId); + (string memory agreementUri, ) = config.registry.templates(guardianInfo.compTemplate.id); bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, @@ -106,8 +101,8 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { config.registry.SIGNATUREDATA_TYPEHASH(), expectedContractId, agreementUri, - config.compGlobalFields, - config.compPartyFields, + guardianInfo.compTemplate.globalFields, + guardianInfo.compTemplate.partyFields, globalValues, partyValues[0], proposerPrivateKey @@ -116,7 +111,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { vm.startBroadcast(proposerPrivateKey); bytes32 contractId = config.controller.proposeAndSignDeal( - config.compTemplateId, + guardianInfo.compTemplate.id, agreementSalt, metavestController.metavestType.Vesting, guardianInfo.partyInfo.evmAddress, diff --git a/scripts/signDealAndCreateMetavest.s.sol b/scripts/signDealAndCreateMetavest.s.sol index 7dd69a7..8b2a1c9 100644 --- a/scripts/signDealAndCreateMetavest.s.sol +++ b/scripts/signDealAndCreateMetavest.s.sol @@ -24,6 +24,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { uint256 granteePrivateKey = vm.envUint("GRANTEE_PRIVATE_KEY"); + ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(vm); run( // granteePrivateKey, // 0x0000000000000000000000000000000000000000000000000000000000000000, // TODO TBD @@ -31,19 +32,13 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { // name: "Alice", // evmAddress: vm.addr(granteePrivateKey) // }), -// ZkSyncGuardianCompensation2024_2025.getDefault() +// ZkSyncGuardianCompensation2024_2025.getDefault(vm) // zkSync Sepolia granteePrivateKey, 0xd0d7610ca18b8a76a36c7e1241929641c06cce69ddc1161beebe69b72dae6cbf, - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ - compTemplateId: bytes32(uint256(201)), // TODO TBD - partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Alice", - evmAddress: vm.addr(granteePrivateKey) - }) - }), - ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + defaultConfig.guardians[0], + defaultConfig ); } @@ -71,7 +66,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { // Sign the deal and create MetaVesT - (string memory agreementUri, ) = config.registry.templates(granteeInfo.compTemplateId); + (string memory agreementUri, ) = config.registry.templates(granteeInfo.compTemplate.id); string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, granteeInfo.partyInfo); bytes memory signature = CyberAgreementUtils.signAgreementTypedData( @@ -80,8 +75,8 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { config.registry.SIGNATUREDATA_TYPEHASH(), agreementId, agreementUri, - config.compGlobalFields, - config.compPartyFields, + granteeInfo.compTemplate.globalFields, + granteeInfo.compTemplate.partyFields, config.formatCompGlobalValues(vm, granteeInfo.partyInfo.evmAddress), granteePartyValues, granteePrivateKey diff --git a/scripts/voidAgreement.s.sol b/scripts/voidAgreement.s.sol index b9072c9..5f7f949 100644 --- a/scripts/voidAgreement.s.sol +++ b/scripts/voidAgreement.s.sol @@ -24,13 +24,13 @@ contract VoidAgreementScript is SafeTxHelper, Script { run( // zkSync Era // vm.envUint("GRANTEE_PRIVATE_KEY"), -// ZkSyncGuardianCompensation2024_2025.getDefault() +// ZkSyncGuardianCompensation2024_2025.getDefault(vm) // zkSync Sepolia vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), // vm.envUint("GRANTEE_PRIVATE_KEY"), 0xc519e4ce6730ae9167f4e080f47ac1544405756cf301f0c8316578fc90f95e0a, - ZkSyncGuardianCompensationSepolia2024_2025.getDefault() + ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) ); } diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 77afe91..6ed0974 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -11,6 +11,7 @@ import {console2} from "forge-std/console2.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; import {DeployZkSyncGuardianCompensationPrerequisitesScript} from "../scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol"; import {DeployZkSyncGuardianCompensationScript} from "../scripts/deployZkSyncGuardianCompensation.s.sol"; +import {CreateAllTemplatesScript} from "../scripts/createAllTemplates.s.sol"; import {ProposeBorgResolutionScript} from "../scripts/proposeBorgResolution.s.sol"; import {ProposeMetaVestDealScript} from "../scripts/proposeMetavestDeal.s.sol"; import {SignDealAndCreateMetavestScript} from "../scripts/signDealAndCreateMetavest.s.sol"; @@ -22,6 +23,7 @@ import {GnosisTransaction} from "./lib/safe.sol"; contract ZkSyncGuardianCompensationTest is DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensationScript, + CreateAllTemplatesScript, ProposeBorgResolutionScript, ProposeMetaVestDealScript, SignDealAndCreateMetavestScript, @@ -69,11 +71,19 @@ contract ZkSyncGuardianCompensationTest is CyberAgreementRegistry registry; VestingAllocationFactory vestingAllocationFactory; metavestController controller; + GnosisTransaction[] memory safeTxsCreateAllTemplates; GnosisTransaction[] memory safeTxs2024_2025; GnosisTransaction[] memory safeTxs2025_2026; - config2024_2025 = ZkSyncGuardianCompensation2024_2025.getDefault(); - config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(); + config2024_2025 = ZkSyncGuardianCompensation2024_2025.getDefault(vm); + config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(vm); + + // Update guardian info + + assertEq(config2024_2025.guardians.length, 1, "Unexpected number of guardians (2024-2025)"); + config2024_2025.guardians[0].partyInfo.evmAddress = alice; + assertEq(config2025_2026.guardians.length, 1, "Unexpected number of guardians (2025-2026)"); + config2025_2026.guardians[0].partyInfo.evmAddress = alice; (auth, registry, vestingAllocationFactory) = DeployZkSyncGuardianCompensationPrerequisitesScript.deployPrerequisites( deployerPrivateKey, @@ -103,6 +113,15 @@ contract ZkSyncGuardianCompensationTest is ); config2025_2026.controller = controller; // Update configs with deployed contracts + // Create all templates + safeTxsCreateAllTemplates = CreateAllTemplatesScript.run(config2025_2026); + + // Simulate MetaLeX SAFE to execute txs as instructed + for (uint256 i = 0; i < safeTxsCreateAllTemplates.length; i++) { + vm.prank(address(config2024_2025.metalexSafe)); + (safeTxsCreateAllTemplates[i].to).call{value: safeTxsCreateAllTemplates[i].value}(safeTxsCreateAllTemplates[i].data); + } + // Simulate Guardian SAFE to execute txs as instructed for (uint256 i = 0; i < safeTxs2024_2025.length; i++) { @@ -119,28 +138,33 @@ contract ZkSyncGuardianCompensationTest is vm.startPrank(address(config2024_2025.metalexSafe)); registry.createTemplate( - config2024_2025.borgResolutionTemplateId, - config2024_2025.borgResolutionTemplateName, - config2024_2025.borgResolutionUri, - config2024_2025.borgResolutionGlobalFields, - config2024_2025.borgResolutionPartyFields + config2024_2025.borgResolutionTemplate.id, + config2024_2025.borgResolutionTemplate.name, + config2024_2025.borgResolutionTemplate.agreementUri, + config2024_2025.borgResolutionTemplate.globalFields, + config2024_2025.borgResolutionTemplate.partyFields ); - registry.createTemplate( - config2024_2025.compTemplateId, - config2024_2025.compTemplateName, - config2024_2025.compAgreementUri, - config2024_2025.compGlobalFields, - config2024_2025.compPartyFields - ); + for (uint256 i = 0; i < config2024_2025.guardians.length; i ++) { + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config2024_2025.guardians[i]; + registry.createTemplate( + guardian.compTemplate.id, + guardian.compTemplate.name, + guardian.compTemplate.agreementUri, + guardian.compTemplate.globalFields, + guardian.compTemplate.partyFields + ); + } vm.stopPrank(); // Simulate vote pass (https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746) + masterMinter = IZkCappedMinterV2(config2024_2025.zkCappedMinter.MINTABLE()); + + // Comment out below after vote has been executed as of block 64423211 vm.startPrank(zkTokenAdmin); - masterMinter = IZkCappedMinterV2(config2024_2025.zkCappedMinter.MINTABLE()); masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(config2024_2025.zkCappedMinter)); masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(config2025_2026.zkCappedMinter)); @@ -153,6 +177,7 @@ contract ZkSyncGuardianCompensationTest is function run() public override( DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensationScript, + CreateAllTemplatesScript, ProposeBorgResolutionScript, ProposeMetaVestDealScript, SignDealAndCreateMetavestScript @@ -173,20 +198,23 @@ contract ZkSyncGuardianCompensationTest is _assertTemplate( config2024_2025.registry, - config2024_2025.compTemplateId, - config2024_2025.compAgreementUri, - config2024_2025.compTemplateName, - config2024_2025.compGlobalFields, - config2024_2025.compPartyFields - ); - _assertTemplate( - config2024_2025.registry, - config2024_2025.borgResolutionTemplateId, - config2024_2025.borgResolutionUri, - config2024_2025.borgResolutionTemplateName, - config2024_2025.borgResolutionGlobalFields, - config2024_2025.borgResolutionPartyFields + config2024_2025.borgResolutionTemplate.id, + config2024_2025.borgResolutionTemplate.agreementUri, + config2024_2025.borgResolutionTemplate.name, + config2024_2025.borgResolutionTemplate.globalFields, + config2024_2025.borgResolutionTemplate.partyFields ); + for (uint256 i = 0; i < config2024_2025.guardians.length; i ++) { + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config2024_2025.guardians[i]; + _assertTemplate( + config2024_2025.registry, + guardian.compTemplate.id, + guardian.compTemplate.agreementUri, + guardian.compTemplate.name, + guardian.compTemplate.globalFields, + guardian.compTemplate.partyFields + ); + } // MetaVesT deployments @@ -194,14 +222,14 @@ contract ZkSyncGuardianCompensationTest is vm.assertEq(config2024_2025.controller.dao(), address(config2024_2025.guardianSafe), "2024-2025 MetaVesTController's DAO should be Guardian SAFE"); vm.assertEq(config2024_2025.controller.registry(), address(config2024_2025.registry), "2024-2025 Unexpected MetaVesTController registry"); vm.assertEq(config2024_2025.controller.vestingFactory(), address(config2024_2025.vestingAllocationFactory), "2024-2025 Unexpected MetaVesTController vesting allocation factory"); - vm.assertEq(config2024_2025.controller.zkCappedMinter(), address(config2024_2025.zkCappedMinter), "2024-2025 MetaVesTController should have ZK Capped Minter should set"); + vm.assertEq(config2024_2025.controller.zkCappedMinter(), address(config2024_2025.zkCappedMinter), "2024-2025 MetaVesTController should have ZK Capped Minter set"); vm.assertTrue(config2024_2025.zkCappedMinter.hasRole(config2024_2025.zkCappedMinter.MINTER_ROLE(), address(config2024_2025.controller)), "2024-2025 ZK Capped Minter should grant MetaVesTController MINTER role"); vm.assertEq(config2025_2026.controller.authority(), address(config2025_2026.guardianSafe), "2025-2026 MetaVesTController's authority should be Guardian SAFE"); vm.assertEq(config2025_2026.controller.dao(), address(config2025_2026.guardianSafe), "2025-2026 MetaVesTController's DAO should be Guardian SAFE"); vm.assertEq(config2025_2026.controller.registry(), address(config2025_2026.registry), "2025-2026 Unexpected MetaVesTController registry"); vm.assertEq(config2025_2026.controller.vestingFactory(), address(config2025_2026.vestingAllocationFactory), "2025-2026 Unexpected MetaVesTController vesting allocation factory"); - vm.assertEq(config2025_2026.controller.zkCappedMinter(), address(config2025_2026.zkCappedMinter), "2025-2026 MetaVesTController should have ZK Capped Minter should set"); + vm.assertEq(config2025_2026.controller.zkCappedMinter(), address(config2025_2026.zkCappedMinter), "2025-2026 MetaVesTController should have ZK Capped Minter set"); vm.assertTrue(config2025_2026.zkCappedMinter.hasRole(config2025_2026.zkCappedMinter.MINTER_ROLE(), address(config2025_2026.controller)), "2025-2026 ZK Capped Minter should grant MetaVesTController MINTER role"); } @@ -214,13 +242,7 @@ contract ZkSyncGuardianCompensationTest is // Run scripts to propose deals bytes32 agreementId = ProposeMetaVestDealScript.run( guardianDelegatePrivateKey, - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ - compTemplateId: config2024_2025.compTemplateId, - partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Alice", - evmAddress: alice - }) - }), + config2024_2025.guardians[0], // alice config2024_2025 ); @@ -243,7 +265,7 @@ contract ZkSyncGuardianCompensationTest is // Verify agreement (bytes32 templateId, , , , , , uint256 expiry) = config2024_2025.registry.agreements(agreementId); - assertEq(templateId, config2024_2025.borgResolutionTemplateId, "Unexpected borg Resolution template ID"); + assertEq(templateId, config2024_2025.borgResolutionTemplate.id, "Unexpected borg Resolution template ID"); (, , , , , address[] memory parties, , , , ) = config2024_2025.registry.getContractDetails(agreementId); vm.assertEq(parties.length, 1, "Should be single-party"); @@ -293,7 +315,7 @@ contract ZkSyncGuardianCompensationTest is assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "should be Guardian SAFE's delegate"); ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory chadInfo = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ - compTemplateId: config2024_2025.compTemplateId, + compTemplate: config2024_2025.guardians[0].compTemplate, // Re-use Alice's template just for test partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Chad", evmAddress: chad @@ -332,13 +354,7 @@ contract ZkSyncGuardianCompensationTest is // Run scripts to propose deals - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory aliceInfo = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ - compTemplateId: config2024_2025.compTemplateId, - partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ - name: "Alice", - evmAddress: alice - }) - }); + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory aliceInfo = config2024_2025.guardians[0]; bytes32 contractIdAlice2024_2025 = ProposeMetaVestDealScript.run( guardianDelegatePrivateKey, From 46d60eeea6b54d5f171a715d9a258350453371f6 Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 2 Sep 2025 14:46:07 -0700 Subject: [PATCH 57/68] chore: refactor proposeMetavestDeal scripts for modularity --- .../proposeAllGuardiansMetavestDeals.s.sol | 38 ++++++++++++++++--- scripts/proposeMetavestDeal.s.sol | 8 ++-- test/ZkSyncGuardianCompensation.t.sol | 8 ++-- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/scripts/proposeAllGuardiansMetavestDeals.s.sol b/scripts/proposeAllGuardiansMetavestDeals.s.sol index 5eff84d..b7d4a0c 100644 --- a/scripts/proposeAllGuardiansMetavestDeals.s.sol +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -21,8 +21,8 @@ import {metavestController} from "../src/MetaVesTController.sol"; contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { /// @dev For running from `forge script`. Provide the deployer private key through env var. - function run() public override { - run( + function run() public virtual override { + runAll( vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), // zkSync Sepolia for 2024-2025 @@ -31,7 +31,7 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { } /// @dev For running in tests - function run( + function runAll( uint256 proposerPrivateKey, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32[] memory) { @@ -50,11 +50,11 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { for (uint256 i = 0; i < config.guardians.length; i++) { console2.log("Proposing to Guardian #%d", i + 1); - console2.log(" name:", config.guardians[i].name); - console2.log(" address:", config.guardians[i].evmAddress); + console2.log(" name:", config.guardians[i].partyInfo.name); + console2.log(" address:", config.guardians[i].partyInfo.evmAddress); console2.log(""); - agreementIds[i] = run( + agreementIds[i] = runSingle( proposerPrivateKey, config.guardians[i], config @@ -70,4 +70,30 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { return agreementIds; } + + function runSingle( + uint256 proposerPrivateKey, + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public override returns(bytes32) { + return ProposeMetaVestDealScript.runSingle( + proposerPrivateKey, + guardianInfo, + config + ); + } + + function runSingle( + uint256 proposerPrivateKey, + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + BaseAllocation.Allocation memory allocation, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public override returns(bytes32) { + return ProposeMetaVestDealScript.runSingle( + proposerPrivateKey, + guardianInfo, + allocation, + config + ); + } } diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index 722d463..95911cf 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -23,7 +23,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(vm); - run( + runSingle( vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), defaultConfig.guardians[0], defaultConfig @@ -31,12 +31,12 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { } /// @dev For running in tests - function run( + function runSingle( uint256 proposerPrivateKey, ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { - return run( + return runSingle( proposerPrivateKey, guardianInfo, // Default guardian allocations config.parseAllocation(), @@ -45,7 +45,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { } /// @dev For running in tests - function run( + function runSingle( uint256 proposerPrivateKey, ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, BaseAllocation.Allocation memory allocation, diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 6ed0974..c83e522 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -240,7 +240,7 @@ contract ZkSyncGuardianCompensationTest is assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); // Run scripts to propose deals - bytes32 agreementId = ProposeMetaVestDealScript.run( + bytes32 agreementId = ProposeMetaVestDealScript.runSingle( guardianDelegatePrivateKey, config2024_2025.guardians[0], // alice config2024_2025 @@ -321,7 +321,7 @@ contract ZkSyncGuardianCompensationTest is evmAddress: chad }) }); - bytes32 contractIdChad = ProposeMetaVestDealScript.run( + bytes32 contractIdChad = ProposeMetaVestDealScript.runSingle( guardianDelegatePrivateKey, chadInfo, BaseAllocation.Allocation({ @@ -356,12 +356,12 @@ contract ZkSyncGuardianCompensationTest is ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory aliceInfo = config2024_2025.guardians[0]; - bytes32 contractIdAlice2024_2025 = ProposeMetaVestDealScript.run( + bytes32 contractIdAlice2024_2025 = ProposeMetaVestDealScript.runSingle( guardianDelegatePrivateKey, aliceInfo, config2024_2025 ); - bytes32 contractIdAlice2025_2026 = ProposeMetaVestDealScript.run( + bytes32 contractIdAlice2025_2026 = ProposeMetaVestDealScript.runSingle( guardianDelegatePrivateKey, aliceInfo, config2025_2026 From 0c569aa9df43454772bd33d19b24a4b1ee838cd3 Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 2 Sep 2025 14:47:06 -0700 Subject: [PATCH 58/68] test: add acceptance tests for existing production deployment --- test/ZkSyncGuardianCompensation.t.sol | 207 +++++++++++++++----------- 1 file changed, 121 insertions(+), 86 deletions(-) diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index c83e522..145ca45 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -13,6 +13,7 @@ import {DeployZkSyncGuardianCompensationPrerequisitesScript} from "../scripts/de import {DeployZkSyncGuardianCompensationScript} from "../scripts/deployZkSyncGuardianCompensation.s.sol"; import {CreateAllTemplatesScript} from "../scripts/createAllTemplates.s.sol"; import {ProposeBorgResolutionScript} from "../scripts/proposeBorgResolution.s.sol"; +import {ProposeAllGuardiansMetaVestDealScript} from "../scripts/proposeAllGuardiansMetavestDeals.s.sol"; import {ProposeMetaVestDealScript} from "../scripts/proposeMetavestDeal.s.sol"; import {SignDealAndCreateMetavestScript} from "../scripts/signDealAndCreateMetavest.s.sol"; import {ZkSyncGuardianCompensation2024_2025} from "../scripts/lib/ZkSyncGuardianCompensation2024_2025.sol"; @@ -25,7 +26,7 @@ contract ZkSyncGuardianCompensationTest is DeployZkSyncGuardianCompensationScript, CreateAllTemplatesScript, ProposeBorgResolutionScript, - ProposeMetaVestDealScript, + ProposeAllGuardiansMetaVestDealScript, SignDealAndCreateMetavestScript, Test { @@ -68,9 +69,6 @@ contract ZkSyncGuardianCompensationTest is deal(bob, 1 ether); deal(chad, 1 ether); - CyberAgreementRegistry registry; - VestingAllocationFactory vestingAllocationFactory; - metavestController controller; GnosisTransaction[] memory safeTxsCreateAllTemplates; GnosisTransaction[] memory safeTxs2024_2025; GnosisTransaction[] memory safeTxs2025_2026; @@ -78,49 +76,77 @@ contract ZkSyncGuardianCompensationTest is config2024_2025 = ZkSyncGuardianCompensation2024_2025.getDefault(vm); config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(vm); - // Update guardian info + // Override guardian info for tests - assertEq(config2024_2025.guardians.length, 1, "Unexpected number of guardians (2024-2025)"); + assertEq(config2024_2025.guardians.length, 6, "Unexpected number of guardians (2024-2025)"); config2024_2025.guardians[0].partyInfo.evmAddress = alice; - assertEq(config2025_2026.guardians.length, 1, "Unexpected number of guardians (2025-2026)"); + assertEq(config2025_2026.guardians.length, 6, "Unexpected number of guardians (2025-2026)"); config2025_2026.guardians[0].partyInfo.evmAddress = alice; - (auth, registry, vestingAllocationFactory) = DeployZkSyncGuardianCompensationPrerequisitesScript.deployPrerequisites( - deployerPrivateKey, - saltStr, - config2024_2025 - ); - - // Update configs with deployed contracts - config2024_2025.registry = registry; - config2024_2025.vestingAllocationFactory = vestingAllocationFactory; - config2025_2026.registry = registry; - config2025_2026.vestingAllocationFactory = vestingAllocationFactory; - - // Deploy 2024-2025 compensation contracts - (controller, safeTxs2024_2025) = DeployZkSyncGuardianCompensationScript.deployCompensation( - deployerPrivateKey, - string(abi.encodePacked(saltStr, ".2024-2025")), - config2024_2025 - ); - config2024_2025.controller = controller; // Update configs with deployed contracts - - // Deploy 2025-2026 compensation contracts - (controller, safeTxs2025_2026) = DeployZkSyncGuardianCompensationScript.deployCompensation( - deployerPrivateKey, - string(abi.encodePacked(saltStr, ".2025-2026")), - config2025_2026 - ); - config2025_2026.controller = controller; // Update configs with deployed contracts - - // Create all templates - safeTxsCreateAllTemplates = CreateAllTemplatesScript.run(config2025_2026); - - // Simulate MetaLeX SAFE to execute txs as instructed - for (uint256 i = 0; i < safeTxsCreateAllTemplates.length; i++) { - vm.prank(address(config2024_2025.metalexSafe)); - (safeTxsCreateAllTemplates[i].to).call{value: safeTxsCreateAllTemplates[i].value}(safeTxsCreateAllTemplates[i].data); - } +// (auth, registry, vestingAllocationFactory) = DeployZkSyncGuardianCompensationPrerequisitesScript.deployPrerequisites( +// deployerPrivateKey, +// saltStr, +// config2024_2025 +// ); +// +// // Update configs with deployed contracts +// config2024_2025.registry = registry; +// config2024_2025.vestingAllocationFactory = vestingAllocationFactory; +// config2025_2026.registry = registry; +// config2025_2026.vestingAllocationFactory = vestingAllocationFactory; +// +// // Deploy 2024-2025 compensation contracts +// (controller, safeTxs2024_2025) = DeployZkSyncGuardianCompensationScript.deployCompensation( +// deployerPrivateKey, +// string(abi.encodePacked(saltStr, ".2024-2025")), +// config2024_2025 +// ); +// config2024_2025.controller = controller; // Update configs with deployed contracts +// +// // Deploy 2025-2026 compensation contracts +// (controller, safeTxs2025_2026) = DeployZkSyncGuardianCompensationScript.deployCompensation( +// deployerPrivateKey, +// string(abi.encodePacked(saltStr, ".2025-2026")), +// config2025_2026 +// ); +// config2025_2026.controller = controller; // Update configs with deployed contracts +// +// // Create all templates +// safeTxsCreateAllTemplates = CreateAllTemplatesScript.run(config2025_2026); +// +// // Simulate MetaLeX SAFE to execute txs as instructed +// for (uint256 i = 0; i < safeTxsCreateAllTemplates.length; i++) { +// vm.prank(address(config2024_2025.metalexSafe)); +// (safeTxsCreateAllTemplates[i].to).call{value: safeTxsCreateAllTemplates[i].value}(safeTxsCreateAllTemplates[i].data); +// } + + // As of 2025/09/02, 2024-2025 & 2025-2026 compensation contracts and templates are all deployed + auth = config2024_2025.registry.AUTH(); + + // From the Safe tx bundle we created + + safeTxs2024_2025 = new GnosisTransaction[](2); + safeTxs2024_2025[0] = GnosisTransaction({ + to: 0xE555FC98E45637D1B45e60E4fc05cF0F22836156, + value: 0, + data: hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000d509349af986e7202f2bc4ae49c203e354faafcd" + }); + safeTxs2024_2025[1] = GnosisTransaction({ + to: 0xD509349AF986E7202f2Bc4ae49C203E354faafCD, + value: 0, + data: hex"66e26184000000000000000000000000e555fc98e45637d1b45e60e4fc05cf0f22836156" + }); + safeTxs2025_2026 = new GnosisTransaction[](2); + safeTxs2025_2026[0] = GnosisTransaction({ + to: 0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A, + value: 0, + data: hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000570d31f59bd0c96a9e1cc889e7e4dbd585d6915b" + }); + safeTxs2025_2026[1] = GnosisTransaction({ + to: 0x570d31F59bD0C96a9e1CC889E7E4dBd585D6915b, + value: 0, + data: hex"66e261840000000000000000000000001358f460bd147c4a6bfdab75ad2b78c837a11d4a" + }); // Simulate Guardian SAFE to execute txs as instructed @@ -135,43 +161,43 @@ contract ZkSyncGuardianCompensationTest is // Simulate MetaLeX SAFE create templates for Guardian Compensation and Service Agreement - vm.startPrank(address(config2024_2025.metalexSafe)); - - registry.createTemplate( - config2024_2025.borgResolutionTemplate.id, - config2024_2025.borgResolutionTemplate.name, - config2024_2025.borgResolutionTemplate.agreementUri, - config2024_2025.borgResolutionTemplate.globalFields, - config2024_2025.borgResolutionTemplate.partyFields - ); - - for (uint256 i = 0; i < config2024_2025.guardians.length; i ++) { - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config2024_2025.guardians[i]; - registry.createTemplate( - guardian.compTemplate.id, - guardian.compTemplate.name, - guardian.compTemplate.agreementUri, - guardian.compTemplate.globalFields, - guardian.compTemplate.partyFields - ); - } - - vm.stopPrank(); +// vm.startPrank(address(config2024_2025.metalexSafe)); +// +// config2024_2025.registry.createTemplate( +// config2024_2025.borgResolutionTemplate.id, +// config2024_2025.borgResolutionTemplate.name, +// config2024_2025.borgResolutionTemplate.agreementUri, +// config2024_2025.borgResolutionTemplate.globalFields, +// config2024_2025.borgResolutionTemplate.partyFields +// ); +// +// for (uint256 i = 0; i < config2024_2025.guardians.length; i ++) { +// ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config2024_2025.guardians[i]; +// config2024_2025.registry.createTemplate( +// guardian.compTemplate.id, +// guardian.compTemplate.name, +// guardian.compTemplate.agreementUri, +// guardian.compTemplate.globalFields, +// guardian.compTemplate.partyFields +// ); +// } +// +// vm.stopPrank(); // Simulate vote pass (https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746) masterMinter = IZkCappedMinterV2(config2024_2025.zkCappedMinter.MINTABLE()); - // Comment out below after vote has been executed as of block 64423211 - vm.startPrank(zkTokenAdmin); - - masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(config2024_2025.zkCappedMinter)); - masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(config2025_2026.zkCappedMinter)); - - IZkCappedMinterV2 grandMasterMinter = IZkCappedMinterV2(masterMinter.MINTABLE()); - grandMasterMinter.grantRole(grandMasterMinter.MINTER_ROLE(), address(masterMinter)); - - vm.stopPrank(); +// // Comment out below after vote has been executed as of block 64423211 +// vm.startPrank(zkTokenAdmin); +// +// masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(config2024_2025.zkCappedMinter)); +// masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(config2025_2026.zkCappedMinter)); +// +// IZkCappedMinterV2 grandMasterMinter = IZkCappedMinterV2(masterMinter.MINTABLE()); +// grandMasterMinter.grantRole(grandMasterMinter.MINTER_ROLE(), address(masterMinter)); +// +// vm.stopPrank(); } function run() public override( @@ -179,7 +205,7 @@ contract ZkSyncGuardianCompensationTest is DeployZkSyncGuardianCompensationScript, CreateAllTemplatesScript, ProposeBorgResolutionScript, - ProposeMetaVestDealScript, + ProposeAllGuardiansMetaVestDealScript, SignDealAndCreateMetavestScript ) { // No-op, we don't use this part of the scripts @@ -240,7 +266,7 @@ contract ZkSyncGuardianCompensationTest is assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); // Run scripts to propose deals - bytes32 agreementId = ProposeMetaVestDealScript.runSingle( + bytes32 agreementId = ProposeAllGuardiansMetaVestDealScript.runSingle( guardianDelegatePrivateKey, config2024_2025.guardians[0], // alice config2024_2025 @@ -321,7 +347,7 @@ contract ZkSyncGuardianCompensationTest is evmAddress: chad }) }); - bytes32 contractIdChad = ProposeMetaVestDealScript.runSingle( + bytes32 contractIdChad = ProposeAllGuardiansMetaVestDealScript.runSingle( guardianDelegatePrivateKey, chadInfo, BaseAllocation.Allocation({ @@ -354,30 +380,39 @@ contract ZkSyncGuardianCompensationTest is // Run scripts to propose deals - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory aliceInfo = config2024_2025.guardians[0]; - - bytes32 contractIdAlice2024_2025 = ProposeMetaVestDealScript.runSingle( + bytes32[] memory agreementIds2024_2025 = ProposeAllGuardiansMetaVestDealScript.runAll( guardianDelegatePrivateKey, - aliceInfo, config2024_2025 ); - bytes32 contractIdAlice2025_2026 = ProposeMetaVestDealScript.runSingle( + bytes32[] memory agreementIds2025_2026 = ProposeAllGuardiansMetaVestDealScript.runAll( guardianDelegatePrivateKey, - aliceInfo, config2025_2026 ); + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory aliceInfo = config2024_2025.guardians[0]; + +// bytes32 contractIdAlice2024_2025 = ProposeMetaVestDealScript.run( +// guardianDelegatePrivateKey, +// aliceInfo, +// config2024_2025 +// ); +// bytes32 contractIdAlice2025_2026 = ProposeMetaVestDealScript.run( +// guardianDelegatePrivateKey, +// aliceInfo, +// config2025_2026 +// ); + // Simulate guardian counter-sign and finalize the deal address metavestAlice2024_2025 = SignDealAndCreateMetavestScript.run( alicePrivateKey, - contractIdAlice2024_2025, + agreementIds2024_2025[0], aliceInfo, config2024_2025 ); address metavestAlice2025_2026 = SignDealAndCreateMetavestScript.run( alicePrivateKey, - contractIdAlice2025_2026, + agreementIds2025_2026[0], aliceInfo, config2025_2026 ); From 89f098ecd88c158b71f1cada92a40b5ee2c49108 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 10 Sep 2025 11:37:36 -0700 Subject: [PATCH 59/68] chore: load guardian SAFE delegate signatures externally if necessary --- .../ZkSyncGuardianCompensation2024_2025.sol | 4 +- .../proposeAllGuardiansMetavestDeals.s.sol | 9 ++++- scripts/proposeMetavestDeal.s.sol | 39 ++++++++++++------- test/ZkSyncGuardianCompensation.t.sol | 7 +++- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol index f826023..eeae859 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -61,6 +61,7 @@ library ZkSyncGuardianCompensation2024_2025 { struct GuardianCompInfo { PartyInfo partyInfo; TemplateInfo compTemplate; + bytes signature; } function getDefault(Vm vm) internal view returns(Config memory) { @@ -152,7 +153,8 @@ library ZkSyncGuardianCompensation2024_2025 { name: vm.envString(string(abi.encodePacked("GUARDIAN_TEMPLATE_NAME_", vm.toString(i)))), globalFields: compGlobalFields, partyFields: compPartyFields - }) + }), + signature: vm.envBytes(string(abi.encodePacked("GUARDIAN_SAFE_DELEGATE_SIGNATURE_", vm.toString(i)))) }); } return guardians; diff --git a/scripts/proposeAllGuardiansMetavestDeals.s.sol b/scripts/proposeAllGuardiansMetavestDeals.s.sol index b7d4a0c..4be93a3 100644 --- a/scripts/proposeAllGuardiansMetavestDeals.s.sol +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -23,7 +23,8 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual override { runAll( - vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), + vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey + vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), // guardianSafeDelegatePrivateKey // zkSync Sepolia for 2024-2025 ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) @@ -33,6 +34,7 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { /// @dev For running in tests function runAll( uint256 proposerPrivateKey, + uint256 guardianSafeDelegatePrivateKey, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32[] memory) { @@ -56,6 +58,7 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { agreementIds[i] = runSingle( proposerPrivateKey, + guardianSafeDelegatePrivateKey, config.guardians[i], config ); @@ -73,11 +76,13 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { function runSingle( uint256 proposerPrivateKey, + uint256 guardianSafeDelegatePrivateKey, ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, ZkSyncGuardianCompensation2024_2025.Config memory config ) public override returns(bytes32) { return ProposeMetaVestDealScript.runSingle( proposerPrivateKey, + guardianSafeDelegatePrivateKey, guardianInfo, config ); @@ -85,12 +90,14 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { function runSingle( uint256 proposerPrivateKey, + uint256 guardianSafeDelegatePrivateKey, ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, BaseAllocation.Allocation memory allocation, ZkSyncGuardianCompensation2024_2025.Config memory config ) public override returns(bytes32) { return ProposeMetaVestDealScript.runSingle( proposerPrivateKey, + guardianSafeDelegatePrivateKey, guardianInfo, allocation, config diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index 95911cf..5fd3bb5 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -24,7 +24,8 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { function run() public virtual { ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(vm); runSingle( - vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), + vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey + vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), // guardianSafeDelegatePrivateKey defaultConfig.guardians[0], defaultConfig ); @@ -33,11 +34,14 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running in tests function runSingle( uint256 proposerPrivateKey, + uint256 guardianSafeDelegatePrivateKey, ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { return runSingle( - proposerPrivateKey, guardianInfo, + proposerPrivateKey, + guardianSafeDelegatePrivateKey, + guardianInfo, // Default guardian allocations config.parseAllocation(), config @@ -47,16 +51,21 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running in tests function runSingle( uint256 proposerPrivateKey, + uint256 guardianSafeDelegatePrivateKey, ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, BaseAllocation.Allocation memory allocation, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { + address guardianSafeDelegate = guardianSafeDelegatePrivateKey != 0 + ? vm.addr(guardianSafeDelegatePrivateKey) + : address(0); address proposer = vm.addr(proposerPrivateKey); console2.log(""); console2.log("=== ProposeMetaVestDealScript ==="); console2.log("Proposer: ", proposer); + console2.log("Guardian SAFE Delegate (if private key available): ", guardianSafeDelegate); console2.log("Guardian Safe: ", address(config.guardianSafe)); console2.log("ZK token: ", address(config.zkToken)); console2.log("CyberAgreementRegistry: ", address(config.registry)); @@ -95,18 +104,20 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { (string memory agreementUri, ) = config.registry.templates(guardianInfo.compTemplate.id); - bytes memory signature = CyberAgreementUtils.signAgreementTypedData( - vm, - config.registry.DOMAIN_SEPARATOR(), - config.registry.SIGNATUREDATA_TYPEHASH(), - expectedContractId, - agreementUri, - guardianInfo.compTemplate.globalFields, - guardianInfo.compTemplate.partyFields, - globalValues, - partyValues[0], - proposerPrivateKey - ); + bytes memory signature = (guardianSafeDelegatePrivateKey != 0) + ? CyberAgreementUtils.signAgreementTypedData( + vm, + config.registry.DOMAIN_SEPARATOR(), + config.registry.SIGNATUREDATA_TYPEHASH(), + expectedContractId, + agreementUri, + guardianInfo.compTemplate.globalFields, + guardianInfo.compTemplate.partyFields, + globalValues, + partyValues[0], + guardianSafeDelegatePrivateKey + ) + : guardianInfo.signature; vm.startBroadcast(proposerPrivateKey); diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 145ca45..7a9ff2b 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -267,6 +267,7 @@ contract ZkSyncGuardianCompensationTest is // Run scripts to propose deals bytes32 agreementId = ProposeAllGuardiansMetaVestDealScript.runSingle( + deployerPrivateKey, guardianDelegatePrivateKey, config2024_2025.guardians[0], // alice config2024_2025 @@ -345,9 +346,11 @@ contract ZkSyncGuardianCompensationTest is partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Chad", evmAddress: chad - }) + }), + signature: "" }); bytes32 contractIdChad = ProposeAllGuardiansMetaVestDealScript.runSingle( + deployerPrivateKey, guardianDelegatePrivateKey, chadInfo, BaseAllocation.Allocation({ @@ -381,10 +384,12 @@ contract ZkSyncGuardianCompensationTest is // Run scripts to propose deals bytes32[] memory agreementIds2024_2025 = ProposeAllGuardiansMetaVestDealScript.runAll( + deployerPrivateKey, guardianDelegatePrivateKey, config2024_2025 ); bytes32[] memory agreementIds2025_2026 = ProposeAllGuardiansMetaVestDealScript.runAll( + deployerPrivateKey, guardianDelegatePrivateKey, config2025_2026 ); From d09b674af8292ef7d45998bbaa503864f48d6479 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 10 Sep 2025 14:40:08 -0700 Subject: [PATCH 60/68] chore: allow offline agreement signing --- scripts/executeSafeTx.s.sol | 4 +- scripts/lib/CyberAgreementUtils.sol | 86 ++++++++++++++++--- .../proposeAllGuardiansMetavestDeals.s.sol | 9 +- scripts/proposeMetavestDeal.s.sol | 84 +++++++++++------- test/ZkSyncGuardianCompensation.t.sol | 4 + 5 files changed, 141 insertions(+), 46 deletions(-) diff --git a/scripts/executeSafeTx.s.sol b/scripts/executeSafeTx.s.sol index f16d1a2..d7829ac 100644 --- a/scripts/executeSafeTx.s.sol +++ b/scripts/executeSafeTx.s.sol @@ -36,8 +36,8 @@ contract ExecuteSafeTxScript is SafeTxHelper, Script { 0, // value abi.encodeWithSelector( CyberAgreementRegistry.setDelegation.selector, - 0x5ff4e90Efa2B88cf3cA92D63d244a78a88219Abf, - block.timestamp + 365 days * 3 // This is a hack, one should not delegate signing for this long + 0xD63383fBf9F3FDf3759acA89dA00c4c0CF3A0865, + 1765401725 ) // data ); } diff --git a/scripts/lib/CyberAgreementUtils.sol b/scripts/lib/CyberAgreementUtils.sol index 7c83107..d945ef5 100644 --- a/scripts/lib/CyberAgreementUtils.sol +++ b/scripts/lib/CyberAgreementUtils.sol @@ -42,12 +42,38 @@ except with the express prior written permission of the copyright holder.*/ pragma solidity 0.8.28; import {Vm} from "forge-std/Test.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; + +// Access hidden cheatcodes +interface EnhancedVm is Vm { + function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json); +} library CyberAgreementUtils { + EnhancedVm constant vm = EnhancedVm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + // Hard-coded since we don't have programmatic access to CyberAgreementRegistry's underlying types + string constant DOMAIN_SEPARATOR_TYPE = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; + string constant SIGNATUREDATA_TYPE = "SignatureData(bytes32 contractId,string legalContractUri,string[] globalFields,string[] partyFields,string[] globalValues,string[] partyValues)"; + + struct DomainSeparator { + string name; + string version; + uint256 chainId; + address verifyingContract; + } + + struct SignatureData { + bytes32 contractId; + string legalContractUri; + string[] globalFields; + string[] partyFields; + string[] globalValues; + string[] partyValues; + } + function signAgreementTypedData( - Vm vm, - bytes32 _domainSeparator, - bytes32 _typeHash, + CyberAgreementRegistry registry, bytes32 contractId, string memory contractUri, string[] memory globalFields, @@ -55,7 +81,7 @@ library CyberAgreementUtils { string[] memory globalValues, string[] memory partyValues, uint256 privKey - ) internal pure returns (bytes memory signature) { + ) internal view returns (bytes memory signature) { // Hash string arrays the same way as the contract bytes32 contractUriHash = keccak256(bytes(contractUri)); bytes32 globalFieldsHash = _hashStringArray(globalFields); @@ -66,7 +92,7 @@ library CyberAgreementUtils { // Create the message hash using the same approach as the contract bytes32 structHash = keccak256( abi.encode( - _typeHash, + registry.SIGNATUREDATA_TYPEHASH(), contractId, contractUriHash, globalFieldsHash, @@ -77,7 +103,7 @@ library CyberAgreementUtils { ); bytes32 digest = keccak256( - abi.encodePacked("\x19\x01", _domainSeparator, structHash) + abi.encodePacked("\x19\x01", registry.DOMAIN_SEPARATOR(), structHash) ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, digest); @@ -86,24 +112,22 @@ library CyberAgreementUtils { } function signVoidTypedData( - Vm vm, - bytes32 _domainSeparator, - bytes32 _typeHash, + CyberAgreementRegistry registry, bytes32 contractId, address party, uint256 privKey - ) internal pure returns (bytes memory signature) { + ) internal view returns (bytes memory signature) { // Create the message hash using the same approach as the contract bytes32 structHash = keccak256( abi.encode( - _typeHash, + registry.VOIDSIGNATUREDATA_TYPEHASH(), contractId, party ) ); bytes32 digest = keccak256( - abi.encodePacked("\x19\x01", _domainSeparator, structHash) + abi.encodePacked("\x19\x01", registry.DOMAIN_SEPARATOR(), structHash) ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, digest); @@ -121,4 +145,42 @@ library CyberAgreementUtils { } return keccak256(abi.encodePacked(hashes)); } + + function formatAgreementTypedDataJson( + CyberAgreementRegistry registry, + bytes32 contractId, + string memory contractUri, + string[] memory globalFields, + string[] memory partyFields, + string[] memory globalValues, + string[] memory partyValues + ) internal returns (string memory) { + string memory domainSeparatorJson = vm.serializeJsonType( + DOMAIN_SEPARATOR_TYPE, + abi.encode(DomainSeparator({ + name: registry.name(), + version: registry.version(), + chainId: block.chainid, + verifyingContract: address(registry) + })) + ); + + string memory signatureDataJson = vm.serializeJsonType( + SIGNATUREDATA_TYPE, + abi.encode(SignatureData({ + contractId: contractId, + legalContractUri: contractUri, + globalFields: globalFields, + partyFields: partyFields, + globalValues: globalValues, + partyValues: partyValues + })) + ); + + // Build the json string with the temporary buffer at key "outputKey" + vm.serializeString("outputKey", "domain", domainSeparatorJson); + vm.serializeString("outputKey", "message", signatureDataJson); + vm.serializeString("outputKey", "primaryType", "SignatureData"); + return vm.serializeString("outputKey", "types", "{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"SignatureData\":[{\"name\":\"contractId\",\"type\":\"bytes32\"},{\"name\":\"legalContractUri\",\"type\":\"string\"},{\"name\":\"globalFields\",\"type\":\"string[]\"},{\"name\":\"partyFields\",\"type\":\"string[]\"},{\"name\":\"globalValues\",\"type\":\"string[]\"},{\"name\":\"partyValues\",\"type\":\"string[]\"}]}"); + } } diff --git a/scripts/proposeAllGuardiansMetavestDeals.s.sol b/scripts/proposeAllGuardiansMetavestDeals.s.sol index 4be93a3..2b012b3 100644 --- a/scripts/proposeAllGuardiansMetavestDeals.s.sol +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -24,7 +24,8 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { function run() public virtual override { runAll( vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey - vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), // guardianSafeDelegatePrivateKey + vm.envOr("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY", uint256(0)), // guardianSafeDelegatePrivateKey + vm.envUint("AGREEMENT_SALT"), // agreementSalt // zkSync Sepolia for 2024-2025 ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) @@ -35,6 +36,7 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { function runAll( uint256 proposerPrivateKey, uint256 guardianSafeDelegatePrivateKey, + uint256 agreementSalt, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32[] memory) { @@ -60,6 +62,7 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { proposerPrivateKey, guardianSafeDelegatePrivateKey, config.guardians[i], + agreementSalt, config ); } @@ -78,12 +81,14 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { uint256 proposerPrivateKey, uint256 guardianSafeDelegatePrivateKey, ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 agreementSalt, ZkSyncGuardianCompensation2024_2025.Config memory config ) public override returns(bytes32) { return ProposeMetaVestDealScript.runSingle( proposerPrivateKey, guardianSafeDelegatePrivateKey, guardianInfo, + agreementSalt, config ); } @@ -92,6 +97,7 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { uint256 proposerPrivateKey, uint256 guardianSafeDelegatePrivateKey, ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 agreementSalt, BaseAllocation.Allocation memory allocation, ZkSyncGuardianCompensation2024_2025.Config memory config ) public override returns(bytes32) { @@ -99,6 +105,7 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { proposerPrivateKey, guardianSafeDelegatePrivateKey, guardianInfo, + agreementSalt, allocation, config ); diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index 5fd3bb5..eeb1705 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -2,13 +2,14 @@ pragma solidity ^0.8.24; import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; -import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreementUtils.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {CyberAgreementUtils} from "./lib/CyberAgreementUtils.sol"; import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; import {Script} from "forge-std/Script.sol"; import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; @@ -22,11 +23,12 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { - ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(vm); + ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm); runSingle( vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey - vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), // guardianSafeDelegatePrivateKey + vm.envOr("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY", uint256(0)), // guardianSafeDelegatePrivateKey defaultConfig.guardians[0], + vm.envUint("AGREEMENT_SALT"), // agreementSalt defaultConfig ); } @@ -36,12 +38,14 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { uint256 proposerPrivateKey, uint256 guardianSafeDelegatePrivateKey, ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 agreementSalt, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { return runSingle( proposerPrivateKey, guardianSafeDelegatePrivateKey, guardianInfo, + agreementSalt, // Default guardian allocations config.parseAllocation(), config @@ -53,6 +57,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { uint256 proposerPrivateKey, uint256 guardianSafeDelegatePrivateKey, ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 agreementSalt, BaseAllocation.Allocation memory allocation, ZkSyncGuardianCompensation2024_2025.Config memory config ) public virtual returns(bytes32) { @@ -91,8 +96,6 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { guardianInfo.partyInfo ); - uint256 agreementSalt = block.timestamp; - bytes32 expectedContractId = keccak256( abi.encode( guardianInfo.compTemplate.id, @@ -106,9 +109,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { bytes memory signature = (guardianSafeDelegatePrivateKey != 0) ? CyberAgreementUtils.signAgreementTypedData( - vm, - config.registry.DOMAIN_SEPARATOR(), - config.registry.SIGNATUREDATA_TYPEHASH(), + config.registry, expectedContractId, agreementUri, guardianInfo.compTemplate.globalFields, @@ -119,30 +120,51 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { ) : guardianInfo.signature; - vm.startBroadcast(proposerPrivateKey); + if (signature.length > 0) { + // Has valid signature, proceed to proposal + vm.startBroadcast(proposerPrivateKey); - bytes32 contractId = config.controller.proposeAndSignDeal( - guardianInfo.compTemplate.id, - agreementSalt, - metavestController.metavestType.Vesting, - guardianInfo.partyInfo.evmAddress, - allocation, - config.milestones, - globalValues, - parties, - partyValues, - signature, - bytes32(0), // no secrets - block.timestamp + 365 days * 2 // 2 years after deployment - ); - - vm.stopBroadcast(); - - console2.log("Created:"); - console2.log(" Agreement ID:"); - console2.logBytes32(contractId); - console2.log(""); + bytes32 contractId = config.controller.proposeAndSignDeal( + guardianInfo.compTemplate.id, + agreementSalt, + metavestController.metavestType.Vesting, + guardianInfo.partyInfo.evmAddress, + allocation, + config.milestones, + globalValues, + parties, + partyValues, + signature, + bytes32(0), // no secrets + block.timestamp + 365 days * 2 // 2 years after deployment + ); + + vm.stopBroadcast(); + + console2.log("Created:"); + console2.log(" Agreement ID:"); + console2.logBytes32(contractId); + console2.log(""); + + return contractId; + + } else { + // Does not have valid signature, prompt for offline signing + console2.log("Signature required: please sign the following EIP-712 typed data:"); + console2.log(" (can be signed with command `cast wallet sign --data ''`)"); + console2.log("==== JSON data start ===="); + console2.log(CyberAgreementUtils.formatAgreementTypedDataJson( + config.registry, + expectedContractId, + agreementUri, + guardianInfo.compTemplate.globalFields, + guardianInfo.compTemplate.partyFields, + globalValues, + partyValues[0] + )); + console2.log("==== JSON data end ===="); - return contractId; + return bytes32(0); + } } } diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 7a9ff2b..8b82a44 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -270,6 +270,7 @@ contract ZkSyncGuardianCompensationTest is deployerPrivateKey, guardianDelegatePrivateKey, config2024_2025.guardians[0], // alice + block.timestamp, // agreementSalt config2024_2025 ); @@ -353,6 +354,7 @@ contract ZkSyncGuardianCompensationTest is deployerPrivateKey, guardianDelegatePrivateKey, chadInfo, + block.timestamp, // agreementSalt BaseAllocation.Allocation({ tokenContract: address(config2024_2025.zkToken), // 10k ZK total in one cliff @@ -386,11 +388,13 @@ contract ZkSyncGuardianCompensationTest is bytes32[] memory agreementIds2024_2025 = ProposeAllGuardiansMetaVestDealScript.runAll( deployerPrivateKey, guardianDelegatePrivateKey, + block.timestamp, // agreementSalt config2024_2025 ); bytes32[] memory agreementIds2025_2026 = ProposeAllGuardiansMetaVestDealScript.runAll( deployerPrivateKey, guardianDelegatePrivateKey, + block.timestamp, // agreementSalt config2025_2026 ); From 90d2de2f6c4037b9f3b853dcdae5786142de2146 Mon Sep 17 00:00:00 2001 From: detoo Date: Wed, 10 Sep 2025 18:06:23 -0700 Subject: [PATCH 61/68] chore: separate tests with fresh deployment vs existing deployment --- scripts/createAllTemplates.s.sol | 47 +++- .../ZkSyncGuardianCompensation2024_2025.sol | 91 ++++--- .../ZkSyncGuardianCompensation2025_2026.sol | 7 +- ...ncGuardianCompensationSepolia2024_2025.sol | 7 +- test/ZkSyncGuardianCompensation.t.sol | 253 ++++++++---------- 5 files changed, 204 insertions(+), 201 deletions(-) diff --git a/scripts/createAllTemplates.s.sol b/scripts/createAllTemplates.s.sol index 052b90e..31cbe86 100644 --- a/scripts/createAllTemplates.s.sol +++ b/scripts/createAllTemplates.s.sol @@ -33,22 +33,41 @@ contract CreateAllTemplatesScript is SafeTxHelper, Script { config = ZkSyncGuardianCompensation2024_2025.getDefault(vm); safe = config.guardianSafe; - GnosisTransaction[] memory safeTxs = new GnosisTransaction[](config.guardians.length + 1); - safeTxs[0] = GnosisTransaction({ - to: address(config.registry), - value: 0 ether, - data: abi.encodeWithSelector( - CyberAgreementRegistry.createTemplate.selector, - config.borgResolutionTemplate.id, - config.borgResolutionTemplate.name, - config.borgResolutionTemplate.agreementUri, - config.borgResolutionTemplate.globalFields, - config.borgResolutionTemplate.partyFields - ) - }); + + // TODO deprecated +// GnosisTransaction[] memory safeTxs = new GnosisTransaction[](config.guardians.length + 1); +// safeTxs[0] = GnosisTransaction({ +// to: address(config.registry), +// value: 0 ether, +// data: abi.encodeWithSelector( +// CyberAgreementRegistry.createTemplate.selector, +// config.borgResolutionTemplate.id, +// config.borgResolutionTemplate.name, +// config.borgResolutionTemplate.agreementUri, +// config.borgResolutionTemplate.globalFields, +// config.borgResolutionTemplate.partyFields +// ) +// }); +// for (uint i = 0; i < config.guardians.length ; i++) { +// ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config.guardians[i]; +// safeTxs[i + 1] = GnosisTransaction({ +// to: address(config.registry), +// value: 0 ether, +// data: abi.encodeWithSelector( +// CyberAgreementRegistry.createTemplate.selector, +// guardian.compTemplate.id, +// guardian.compTemplate.name, +// guardian.compTemplate.agreementUri, +// guardian.compTemplate.globalFields, +// guardian.compTemplate.partyFields +// ) +// }); +// } + + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](config.guardians.length); for (uint i = 0; i < config.guardians.length ; i++) { ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config.guardians[i]; - safeTxs[i + 1] = GnosisTransaction({ + safeTxs[i] = GnosisTransaction({ to: address(config.registry), value: 0 ether, data: abi.encodeWithSelector( diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol index eeae859..2ec0cfc 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -33,9 +33,10 @@ library ZkSyncGuardianCompensation2024_2025 { VestingAllocationFactory vestingAllocationFactory; metavestController controller; - // zkSync Guardian BORG Resolution - - TemplateInfo borgResolutionTemplate; + // TODO deprecated +// // zkSync Guardian BORG Resolution +// +// TemplateInfo borgResolutionTemplate; // zkSync Guardian Compensation Agreement (one template per guardian for now) @@ -91,9 +92,10 @@ library ZkSyncGuardianCompensation2024_2025 { vestingAllocationFactory: VestingAllocationFactory(0xD1c48D8866d81CC0f6567f3E3fbbb8eF48E30D99), controller: metavestController(0xD509349AF986E7202f2Bc4ae49C203E354faafCD), - // zkSync Guardian BORG Resolution - - borgResolutionTemplate: loadBorgResolutionTemplate(vm), + // TODO deprecated +// // zkSync Guardian BORG Resolution +// +// borgResolutionTemplate: loadBorgResolutionTemplate(vm), // zkSync Guardian Compensation Agreement @@ -104,41 +106,25 @@ library ZkSyncGuardianCompensation2024_2025 { }); } - function loadBorgResolutionTemplate(Vm vm) internal view returns(TemplateInfo memory) { - string[] memory borgResolutionGlobalFields = new string[](0); - - string[] memory borgResolutionPartyFields = new string[](2); - borgResolutionPartyFields[0] = "name"; - borgResolutionPartyFields[1] = "evmAddress"; - - return TemplateInfo({ - id: bytes32(vm.envUint("BORG_RESOLUTION_TEMPLATE_ID")), - agreementUri: vm.envString("BORG_RESOLUTION_URI"), - name: vm.envString("BORG_RESOLUTION_TEMPLATE_NAME"), - globalFields: borgResolutionGlobalFields, - partyFields: borgResolutionPartyFields - }); - } + // TODO deprecated +// function loadBorgResolutionTemplate(Vm vm) internal view returns(TemplateInfo memory) { +// string[] memory borgResolutionGlobalFields = new string[](0); +// +// string[] memory borgResolutionPartyFields = new string[](2); +// borgResolutionPartyFields[0] = "name"; +// borgResolutionPartyFields[1] = "evmAddress"; +// +// return TemplateInfo({ +// id: bytes32(vm.envUint("BORG_RESOLUTION_TEMPLATE_ID")), +// agreementUri: vm.envString("BORG_RESOLUTION_URI"), +// name: vm.envString("BORG_RESOLUTION_TEMPLATE_NAME"), +// globalFields: borgResolutionGlobalFields, +// partyFields: borgResolutionPartyFields +// }); +// } function loadGuardianAndComps(Vm vm) internal view returns(GuardianCompInfo[] memory) { - string[] memory compGlobalFields = new string[](11); - compGlobalFields[0] = "metavestType"; - compGlobalFields[1] = "grantor"; - compGlobalFields[2] = "grantee"; - compGlobalFields[3] = "tokenContract"; - compGlobalFields[4] = "tokenStreamTotal"; - compGlobalFields[5] = "vestingCliffCredit"; - compGlobalFields[6] = "unlockingCliffCredit"; - compGlobalFields[7] = "vestingRate"; - compGlobalFields[8] = "vestingStartTime"; - compGlobalFields[9] = "unlockRate"; - compGlobalFields[10] = "unlockStartTime"; - - string[] memory compPartyFields = new string[](2); - compPartyFields[0] = "name"; - compPartyFields[1] = "evmAddress"; - - uint256 numGuardians = vm.envUint("NUM_GUARDIANS"); + uint256 numGuardians = vm.envOr("NUM_GUARDIANS", uint256(0)); GuardianCompInfo[] memory guardians = new GuardianCompInfo[](numGuardians); for (uint i = 0; i < guardians.length ; i++) { @@ -151,8 +137,8 @@ library ZkSyncGuardianCompensation2024_2025 { id: bytes32(vm.envUint(string(abi.encodePacked("GUARDIAN_TEMPLATE_ID_", vm.toString(i))))), agreementUri: vm.envString(string(abi.encodePacked("GUARDIAN_AGREEMENT_URI_", vm.toString(i)))), name: vm.envString(string(abi.encodePacked("GUARDIAN_TEMPLATE_NAME_", vm.toString(i)))), - globalFields: compGlobalFields, - partyFields: compPartyFields + globalFields: getCompGlobalFields(), + partyFields: getCompPartyFields() }), signature: vm.envBytes(string(abi.encodePacked("GUARDIAN_SAFE_DELEGATE_SIGNATURE_", vm.toString(i)))) }); @@ -174,6 +160,29 @@ library ZkSyncGuardianCompensation2024_2025 { }); } + function getCompGlobalFields() internal pure returns(string[] memory) { + string[] memory compGlobalFields = new string[](11); + compGlobalFields[0] = "metavestType"; + compGlobalFields[1] = "grantor"; + compGlobalFields[2] = "grantee"; + compGlobalFields[3] = "tokenContract"; + compGlobalFields[4] = "tokenStreamTotal"; + compGlobalFields[5] = "vestingCliffCredit"; + compGlobalFields[6] = "unlockingCliffCredit"; + compGlobalFields[7] = "vestingRate"; + compGlobalFields[8] = "vestingStartTime"; + compGlobalFields[9] = "unlockRate"; + compGlobalFields[10] = "unlockStartTime"; + return compGlobalFields; + } + + function getCompPartyFields() internal pure returns(string[] memory) { + string[] memory compPartyFields = new string[](2); + compPartyFields[0] = "name"; + compPartyFields[1] = "evmAddress"; + return compPartyFields; + } + function formatCompGlobalValues( Config memory config, Vm vm, diff --git a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol index 7879143..f49a7c0 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol +++ b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol @@ -38,9 +38,10 @@ library ZkSyncGuardianCompensation2025_2026 { vestingAllocationFactory: defaultConfig.vestingAllocationFactory, controller: metavestController(0x570d31F59bD0C96a9e1CC889E7E4dBd585D6915b), - // zkSync Guardian BORG Resolution - - borgResolutionTemplate: defaultConfig.borgResolutionTemplate, + // TODO deprecated +// // zkSync Guardian BORG Resolution +// +// borgResolutionTemplate: defaultConfig.borgResolutionTemplate, // zkSync Guardian Compensation Agreement diff --git a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol index fe69efd..5e7d447 100644 --- a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol +++ b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol @@ -43,9 +43,10 @@ library ZkSyncGuardianCompensationSepolia2024_2025 { vestingAllocationFactory: VestingAllocationFactory(0x3fFd990dB0E398235456A720501E6007003a6cdf), controller: metavestController(0x856A8Aea8a37A338e2490384Bb790cD87b5CaaE4), - // zkSync Guardian BORG Resolution - - borgResolutionTemplate: ZkSyncGuardianCompensation2024_2025.loadBorgResolutionTemplate(vm), + // TODO deprecated +// // zkSync Guardian BORG Resolution +// +// borgResolutionTemplate: ZkSyncGuardianCompensation2024_2025.loadBorgResolutionTemplate(vm), // zkSync Guardian Compensation Agreement diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 8b82a44..8548733 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -12,7 +12,7 @@ import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementReg import {DeployZkSyncGuardianCompensationPrerequisitesScript} from "../scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol"; import {DeployZkSyncGuardianCompensationScript} from "../scripts/deployZkSyncGuardianCompensation.s.sol"; import {CreateAllTemplatesScript} from "../scripts/createAllTemplates.s.sol"; -import {ProposeBorgResolutionScript} from "../scripts/proposeBorgResolution.s.sol"; +//import {ProposeBorgResolutionScript} from "../scripts/proposeBorgResolution.s.sol"; import {ProposeAllGuardiansMetaVestDealScript} from "../scripts/proposeAllGuardiansMetavestDeals.s.sol"; import {ProposeMetaVestDealScript} from "../scripts/proposeMetavestDeal.s.sol"; import {SignDealAndCreateMetavestScript} from "../scripts/signDealAndCreateMetavest.s.sol"; @@ -20,12 +20,14 @@ import {ZkSyncGuardianCompensation2024_2025} from "../scripts/lib/ZkSyncGuardian import {ZkSyncGuardianCompensation2025_2026} from "../scripts/lib/ZkSyncGuardianCompensation2025_2026.sol"; import {GnosisTransaction} from "./lib/safe.sol"; -// Test by forge test --zksync --via-ir +// Test with fresh deployment (except third-party dependencies) +// - Use third-party dependencies on zkSync Era mainnet +// - Does not need to be run with environment variables contract ZkSyncGuardianCompensationTest is DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensationScript, CreateAllTemplatesScript, - ProposeBorgResolutionScript, +// ProposeBorgResolutionScript, ProposeAllGuardiansMetaVestDealScript, SignDealAndCreateMetavestScript, Test @@ -56,6 +58,8 @@ contract ZkSyncGuardianCompensationTest is ZkSyncGuardianCompensation2024_2025.Config config2024_2025; ZkSyncGuardianCompensation2024_2025.Config config2025_2026; + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo aliceInfo; + BorgAuth auth; metavestController controller2024_2025; metavestController controller2025_2026; @@ -69,6 +73,9 @@ contract ZkSyncGuardianCompensationTest is deal(bob, 1 ether); deal(chad, 1 ether); + CyberAgreementRegistry registry; + VestingAllocationFactory vestingAllocationFactory; + metavestController controller; GnosisTransaction[] memory safeTxsCreateAllTemplates; GnosisTransaction[] memory safeTxs2024_2025; GnosisTransaction[] memory safeTxs2025_2026; @@ -77,76 +84,61 @@ contract ZkSyncGuardianCompensationTest is config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(vm); // Override guardian info for tests - - assertEq(config2024_2025.guardians.length, 6, "Unexpected number of guardians (2024-2025)"); - config2024_2025.guardians[0].partyInfo.evmAddress = alice; - assertEq(config2025_2026.guardians.length, 6, "Unexpected number of guardians (2025-2026)"); - config2025_2026.guardians[0].partyInfo.evmAddress = alice; - -// (auth, registry, vestingAllocationFactory) = DeployZkSyncGuardianCompensationPrerequisitesScript.deployPrerequisites( -// deployerPrivateKey, -// saltStr, -// config2024_2025 -// ); -// -// // Update configs with deployed contracts -// config2024_2025.registry = registry; -// config2024_2025.vestingAllocationFactory = vestingAllocationFactory; -// config2025_2026.registry = registry; -// config2025_2026.vestingAllocationFactory = vestingAllocationFactory; -// -// // Deploy 2024-2025 compensation contracts -// (controller, safeTxs2024_2025) = DeployZkSyncGuardianCompensationScript.deployCompensation( -// deployerPrivateKey, -// string(abi.encodePacked(saltStr, ".2024-2025")), -// config2024_2025 -// ); -// config2024_2025.controller = controller; // Update configs with deployed contracts -// -// // Deploy 2025-2026 compensation contracts -// (controller, safeTxs2025_2026) = DeployZkSyncGuardianCompensationScript.deployCompensation( -// deployerPrivateKey, -// string(abi.encodePacked(saltStr, ".2025-2026")), -// config2025_2026 -// ); -// config2025_2026.controller = controller; // Update configs with deployed contracts -// -// // Create all templates -// safeTxsCreateAllTemplates = CreateAllTemplatesScript.run(config2025_2026); -// -// // Simulate MetaLeX SAFE to execute txs as instructed -// for (uint256 i = 0; i < safeTxsCreateAllTemplates.length; i++) { -// vm.prank(address(config2024_2025.metalexSafe)); -// (safeTxsCreateAllTemplates[i].to).call{value: safeTxsCreateAllTemplates[i].value}(safeTxsCreateAllTemplates[i].data); -// } - - // As of 2025/09/02, 2024-2025 & 2025-2026 compensation contracts and templates are all deployed - auth = config2024_2025.registry.AUTH(); - - // From the Safe tx bundle we created - - safeTxs2024_2025 = new GnosisTransaction[](2); - safeTxs2024_2025[0] = GnosisTransaction({ - to: 0xE555FC98E45637D1B45e60E4fc05cF0F22836156, - value: 0, - data: hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000d509349af986e7202f2bc4ae49c203e354faafcd" - }); - safeTxs2024_2025[1] = GnosisTransaction({ - to: 0xD509349AF986E7202f2Bc4ae49C203E354faafCD, - value: 0, - data: hex"66e26184000000000000000000000000e555fc98e45637d1b45e60e4fc05cf0f22836156" - }); - safeTxs2025_2026 = new GnosisTransaction[](2); - safeTxs2025_2026[0] = GnosisTransaction({ - to: 0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A, - value: 0, - data: hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000570d31f59bd0c96a9e1cc889e7e4dbd585d6915b" - }); - safeTxs2025_2026[1] = GnosisTransaction({ - to: 0x570d31F59bD0C96a9e1CC889E7E4dBd585D6915b, - value: 0, - data: hex"66e261840000000000000000000000001358f460bd147c4a6bfdab75ad2b78c837a11d4a" + aliceInfo = aliceInfo = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ + partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ + name: "Alice", + evmAddress: alice + }), + compTemplate: ZkSyncGuardianCompensation2024_2025.TemplateInfo({ + id: bytes32(uint256(999001)), + agreementUri: "ipfs://bafkreidefnk2tf6req4tn3bya7pkfkt45i6cppmannb5fz7ncv6mfg6vj4", + name: "Alice template", + globalFields: ZkSyncGuardianCompensation2024_2025.getCompGlobalFields(), + partyFields: ZkSyncGuardianCompensation2024_2025.getCompPartyFields() + }), + signature: "" }); + config2024_2025.guardians = new ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[](1); + config2025_2026.guardians = new ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[](1); + config2024_2025.guardians[0] = aliceInfo; + config2025_2026.guardians[0] = aliceInfo; + + (auth, registry, vestingAllocationFactory) = DeployZkSyncGuardianCompensationPrerequisitesScript.deployPrerequisites( + deployerPrivateKey, + saltStr, + config2024_2025 + ); + + // Update configs with deployed contracts + config2024_2025.registry = registry; + config2024_2025.vestingAllocationFactory = vestingAllocationFactory; + config2025_2026.registry = registry; + config2025_2026.vestingAllocationFactory = vestingAllocationFactory; + + // Deploy 2024-2025 compensation contracts + (controller, safeTxs2024_2025) = DeployZkSyncGuardianCompensationScript.deployCompensation( + deployerPrivateKey, + string(abi.encodePacked(saltStr, ".2024-2025")), + config2024_2025 + ); + config2024_2025.controller = controller; // Update configs with deployed contracts + + // Deploy 2025-2026 compensation contracts + (controller, safeTxs2025_2026) = DeployZkSyncGuardianCompensationScript.deployCompensation( + deployerPrivateKey, + string(abi.encodePacked(saltStr, ".2025-2026")), + config2025_2026 + ); + config2025_2026.controller = controller; // Update configs with deployed contracts + + // Create all templates + safeTxsCreateAllTemplates = CreateAllTemplatesScript.run(config2025_2026); + + // Simulate MetaLeX SAFE to execute txs as instructed + for (uint256 i = 0; i < safeTxsCreateAllTemplates.length; i++) { + vm.prank(address(config2024_2025.metalexSafe)); + (safeTxsCreateAllTemplates[i].to).call{value: safeTxsCreateAllTemplates[i].value}(safeTxsCreateAllTemplates[i].data); + } // Simulate Guardian SAFE to execute txs as instructed @@ -161,34 +153,26 @@ contract ZkSyncGuardianCompensationTest is // Simulate MetaLeX SAFE create templates for Guardian Compensation and Service Agreement -// vm.startPrank(address(config2024_2025.metalexSafe)); -// -// config2024_2025.registry.createTemplate( -// config2024_2025.borgResolutionTemplate.id, -// config2024_2025.borgResolutionTemplate.name, -// config2024_2025.borgResolutionTemplate.agreementUri, -// config2024_2025.borgResolutionTemplate.globalFields, -// config2024_2025.borgResolutionTemplate.partyFields -// ); -// -// for (uint256 i = 0; i < config2024_2025.guardians.length; i ++) { -// ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config2024_2025.guardians[i]; -// config2024_2025.registry.createTemplate( -// guardian.compTemplate.id, -// guardian.compTemplate.name, -// guardian.compTemplate.agreementUri, -// guardian.compTemplate.globalFields, -// guardian.compTemplate.partyFields -// ); -// } -// -// vm.stopPrank(); + vm.startPrank(address(config2024_2025.metalexSafe)); + + for (uint256 i = 0; i < config2024_2025.guardians.length; i ++) { + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config2024_2025.guardians[i]; + config2024_2025.registry.createTemplate( + guardian.compTemplate.id, + guardian.compTemplate.name, + guardian.compTemplate.agreementUri, + guardian.compTemplate.globalFields, + guardian.compTemplate.partyFields + ); + } + + vm.stopPrank(); // Simulate vote pass (https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746) masterMinter = IZkCappedMinterV2(config2024_2025.zkCappedMinter.MINTABLE()); -// // Comment out below after vote has been executed as of block 64423211 + // No longer needed after vote has been executed as of block 64423211 // vm.startPrank(zkTokenAdmin); // // masterMinter.grantRole(masterMinter.MINTER_ROLE(), address(config2024_2025.zkCappedMinter)); @@ -204,7 +188,7 @@ contract ZkSyncGuardianCompensationTest is DeployZkSyncGuardianCompensationPrerequisitesScript, DeployZkSyncGuardianCompensationScript, CreateAllTemplatesScript, - ProposeBorgResolutionScript, +// ProposeBorgResolutionScript, ProposeAllGuardiansMetaVestDealScript, SignDealAndCreateMetavestScript ) { @@ -222,14 +206,15 @@ contract ZkSyncGuardianCompensationTest is vm.assertEq(auth.userRoles(deployer), 0, "deployer should revoke BorgAuth ownership"); vm.assertEq(address(config2024_2025.registry.AUTH()), address(auth), "Unexpected CyberAgreementRegistry auth"); - _assertTemplate( - config2024_2025.registry, - config2024_2025.borgResolutionTemplate.id, - config2024_2025.borgResolutionTemplate.agreementUri, - config2024_2025.borgResolutionTemplate.name, - config2024_2025.borgResolutionTemplate.globalFields, - config2024_2025.borgResolutionTemplate.partyFields - ); + // TODO deprecated +// _assertTemplate( +// config2024_2025.registry, +// config2024_2025.borgResolutionTemplate.id, +// config2024_2025.borgResolutionTemplate.agreementUri, +// config2024_2025.borgResolutionTemplate.name, +// config2024_2025.borgResolutionTemplate.globalFields, +// config2024_2025.borgResolutionTemplate.partyFields +// ); for (uint256 i = 0; i < config2024_2025.guardians.length; i ++) { ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config2024_2025.guardians[i]; _assertTemplate( @@ -278,27 +263,28 @@ contract ZkSyncGuardianCompensationTest is assertGt(agreementExpiry, config2024_2025.zkCappedMinter.EXPIRATION_TIME(), "Agreement expiry should be later than the minter's"); } - function test_ProposeBorgResolution() public { - // Simulate Guardian SAFE delegation - vm.prank(address(config2024_2025.guardianSafe)); - config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); - assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "should be MetaLeX SAFE's delegate"); - - // Simulate MetaLeX delegate proposing and signing agreement - bytes32 agreementId = ProposeBorgResolutionScript.run( - guardianDelegatePrivateKey, - config2024_2025 - ); - - // Verify agreement - - (bytes32 templateId, , , , , , uint256 expiry) = config2024_2025.registry.agreements(agreementId); - assertEq(templateId, config2024_2025.borgResolutionTemplate.id, "Unexpected borg Resolution template ID"); - - (, , , , , address[] memory parties, , , , ) = config2024_2025.registry.getContractDetails(agreementId); - vm.assertEq(parties.length, 1, "Should be single-party"); - vm.assertEq(parties[0], address(config2024_2025.guardianSafe), "First party should be Guardian SAFE"); - } + // TODO deprecated +// function test_ProposeBorgResolution() public { +// // Simulate Guardian SAFE delegation +// vm.prank(address(config2024_2025.guardianSafe)); +// config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); +// assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "should be MetaLeX SAFE's delegate"); +// +// // Simulate MetaLeX delegate proposing and signing agreement +// bytes32 agreementId = ProposeBorgResolutionScript.run( +// guardianDelegatePrivateKey, +// config2024_2025 +// ); +// +// // Verify agreement +// +// (bytes32 templateId, , , , , , uint256 expiry) = config2024_2025.registry.agreements(agreementId); +// assertEq(templateId, config2024_2025.borgResolutionTemplate.id, "Unexpected borg Resolution template ID"); +// +// (, , , , , address[] memory parties, , , , ) = config2024_2025.registry.getContractDetails(agreementId); +// vm.assertEq(parties.length, 1, "Should be single-party"); +// vm.assertEq(parties[0], address(config2024_2025.guardianSafe), "First party should be Guardian SAFE"); +// } function test_GuardianCompensation() public { (address metavestAddressAlice2024_2025, address metavestAddressAlice2025_2026) = _proposeAndFinalizeAllGuardianDeals(); @@ -348,7 +334,7 @@ contract ZkSyncGuardianCompensationTest is name: "Chad", evmAddress: chad }), - signature: "" + signature: "" // No offline signature needed since we will sign with Chad's private key }); bytes32 contractIdChad = ProposeAllGuardiansMetaVestDealScript.runSingle( deployerPrivateKey, @@ -383,7 +369,7 @@ contract ZkSyncGuardianCompensationTest is config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); - // Run scripts to propose deals + // Run scripts to propose deals for all guardians bytes32[] memory agreementIds2024_2025 = ProposeAllGuardiansMetaVestDealScript.runAll( deployerPrivateKey, @@ -398,19 +384,6 @@ contract ZkSyncGuardianCompensationTest is config2025_2026 ); - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory aliceInfo = config2024_2025.guardians[0]; - -// bytes32 contractIdAlice2024_2025 = ProposeMetaVestDealScript.run( -// guardianDelegatePrivateKey, -// aliceInfo, -// config2024_2025 -// ); -// bytes32 contractIdAlice2025_2026 = ProposeMetaVestDealScript.run( -// guardianDelegatePrivateKey, -// aliceInfo, -// config2025_2026 -// ); - // Simulate guardian counter-sign and finalize the deal address metavestAlice2024_2025 = SignDealAndCreateMetavestScript.run( From d7881cfb2f7eb1b418f420fb0c379b99c01ed02c Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 11 Sep 2025 12:05:00 -0700 Subject: [PATCH 62/68] chore: add acceptance tests to simulate and track existing deployment --- test/ZkSyncGuardianCompensation.t.sol | 108 ++++++++-------- ...ZkSyncGuardianCompensationAcceptance.t.sol | 117 ++++++++++++++++++ 2 files changed, 166 insertions(+), 59 deletions(-) create mode 100644 test/ZkSyncGuardianCompensationAcceptance.t.sol diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 8548733..3f0507c 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -36,6 +36,7 @@ contract ZkSyncGuardianCompensationTest is address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; string saltStr = "ZkSyncGuardianCompensationTest"; + uint256 agreementSalt = block.timestamp; // Randomly generated to avoid contaminated common test address uint256 privateKeySalt = 0x4425fdf88097e51c669a66d392c4019ad555544e966908af6ee3cec32f53ab77; @@ -46,31 +47,24 @@ contract ZkSyncGuardianCompensationTest is address metalexDelegate = vm.addr(metalexDelegatePrivateKey); uint256 guardianDelegatePrivateKey = privateKeySalt + 2; address guardianDelegate = vm.addr(guardianDelegatePrivateKey); - uint256 alicePrivateKey = privateKeySalt + 3; - address alice = vm.addr(alicePrivateKey); - uint256 bobPrivateKey = privateKeySalt + 4; - address bob = vm.addr(bobPrivateKey); - uint256 chadPrivateKey = privateKeySalt + 5; + uint256 chadPrivateKey = privateKeySalt + 3; address chad = vm.addr(chadPrivateKey); + uint256[] guardianPrivateKeys; IZkCappedMinterV2 masterMinter; ZkSyncGuardianCompensation2024_2025.Config config2024_2025; ZkSyncGuardianCompensation2024_2025.Config config2025_2026; - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo aliceInfo; - BorgAuth auth; metavestController controller2024_2025; metavestController controller2025_2026; - function setUp() public { + function setUp() virtual public { // Prepare funds for accounts used by the actual deployment scripts deal(deployer, 1 ether); deal(metalexDelegate, 1 ether); deal(guardianDelegate, 1 ether); - deal(alice, 1 ether); - deal(bob, 1 ether); deal(chad, 1 ether); CyberAgreementRegistry registry; @@ -84,10 +78,14 @@ contract ZkSyncGuardianCompensationTest is config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(vm); // Override guardian info for tests - aliceInfo = aliceInfo = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ + + guardianPrivateKeys = new uint256[](1); + guardianPrivateKeys[0] = privateKeySalt + 100; + config2024_2025.guardians = new ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[](1); + config2024_2025.guardians[0] = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ name: "Alice", - evmAddress: alice + evmAddress: vm.addr(guardianPrivateKeys[0]) }), compTemplate: ZkSyncGuardianCompensation2024_2025.TemplateInfo({ id: bytes32(uint256(999001)), @@ -98,10 +96,10 @@ contract ZkSyncGuardianCompensationTest is }), signature: "" }); - config2024_2025.guardians = new ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[](1); - config2025_2026.guardians = new ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[](1); - config2024_2025.guardians[0] = aliceInfo; - config2025_2026.guardians[0] = aliceInfo; + config2025_2026.guardians = config2024_2025.guardians; + deal(config2025_2026.guardians[0].partyInfo.evmAddress, 1 ether); // Prepare funds for guardians + + // Deploy prerequisites (auth, registry, vestingAllocationFactory) = DeployZkSyncGuardianCompensationPrerequisitesScript.deployPrerequisites( deployerPrivateKey, @@ -182,6 +180,11 @@ contract ZkSyncGuardianCompensationTest is // grandMasterMinter.grantRole(grandMasterMinter.MINTER_ROLE(), address(masterMinter)); // // vm.stopPrank(); + + // Simulate Guardian SAFE to delegate signing to an EOA + vm.prank(address(config2024_2025.guardianSafe)); + config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60 days); // A bit longer to accommodate test cases + assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); } function run() public override( @@ -245,17 +248,12 @@ contract ZkSyncGuardianCompensationTest is } function test_AgreementDeadline() public { - // Guardian SAFE to delegate signing to an EOA - vm.prank(address(config2024_2025.guardianSafe)); - config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); - assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); - // Run scripts to propose deals bytes32 agreementId = ProposeAllGuardiansMetaVestDealScript.runSingle( deployerPrivateKey, guardianDelegatePrivateKey, config2024_2025.guardians[0], // alice - block.timestamp, // agreementSalt + agreementSalt, config2024_2025 ); @@ -265,11 +263,6 @@ contract ZkSyncGuardianCompensationTest is // TODO deprecated // function test_ProposeBorgResolution() public { -// // Simulate Guardian SAFE delegation -// vm.prank(address(config2024_2025.guardianSafe)); -// config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); -// assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "should be MetaLeX SAFE's delegate"); -// // // Simulate MetaLeX delegate proposing and signing agreement // bytes32 agreementId = ProposeBorgResolutionScript.run( // guardianDelegatePrivateKey, @@ -287,10 +280,10 @@ contract ZkSyncGuardianCompensationTest is // } function test_GuardianCompensation() public { - (address metavestAddressAlice2024_2025, address metavestAddressAlice2025_2026) = _proposeAndFinalizeAllGuardianDeals(); + (address[] memory metavestAddresses2024_2025, address[] memory metavestAddresses2025_2026) = _proposeAndFinalizeAllGuardianDeals(); - VestingAllocation vestingAllocationAlice2024_2025 = VestingAllocation(metavestAddressAlice2024_2025); - VestingAllocation vestingAllocationAlice2025_2026 = VestingAllocation(metavestAddressAlice2025_2026); + VestingAllocation vestingAllocationAlice2024_2025 = VestingAllocation(metavestAddresses2024_2025[0]); + VestingAllocation vestingAllocationAlice2025_2026 = VestingAllocation(metavestAddresses2025_2026[0]); // Alice should be able to withdraw all on 2025/09/01 because this compensation is for 2024~2025 vm.warp(1756684800); // 2025/09/01 00:00 UTC @@ -310,8 +303,8 @@ contract ZkSyncGuardianCompensationTest is } function test_AdminToolingCompensation() public { - (address metavestAddressAlice2024_2025, ) = _proposeAndFinalizeAllGuardianDeals(); - VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddressAlice2024_2025); + (address[] memory metavestAddresses2024_2025, ) = _proposeAndFinalizeAllGuardianDeals(); + VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddresses2024_2025[0]); // 2024-2025 Vesting starts vm.warp(1756684800); // 2025/09/01 00:00 UTC @@ -323,11 +316,6 @@ contract ZkSyncGuardianCompensationTest is // Add new grantee for admin/tooling compensation - // Guardian SAFE to delegate signing to an EOA - vm.prank(address(config2024_2025.guardianSafe)); - config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); - assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "should be Guardian SAFE's delegate"); - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory chadInfo = ZkSyncGuardianCompensation2024_2025.GuardianCompInfo({ compTemplate: config2024_2025.guardians[0].compTemplate, // Re-use Alice's template just for test partyInfo: ZkSyncGuardianCompensation2024_2025.PartyInfo({ @@ -340,7 +328,7 @@ contract ZkSyncGuardianCompensationTest is deployerPrivateKey, guardianDelegatePrivateKey, chadInfo, - block.timestamp, // agreementSalt + agreementSalt, BaseAllocation.Allocation({ tokenContract: address(config2024_2025.zkToken), // 10k ZK total in one cliff @@ -363,43 +351,45 @@ contract ZkSyncGuardianCompensationTest is _granteeWithdrawAndAsserts(config2024_2025.zkToken, config2024_2025.zkCappedMinter, vestingAllocationChad, 10e3 ether, "Chad cliff"); } - function _proposeAndFinalizeAllGuardianDeals() internal returns(address, address) { - // Guardian SAFE to delegate signing to an EOA - vm.prank(address(config2024_2025.guardianSafe)); - config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60); - assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); - + function _proposeAndFinalizeAllGuardianDeals() internal returns(address[] memory, address[] memory) { // Run scripts to propose deals for all guardians bytes32[] memory agreementIds2024_2025 = ProposeAllGuardiansMetaVestDealScript.runAll( deployerPrivateKey, guardianDelegatePrivateKey, - block.timestamp, // agreementSalt + agreementSalt, config2024_2025 ); bytes32[] memory agreementIds2025_2026 = ProposeAllGuardiansMetaVestDealScript.runAll( deployerPrivateKey, guardianDelegatePrivateKey, - block.timestamp, // agreementSalt + agreementSalt, config2025_2026 ); // Simulate guardian counter-sign and finalize the deal - address metavestAlice2024_2025 = SignDealAndCreateMetavestScript.run( - alicePrivateKey, - agreementIds2024_2025[0], - aliceInfo, - config2024_2025 - ); - address metavestAlice2025_2026 = SignDealAndCreateMetavestScript.run( - alicePrivateKey, - agreementIds2025_2026[0], - aliceInfo, - config2025_2026 - ); + address[] memory metavests2024_2025 = new address[](guardianPrivateKeys.length); + address[] memory metavests2025_2026 = new address[](guardianPrivateKeys.length); + + for (uint256 i = 0; i < metavests2024_2025.length; i++) { + metavests2024_2025[i] = SignDealAndCreateMetavestScript.run( + guardianPrivateKeys[i], + agreementIds2024_2025[i], + config2024_2025.guardians[i], + config2024_2025 + ); + } + for (uint256 i = 0; i < metavests2025_2026.length; i++) { + metavests2025_2026[i] = SignDealAndCreateMetavestScript.run( + guardianPrivateKeys[i], + agreementIds2025_2026[i], + config2025_2026.guardians[i], + config2025_2026 + ); + } - return (metavestAlice2024_2025, metavestAlice2025_2026); + return (metavests2024_2025, metavests2025_2026); } function _assertTemplate( diff --git a/test/ZkSyncGuardianCompensationAcceptance.t.sol b/test/ZkSyncGuardianCompensationAcceptance.t.sol new file mode 100644 index 0000000..fa3bd12 --- /dev/null +++ b/test/ZkSyncGuardianCompensationAcceptance.t.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import "../src/MetaVesTController.sol"; +import "../src/VestingAllocationFactory.sol"; +import "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "./lib/MetaVesTControllerTestBase.sol"; +import {ZkSyncGuardianCompensationTest} from "./ZkSyncGuardianCompensation.t.sol"; +import {CreateAllTemplatesScript} from "../scripts/createAllTemplates.s.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {DeployZkSyncGuardianCompensationPrerequisitesScript} from "../scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol"; +import {DeployZkSyncGuardianCompensationScript} from "../scripts/deployZkSyncGuardianCompensation.s.sol"; +import {GnosisTransaction} from "./lib/safe.sol"; +import {ProposeAllGuardiansMetaVestDealScript} from "../scripts/proposeAllGuardiansMetavestDeals.s.sol"; +import {ProposeMetaVestDealScript} from "../scripts/proposeMetavestDeal.s.sol"; +import {SignDealAndCreateMetavestScript} from "../scripts/signDealAndCreateMetavest.s.sol"; +import {Test} from "forge-std/Test.sol"; +import {ZkSyncGuardianCompensation2024_2025} from "../scripts/lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "../scripts/lib/ZkSyncGuardianCompensation2025_2026.sol"; +import {console2} from "forge-std/console2.sol"; + +// Test with existing deployment +// - Assume existing deployment on zkSync Era mainnet +// - Phases deployed/completed are commented out to reflect real-world conditions +// - Phases not yet completed will be simulated +// - Will use same environment variables as the real deployment, but some of them will be overridden so we could test +contract ZkSyncGuardianCompensationAcceptanceTest is ZkSyncGuardianCompensationTest { + + function setUp() override public { + agreementSalt = 1757616222; // Fixed agreement salt so we can do offline signatures + + // Override accounts for acceptance tests + + // Use the real deployer + deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + deployer = vm.addr(deployerPrivateKey); + + // TODO WIP: Use real Guardians SAFE delegate. We don't have his private key and will use offline signatures instead +// address guardianDelegate = 0xa376AaF645dbd9b4f501B2A8a97bc21DcA15B001; + + // Prepare funds for accounts used by the actual deployment scripts + deal(deployer, 1 ether); + deal(chad, 1 ether); + + GnosisTransaction[] memory safeTxsCreateAllTemplates; + GnosisTransaction[] memory safeTxs2024_2025; + GnosisTransaction[] memory safeTxs2025_2026; + + config2024_2025 = ZkSyncGuardianCompensation2024_2025.getDefault(vm); + config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(vm); + + guardianPrivateKeys = new uint256[](config2024_2025.guardians.length); + for (uint256 i = 0; i < config2024_2025.guardians.length; i++) { + // Note we override all guardians with the same address to simplify offline signing + guardianPrivateKeys[i] = privateKeySalt + 100; + address guardian = vm.addr(guardianPrivateKeys[i]); + // Override guardian address with one we control + config2024_2025.guardians[i].partyInfo.evmAddress = guardian; + config2025_2026.guardians[i].partyInfo.evmAddress = guardian; + // Prepare funds for guardians + deal(guardian, 1 ether); + } + + // Assume prerequisites have been deployed + auth = config2024_2025.registry.AUTH(); + + // Assume 2024-2025 compensation contracts have been deployed + + // Assume 2025-2026 compensation contracts have been deployed + + // Assume all all templates have been deployed + + // Simulate MetaLeX SAFE to execute txs as instructed (payloads are copied directly from a recent production deployment) + + safeTxs2024_2025 = new GnosisTransaction[](2); + safeTxs2024_2025[0] = GnosisTransaction({ + to: 0xE555FC98E45637D1B45e60E4fc05cF0F22836156, + value: 0, + data: hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000d509349af986e7202f2bc4ae49c203e354faafcd" + }); + safeTxs2024_2025[1] = GnosisTransaction({ + to: 0xD509349AF986E7202f2Bc4ae49C203E354faafCD, + value: 0, + data: hex"66e26184000000000000000000000000e555fc98e45637d1b45e60e4fc05cf0f22836156" + }); + safeTxs2025_2026 = new GnosisTransaction[](2); + safeTxs2025_2026[0] = GnosisTransaction({ + to: 0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A, + value: 0, + data: hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000570d31f59bd0c96a9e1cc889e7e4dbd585d6915b" + }); + safeTxs2025_2026[1] = GnosisTransaction({ + to: 0x570d31F59bD0C96a9e1CC889E7E4dBd585D6915b, + value: 0, + data: hex"66e261840000000000000000000000001358f460bd147c4a6bfdab75ad2b78c837a11d4a" + }); + + for (uint256 i = 0; i < safeTxs2024_2025.length; i++) { + vm.prank(address(config2024_2025.guardianSafe)); + (safeTxs2024_2025[i].to).call{value: safeTxs2024_2025[i].value}(safeTxs2024_2025[i].data); + } + for (uint256 i = 0; i < safeTxs2025_2026.length; i++) { + vm.prank(address(config2025_2026.guardianSafe)); + (safeTxs2025_2026[i].to).call{value: safeTxs2025_2026[i].value}(safeTxs2025_2026[i].data); + } + + // Vote has been executed as of block 64423211 + // https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 + masterMinter = IZkCappedMinterV2(config2024_2025.zkCappedMinter.MINTABLE()); + + // Simulate Guardian SAFE to delegate signing to an EOA + vm.prank(address(config2024_2025.guardianSafe)); + config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60 days); // A bit longer to accommodate test cases + assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); + } +} From 9f7ec5fc54905dd70e28e8a8314fc4158f2413dc Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 11 Sep 2025 13:07:40 -0700 Subject: [PATCH 63/68] chore: test offline signatures with dev wallets --- test/ZkSyncGuardianCompensation.t.sol | 2 +- ...ZkSyncGuardianCompensationAcceptance.t.sol | 40 +++++++++++++------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol index 3f0507c..c60728c 100644 --- a/test/ZkSyncGuardianCompensation.t.sol +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -302,7 +302,7 @@ contract ZkSyncGuardianCompensationTest is _granteeWithdrawAndAsserts(config2025_2026.zkToken, config2025_2026.zkCappedMinter, vestingAllocationAlice2025_2026, 312.5e3 ether, "Alice 2025-2026 remaining"); } - function test_AdminToolingCompensation() public { + function test_AdminToolingCompensation() virtual public { (address[] memory metavestAddresses2024_2025, ) = _proposeAndFinalizeAllGuardianDeals(); VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddresses2024_2025[0]); diff --git a/test/ZkSyncGuardianCompensationAcceptance.t.sol b/test/ZkSyncGuardianCompensationAcceptance.t.sol index fa3bd12..f12e578 100644 --- a/test/ZkSyncGuardianCompensationAcceptance.t.sol +++ b/test/ZkSyncGuardianCompensationAcceptance.t.sol @@ -36,8 +36,11 @@ contract ZkSyncGuardianCompensationAcceptanceTest is ZkSyncGuardianCompensationT deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); deployer = vm.addr(deployerPrivateKey); - // TODO WIP: Use real Guardians SAFE delegate. We don't have his private key and will use offline signatures instead -// address guardianDelegate = 0xa376AaF645dbd9b4f501B2A8a97bc21DcA15B001; + // Use real Guardians SAFE delegate. We don't have his private key and will use offline signatures instead + // TODO WIP: using dev account first to test +// guardianDelegate = 0xa376AaF645dbd9b4f501B2A8a97bc21DcA15B001; + guardianDelegate = 0xD63383fBf9F3FDf3759acA89dA00c4c0CF3A0865; + guardianDelegatePrivateKey = 0; // Prepare funds for accounts used by the actual deployment scripts deal(deployer, 1 ether); @@ -50,17 +53,24 @@ contract ZkSyncGuardianCompensationAcceptanceTest is ZkSyncGuardianCompensationT config2024_2025 = ZkSyncGuardianCompensation2024_2025.getDefault(vm); config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(vm); - guardianPrivateKeys = new uint256[](config2024_2025.guardians.length); - for (uint256 i = 0; i < config2024_2025.guardians.length; i++) { - // Note we override all guardians with the same address to simplify offline signing - guardianPrivateKeys[i] = privateKeySalt + 100; - address guardian = vm.addr(guardianPrivateKeys[i]); - // Override guardian address with one we control - config2024_2025.guardians[i].partyInfo.evmAddress = guardian; - config2025_2026.guardians[i].partyInfo.evmAddress = guardian; - // Prepare funds for guardians - deal(guardian, 1 ether); - } + // Override guardian info for tests + // We can't load two-year worth of offline signatures at the same time, so we will + // load the first year through env vars and load the seconds through constants. + // Also, for simplicity we will reduce the number of guardians to one for each year. + assertEq(config2024_2025.guardians.length, 1, "There should be just one test guardian"); + + guardianPrivateKeys = new uint256[](1); + guardianPrivateKeys[0] = privateKeySalt + 100; + address guardian = vm.addr(guardianPrivateKeys[0]); + // Prepare funds for guardians + deal(guardian, 1 ether); + + // Override guardian address with one we control + config2024_2025.guardians[0].partyInfo.evmAddress = guardian; + + // Override guardian address with one we control, and its offline signature + config2025_2026.guardians[0].partyInfo.evmAddress = guardian; + config2025_2026.guardians[0].signature = hex"1407f5c50c3ca1f6178f2f6c9254a2513dc8c6846135f5de7c4aee81b8275a523b46b8a983f3ec8741729ca81af7d898a44acdbd35c3665777f53f6bb0051fa31c"; // Assume prerequisites have been deployed auth = config2024_2025.registry.AUTH(); @@ -114,4 +124,8 @@ contract ZkSyncGuardianCompensationAcceptanceTest is ZkSyncGuardianCompensationT config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60 days); // A bit longer to accommodate test cases assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); } + + function test_AdminToolingCompensation() override public { + // No-op: disabled since we won't have signatures for it + } } From d9399f0d30bbc26f98a1620b9fb962b10dcf388f Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 11 Sep 2025 14:01:42 -0700 Subject: [PATCH 64/68] chore: test offline (mocked) signatures with prod wallets --- test/ZkSyncGuardianCompensationAcceptance.t.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/ZkSyncGuardianCompensationAcceptance.t.sol b/test/ZkSyncGuardianCompensationAcceptance.t.sol index f12e578..69254be 100644 --- a/test/ZkSyncGuardianCompensationAcceptance.t.sol +++ b/test/ZkSyncGuardianCompensationAcceptance.t.sol @@ -37,9 +37,7 @@ contract ZkSyncGuardianCompensationAcceptanceTest is ZkSyncGuardianCompensationT deployer = vm.addr(deployerPrivateKey); // Use real Guardians SAFE delegate. We don't have his private key and will use offline signatures instead - // TODO WIP: using dev account first to test -// guardianDelegate = 0xa376AaF645dbd9b4f501B2A8a97bc21DcA15B001; - guardianDelegate = 0xD63383fBf9F3FDf3759acA89dA00c4c0CF3A0865; + guardianDelegate = 0xa376AaF645dbd9b4f501B2A8a97bc21DcA15B001; guardianDelegatePrivateKey = 0; // Prepare funds for accounts used by the actual deployment scripts @@ -70,7 +68,7 @@ contract ZkSyncGuardianCompensationAcceptanceTest is ZkSyncGuardianCompensationT // Override guardian address with one we control, and its offline signature config2025_2026.guardians[0].partyInfo.evmAddress = guardian; - config2025_2026.guardians[0].signature = hex"1407f5c50c3ca1f6178f2f6c9254a2513dc8c6846135f5de7c4aee81b8275a523b46b8a983f3ec8741729ca81af7d898a44acdbd35c3665777f53f6bb0051fa31c"; + config2025_2026.guardians[0].signature = hex"144ca44344bc709c156abaa532dd3f049fced51ce43a0aecd888c574ba75e31a47b17f3db279933e2918f3ba11c21e09dc1d5d5652ef9576c7d57ffd4fad546f1c"; // Assume prerequisites have been deployed auth = config2024_2025.registry.AUTH(); From d6676702e94be7252f81c1e1a977a353783aee14 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 11 Sep 2025 15:07:07 -0700 Subject: [PATCH 65/68] chore: refactor acceptance tests so it tests against production env vars --- .../ZkSyncGuardianCompensationAcceptance.t.sol | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/ZkSyncGuardianCompensationAcceptance.t.sol b/test/ZkSyncGuardianCompensationAcceptance.t.sol index 69254be..cc16ed0 100644 --- a/test/ZkSyncGuardianCompensationAcceptance.t.sol +++ b/test/ZkSyncGuardianCompensationAcceptance.t.sol @@ -52,20 +52,28 @@ contract ZkSyncGuardianCompensationAcceptanceTest is ZkSyncGuardianCompensationT config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(vm); // Override guardian info for tests - // We can't load two-year worth of offline signatures at the same time, so we will - // load the first year through env vars and load the seconds through constants. - // Also, for simplicity we will reduce the number of guardians to one for each year. - assertEq(config2024_2025.guardians.length, 1, "There should be just one test guardian"); + // There will be only one guardian for test guardianPrivateKeys = new uint256[](1); guardianPrivateKeys[0] = privateKeySalt + 100; address guardian = vm.addr(guardianPrivateKeys[0]); // Prepare funds for guardians deal(guardian, 1 ether); - // Override guardian address with one we control + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory tempGuardianInfo; + + // Reduce guardians to the first one + tempGuardianInfo = config2024_2025.guardians[0]; + config2024_2025.guardians = new ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[](1); + config2024_2025.guardians[0] = tempGuardianInfo; + // Override guardian address with one we control, and its offline signature config2024_2025.guardians[0].partyInfo.evmAddress = guardian; + config2024_2025.guardians[0].signature = hex"7b3492d39b39cfbbc4c134ff06f4cc68afcb224d6f94c5813ffae53db94d5c8b622457c2abaa2403aba84711f37ae17ffa367f3574cb7cd3a51da4461c017d331b"; + // Reduce guardians to the first one + tempGuardianInfo = config2025_2026.guardians[0]; + config2025_2026.guardians = new ZkSyncGuardianCompensation2024_2025.GuardianCompInfo[](1); + config2025_2026.guardians[0] = tempGuardianInfo; // Override guardian address with one we control, and its offline signature config2025_2026.guardians[0].partyInfo.evmAddress = guardian; config2025_2026.guardians[0].signature = hex"144ca44344bc709c156abaa532dd3f049fced51ce43a0aecd888c574ba75e31a47b17f3db279933e2918f3ba11c21e09dc1d5d5652ef9576c7d57ffd4fad546f1c"; From baafce3570512822dd6fc230cd60aa8193ccf0a9 Mon Sep 17 00:00:00 2001 From: detoo Date: Sun, 14 Sep 2025 10:20:12 -0700 Subject: [PATCH 66/68] chore: refactor Guardian SAFE tx batch to match production scenarios --- ...ZkSyncGuardianCompensationAcceptance.t.sol | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/test/ZkSyncGuardianCompensationAcceptance.t.sol b/test/ZkSyncGuardianCompensationAcceptance.t.sol index cc16ed0..71f880a 100644 --- a/test/ZkSyncGuardianCompensationAcceptance.t.sol +++ b/test/ZkSyncGuardianCompensationAcceptance.t.sol @@ -44,9 +44,7 @@ contract ZkSyncGuardianCompensationAcceptanceTest is ZkSyncGuardianCompensationT deal(deployer, 1 ether); deal(chad, 1 ether); - GnosisTransaction[] memory safeTxsCreateAllTemplates; - GnosisTransaction[] memory safeTxs2024_2025; - GnosisTransaction[] memory safeTxs2025_2026; + GnosisTransaction[] memory guardianSafeTxs; config2024_2025 = ZkSyncGuardianCompensation2024_2025.getDefault(vm); config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(vm); @@ -89,46 +87,44 @@ contract ZkSyncGuardianCompensationAcceptanceTest is ZkSyncGuardianCompensationT // Simulate MetaLeX SAFE to execute txs as instructed (payloads are copied directly from a recent production deployment) - safeTxs2024_2025 = new GnosisTransaction[](2); - safeTxs2024_2025[0] = GnosisTransaction({ + guardianSafeTxs = new GnosisTransaction[](5); + guardianSafeTxs[0] = GnosisTransaction({ + to: 0x07E0a0BeC742f90f7879830bC917E783dA6a6357, + value: 0, + data: hex"e988dc91000000000000000000000000a376aaf645dbd9b4f501b2a8a97bc21dca15b0010000000000000000000000000000000000000000000000000000000068db1d80" + }); + guardianSafeTxs[1] = GnosisTransaction({ to: 0xE555FC98E45637D1B45e60E4fc05cF0F22836156, value: 0, data: hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000d509349af986e7202f2bc4ae49c203e354faafcd" }); - safeTxs2024_2025[1] = GnosisTransaction({ + guardianSafeTxs[2] = GnosisTransaction({ to: 0xD509349AF986E7202f2Bc4ae49C203E354faafCD, value: 0, data: hex"66e26184000000000000000000000000e555fc98e45637d1b45e60e4fc05cf0f22836156" }); - safeTxs2025_2026 = new GnosisTransaction[](2); - safeTxs2025_2026[0] = GnosisTransaction({ + guardianSafeTxs[3] = GnosisTransaction({ to: 0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A, value: 0, data: hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000570d31f59bd0c96a9e1cc889e7e4dbd585d6915b" }); - safeTxs2025_2026[1] = GnosisTransaction({ + guardianSafeTxs[4] = GnosisTransaction({ to: 0x570d31F59bD0C96a9e1CC889E7E4dBd585D6915b, value: 0, data: hex"66e261840000000000000000000000001358f460bd147c4a6bfdab75ad2b78c837a11d4a" }); - for (uint256 i = 0; i < safeTxs2024_2025.length; i++) { + for (uint256 i = 0; i < guardianSafeTxs.length; i++) { vm.prank(address(config2024_2025.guardianSafe)); - (safeTxs2024_2025[i].to).call{value: safeTxs2024_2025[i].value}(safeTxs2024_2025[i].data); - } - for (uint256 i = 0; i < safeTxs2025_2026.length; i++) { - vm.prank(address(config2025_2026.guardianSafe)); - (safeTxs2025_2026[i].to).call{value: safeTxs2025_2026[i].value}(safeTxs2025_2026[i].data); + (guardianSafeTxs[i].to).call{value: guardianSafeTxs[i].value}(guardianSafeTxs[i].data); } + // Verify Guardian SAFE has delegated signing + assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); + // Vote has been executed as of block 64423211 // https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 masterMinter = IZkCappedMinterV2(config2024_2025.zkCappedMinter.MINTABLE()); - - // Simulate Guardian SAFE to delegate signing to an EOA - vm.prank(address(config2024_2025.guardianSafe)); - config2024_2025.registry.setDelegation(guardianDelegate, block.timestamp + 60 days); // A bit longer to accommodate test cases - assertTrue(config2024_2025.registry.isValidDelegate(address(config2024_2025.guardianSafe), guardianDelegate), "delegate should be Guardian SAFE's delegate"); } function test_AdminToolingCompensation() override public { From e82f03a887bb92ea11cfc73c8eb8711f0a70a1b1 Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 15 Sep 2025 15:52:59 -0700 Subject: [PATCH 67/68] chore: prepare to create compensation agreements --- scripts/proposeAllGuardiansMetavestDeals.s.sol | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/proposeAllGuardiansMetavestDeals.s.sol b/scripts/proposeAllGuardiansMetavestDeals.s.sol index 2b012b3..01fe667 100644 --- a/scripts/proposeAllGuardiansMetavestDeals.s.sol +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; import {ProposeMetaVestDealScript} from "./proposeMetavestDeal.s.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; @@ -23,12 +24,17 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual override { runAll( + // zkSync Era for 2024-2025 vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey - vm.envOr("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY", uint256(0)), // guardianSafeDelegatePrivateKey - vm.envUint("AGREEMENT_SALT"), // agreementSalt + uint256(0), // delegate will sign offline + uint256(keccak256("MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2024-2025")), // agreementSalt + ZkSyncGuardianCompensation2024_2025.getDefault(vm) - // zkSync Sepolia for 2024-2025 - ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) + // zkSync Era for 2025-2026 +// vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey +// uint256(0), // delegate will sign offline +// uint256(keccak256("MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2025-2026")), // agreementSalt +// ZkSyncGuardianCompensation2025_2026.getDefault(vm) ); } From 63c129ac2ca2a696553f6c589519d1c36c2a1fad Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 16 Sep 2025 10:04:48 -0700 Subject: [PATCH 68/68] chore: simulate agreement creation with real signatures --- .../proposeAllGuardiansMetavestDeals.s.sol | 26 ++++++++++++++++--- ...ZkSyncGuardianCompensationAcceptance.t.sol | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/scripts/proposeAllGuardiansMetavestDeals.s.sol b/scripts/proposeAllGuardiansMetavestDeals.s.sol index 01fe667..4c56d3c 100644 --- a/scripts/proposeAllGuardiansMetavestDeals.s.sol +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -1,10 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.24; -import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; -import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; -import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; -import {ProposeMetaVestDealScript} from "./proposeMetavestDeal.s.sol"; +import {GnosisTransaction} from "../test/lib/safe.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; @@ -12,10 +9,14 @@ import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreement import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {ProposeMetaVestDealScript} from "./proposeMetavestDeal.s.sol"; import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; import {Script} from "forge-std/Script.sol"; import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; @@ -23,6 +24,23 @@ import {metavestController} from "../src/MetaVesTController.sol"; contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual override { +// ZkSyncGuardianCompensation2024_2025.Config memory config = ZkSyncGuardianCompensation2024_2025.getDefault(vm); +// +// // Simulate Guardian SAFE delegation as instructed (payloads are copied directly from a recent production deployment) +// GnosisTransaction[] memory guardianSafeTxs = new GnosisTransaction[](1); +// guardianSafeTxs[0] = GnosisTransaction({ +// to: 0x07E0a0BeC742f90f7879830bC917E783dA6a6357, +// value: 0, +// data: hex"e988dc91000000000000000000000000a376aaf645dbd9b4f501b2a8a97bc21dca15b0010000000000000000000000000000000000000000000000000000000068db1d80" +// }); +// for (uint256 i = 0; i < guardianSafeTxs.length; i++) { +// vm.prank(address(config.guardianSafe)); +// (guardianSafeTxs[i].to).call{value: guardianSafeTxs[i].value}(guardianSafeTxs[i].data); +// } +// +// // Verify Guardian SAFE has delegated signing +// vm.assertTrue(config.registry.isValidDelegate(address(config.guardianSafe), 0xa376AaF645dbd9b4f501B2A8a97bc21DcA15B001), "delegate should be Guardian SAFE's delegate"); + runAll( // zkSync Era for 2024-2025 vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey diff --git a/test/ZkSyncGuardianCompensationAcceptance.t.sol b/test/ZkSyncGuardianCompensationAcceptance.t.sol index 71f880a..f006f53 100644 --- a/test/ZkSyncGuardianCompensationAcceptance.t.sol +++ b/test/ZkSyncGuardianCompensationAcceptance.t.sol @@ -85,7 +85,7 @@ contract ZkSyncGuardianCompensationAcceptanceTest is ZkSyncGuardianCompensationT // Assume all all templates have been deployed - // Simulate MetaLeX SAFE to execute txs as instructed (payloads are copied directly from a recent production deployment) + // Simulate Guardian SAFE to execute txs as instructed (payloads are copied directly from a recent production deployment) guardianSafeTxs = new GnosisTransaction[](5); guardianSafeTxs[0] = GnosisTransaction({