From 6650f112daa88140b597916338957c424b6ad4b3 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 9 Oct 2025 15:15:31 -0700 Subject: [PATCH 01/14] wip: retrofit zkSync MetaVest features without capped minter --- .gitmodules | 3 -- src/BaseAllocation.sol | 12 ++++---- src/MetaVesTController.sol | 58 +++++++++++++------------------------- 3 files changed, 25 insertions(+), 48 deletions(-) diff --git a/.gitmodules b/.gitmodules index 9e651f1..85b1ec0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std -[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/src/BaseAllocation.sol b/src/BaseAllocation.sol index bbd3ec8..17684e1 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -1,7 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.24; - -import "./interfaces/zk-governance/IZkCappedMinterV2.sol"; +pragma solidity 0.8.24; /// @notice interface to a MetaLeX condition contract /// @dev see https://github.com/MetaLex-Tech/BORG-CORE/tree/main/src/libs/conditions @@ -17,7 +15,6 @@ 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) @@ -271,7 +268,8 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ if(terminated) revert MetaVesT_AlreadyTerminated(); if (_milestoneIndex >= milestones.length) revert MetaVesT_MilestoneIndexOutOfRange(); uint256 _milestoneAward = milestones[_milestoneIndex].milestoneAward; - // No need to transfer the milestone award back to the authority since the tokens are minted on-demand + //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); delete milestones[_milestoneIndex]; milestones[_milestoneIndex] = milestones[milestones.length - 1]; milestones.pop(); @@ -320,9 +318,9 @@ 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()) revert MetaVesT_MoreThanAvailable(); + if (_amount > getAmountWithdrawable() || _amount > IERC20M(allocation.tokenContract).balanceOf(address(this))) revert MetaVesT_MoreThanAvailable(); tokensWithdrawn += _amount; - IController(controller).mint(recipient, _amount); + safeTransfer(allocation.tokenContract, msg.sender, _amount); emit MetaVesT_Withdrawn(msg.sender, recipient, allocation.tokenContract, _amount); } diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 42c7b11..74a7faf 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -14,8 +14,6 @@ import "./BaseAllocation.sol"; //import "./RestrictedTokenAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; import "./interfaces/IPriceAllocation.sol"; -import "./interfaces/zk-governance/IZkCappedMinterV2.sol"; -import "./interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; import "./lib/EnumberableSet.sol"; //interface deleted @@ -117,7 +115,6 @@ contract metavestController is UUPSUpgradeable, 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_ZkCappedMinterUpdated(address zkCappedMinter); event MetaVesTController_DealProposed( bytes32 indexed agreementId, address indexed grantee, @@ -132,7 +129,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 @@ -390,7 +387,6 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { { revert MetaVesTController_IncorrectMetaVesTType(); } - // Grant MetaVesT minter privilege metavestAgreementIds[deal.metavest] = agreementId; return deal.metavest; @@ -429,6 +425,13 @@ contract metavestController is UUPSUpgradeable, 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, @@ -478,6 +481,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; if (_total == 0) revert MetaVesTController_ZeroAmount(); + validateTokenApprovalAndBalance(_allocation.tokenContract, _total); address vestingAllocation = IAllocationFactory(vestingFactory).createAllocation( IAllocationFactory.AllocationType.Vesting, @@ -490,6 +494,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { 0, 0 ); + safeTransferFrom(_allocation.tokenContract, authority, vestingAllocation, _total); return vestingAllocation; } @@ -502,7 +507,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { // // uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; // if (_total == 0) revert MetaVesTController_ZeroAmount(); -// //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); +// validateTokenApprovalAndBalance(_allocation.tokenContract, _total); // // address tokenOptionAllocation = createAndInitializeTokenOptionAllocation( // _grantee, @@ -513,7 +518,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { // _milestones // ); // -// //safeTransferFrom(_allocation.tokenContract, authority, tokenOptionAllocation, _total); +// safeTransferFrom(_allocation.tokenContract, authority, tokenOptionAllocation, _total); // return tokenOptionAllocation; // } // @@ -524,7 +529,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { // // uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; // if (_total == 0) revert MetaVesTController_ZeroAmount(); -// //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); +// validateTokenApprovalAndBalance(_allocation.tokenContract, _total); // // address restrictedTokenAward = createAndInitializeRestrictedTokenAward( // _grantee, @@ -535,19 +540,9 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { // _milestones // ); // -// //safeTransferFrom(_allocation.tokenContract, authority, restrictedTokenAward, _total); +// 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(); @@ -609,9 +604,13 @@ contract metavestController is UUPSUpgradeable, 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(); - // No need to allocate token right now since they are minted on-demand - + // send the new milestoneAward to 'metavest' + safeTransferFrom(_tokenContract, msg.sender, _grant, _milestone.milestoneAward); BaseAllocation(_grant).addMilestone(_milestone); } @@ -898,23 +897,6 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { emit MetaVesTController_AddressRemovedFromSet(_name, _metaVest); } - function setZkCappedMinter(address _zkCappedMinter) external onlyAuthority { - zkCappedMinter = _zkCappedMinter; - 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 getDeal(bytes32 agreementId) public view returns (DealData memory) { return deals[agreementId]; } From f15800a302be5e449bdc6bc429b655c3c5c45641 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 9 Oct 2025 17:08:44 -0700 Subject: [PATCH 02/14] fix: stack too deep --- src/MetaVesTController.sol | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 74a7faf..e2a7b76 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -285,8 +285,8 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { uint256 salt, metavestType _metavestType, address grantee, - BaseAllocation.Allocation calldata allocation, - BaseAllocation.Milestone[] calldata milestones, + BaseAllocation.Allocation memory allocation, + BaseAllocation.Milestone[] memory milestones, string[] memory globalValues, address[] memory parties, string[][] memory partyValues, @@ -295,14 +295,14 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { uint256 expiry ) external returns (bytes32) { - bytes32 agreementId = ICyberAgreementRegistry(registry).createContract( + // Call internal function to avoid stack-too-deep errors + bytes32 agreementId = _createAgreement( templateId, salt, globalValues, parties, partyValues, secretHash, - address(this), expiry ); @@ -337,6 +337,27 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { return agreementId; } + function _createAgreement( + bytes32 templateId, + uint256 salt, + string[] memory globalValues, + address[] memory parties, + string[][] memory partyValues, + bytes32 secretHash, + uint256 expiry + ) internal returns (bytes32) { + return ICyberAgreementRegistry(registry).createContract( + templateId, + salt, + globalValues, + parties, + partyValues, + secretHash, + address(this), + expiry + ); + } + function signDealAndCreateMetavest( address grantee, address recipient, From efe05ed5be2200cecf94912256bd504fa3211e03 Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 10 Oct 2025 10:42:18 -0700 Subject: [PATCH 03/14] wip: test: adopt tests from zk-sync-guardian-compensations --- .gitmodules | 6 + lib/openzeppelin-contracts | 1 + lib/openzeppelin-contracts-upgradeable | 1 + remappings.txt | 12 +- src/BaseAllocation.sol | 2 +- src/MetaVesTController.sol | 2 +- test/AuditBaseA2.t.sol | 2 +- test/AuditBaseC.t.sol | 19 +- test/AuditBaseC3.t.sol | 19 +- test/VestingAllocation.t.sol | 49 +--- test/amendement.t.sol | 32 +-- test/controller.t.sol | 283 +++++++++++------------- test/lib/MetaVesTControllerTestBase.sol | 24 +- test/mocks/MockERC20.sol | 9 + 14 files changed, 220 insertions(+), 241 deletions(-) create mode 160000 lib/openzeppelin-contracts create mode 160000 lib/openzeppelin-contracts-upgradeable create mode 100644 test/mocks/MockERC20.sol diff --git a/.gitmodules b/.gitmodules index 85b1ec0..357827c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,9 @@ [submodule "lib/cybercorps-contracts"] path = lib/cybercorps-contracts url = https://github.com/MetaLex-Tech/cybercorps-contracts +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/openzeppelin/openzeppelin-contracts +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..c64a1ed --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit c64a1edb67b6e3f4a15cca8909c9482ad33a02b0 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..e725abd --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit e725abddf1e01cf05ace496e950fc8e243cc7cab diff --git a/remappings.txt b/remappings.txt index 8aeadcc..8f75360 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,12 +1,4 @@ -ds-test/=lib/zk-governance/l2-contracts/lib/forge-std/lib/ds-test/src/ -erc4626-tests/=lib/zk-governance/l1-contracts/lib/openzeppelin-contracts/lib/erc4626-tests/ -flexible-voting/=lib/zk-governance/l2-contracts/lib/flexible-voting/ forge-std/=lib/forge-std/src/ -murky/=lib/zk-governance/l2-contracts/lib/murky/ -openzeppelin-contracts-upgradeable/=lib/zk-governance/l2-contracts/lib/openzeppelin-contracts-upgradeable/ -openzeppelin-contracts/=lib/zk-governance/l1-contracts/lib/openzeppelin-contracts/ -@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/ +openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/ cybercorps-contracts/=lib/cybercorps-contracts/ diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index 17684e1..c157913 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.28; /// @notice interface to a MetaLeX condition contract /// @dev see https://github.com/MetaLex-Tech/BORG-CORE/tree/main/src/libs/conditions diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index e2a7b76..4ad61a1 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -9,7 +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 {UUPSUpgradeable} from "openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "./BaseAllocation.sol"; //import "./RestrictedTokenAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; diff --git a/test/AuditBaseA2.t.sol b/test/AuditBaseA2.t.sol index 6a4288e..427dd78 100644 --- a/test/AuditBaseA2.t.sol +++ b/test/AuditBaseA2.t.sol @@ -14,7 +14,7 @@ contract EvilGrant { } } -// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +// TODO WIP: non-VestingAllocation tests are disabled until reviewed with new design with CyberAgreementRegistry contract Audit is MetaVestControllerTest { function test_RevertIf_AuditArbitraryVote() public { // template from testVoteOnMetavestAmendment diff --git a/test/AuditBaseC.t.sol b/test/AuditBaseC.t.sol index b466e23..646e8f7 100644 --- a/test/AuditBaseC.t.sol +++ b/test/AuditBaseC.t.sol @@ -4,9 +4,26 @@ import "forge-std/Test.sol"; import "../test/controller.t.sol"; -// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +// TODO WIP: non-VestingAllocation tests are disabled until reviewed with new design with CyberAgreementRegistry contract Audit is MetaVestControllerTest { + function testFailAuditTerminateFailAfterWithdraw() public { + // template from testTerminateVestAndRecoverSlowUnlock + address vestingAllocation = createDummyVestingAllocationSlowUnlock(); + uint256 snapshot = paymentToken.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(); diff --git a/test/AuditBaseC3.t.sol b/test/AuditBaseC3.t.sol index 31f356e..8a562fa 100644 --- a/test/AuditBaseC3.t.sol +++ b/test/AuditBaseC3.t.sol @@ -4,9 +4,26 @@ import "forge-std/Test.sol"; import "../test/controller.t.sol"; -// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +// TODO WIP: non-VestingAllocation tests are disabled until reviewed with new design with CyberAgreementRegistry contract Audit is MetaVestControllerTest { + function testFailAuditTerminateFailAfterWithdraw() public { + // template from testTerminateVestAndRecoverSlowUnlock + address vestingAllocation = createDummyVestingAllocationSlowUnlock(); + uint256 snapshot = paymentToken.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(); diff --git a/test/VestingAllocation.t.sol b/test/VestingAllocation.t.sol index 76c1231..dd2dee7 100644 --- a/test/VestingAllocation.t.sol +++ b/test/VestingAllocation.t.sol @@ -2,26 +2,18 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; -import {ZkCappedMinterV2, IMintable} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; -import {ZkTokenV1} from "zk-governance/l2-contracts/src/ZkTokenV1.sol"; +import {MockERC20} from "./mocks/MockERC20.sol"; import {BaseAllocation} from "../src/BaseAllocation.sol"; import {VestingAllocation} from "../src/VestingAllocation.sol"; import {metavestController} from "../src/MetaVesTController.sol"; contract MockMetaVesTController { address public authority; - address public zkCappedMinter; constructor( - address _authority, - address _zkCappedMinter + address _authority ) { authority = _authority; - zkCappedMinter = _zkCappedMinter; - } - - function mint(address recipient, uint256 amount) external { - ZkCappedMinterV2(zkCappedMinter).mint(recipient, amount); } function updateMetavestVestingRate( @@ -38,35 +30,16 @@ contract VestingAllocationTest is Test { address recipient = address(0xb); address newRecipient = address(0xc); - ZkTokenV1 zkToken; + MockERC20 paymentToken; MockMetaVesTController mockController; VestingAllocation vestingAllocation; function setUp() public { - zkToken = new ZkTokenV1(); - zkToken.initialize(address(this), address(this), 0 ether); - - // 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)); + paymentToken = new MockERC20("Payment Token", "PAY"); // Create mock controller - mockController = new MockMetaVesTController(address(this), address(zkCappedMinter)); - - // Grant controller minter privilege - zkCappedMinter.grantRole( - zkCappedMinter.MINTER_ROLE(), - address(mockController) - ); + mockController = new MockMetaVesTController(address(this)); BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); milestones[0] = BaseAllocation.Milestone({ @@ -81,7 +54,7 @@ contract VestingAllocationTest is Test { recipient, address(mockController), BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 1000 ether, vestingCliffCredit: 100 ether, unlockingCliffCredit: 100 ether, @@ -101,14 +74,14 @@ contract VestingAllocationTest is Test { function test_Withdraw() public { // Should withdraw to recipient by default - uint256 balanceBefore = zkToken.balanceOf(recipient); + uint256 balanceBefore = paymentToken.balanceOf(recipient); vm.expectEmit(true, true, true, true); - emit BaseAllocation.MetaVesT_Withdrawn(grantee, recipient, address(zkToken), 100 ether); + emit BaseAllocation.MetaVesT_Withdrawn(grantee, recipient, address(paymentToken), 100 ether); vm.prank(grantee); VestingAllocation(vestingAllocation).withdraw(100 ether); - assertEq(zkToken.balanceOf(recipient), balanceBefore + 100 ether); + assertEq(paymentToken.balanceOf(recipient), balanceBefore + 100 ether); } function test_RevertIf_WithdrawTooMuch() public { @@ -125,10 +98,10 @@ contract VestingAllocationTest is Test { VestingAllocation(vestingAllocation).updateRecipient(newRecipient); // Should withdraw to new recipient now - uint256 balanceBefore = zkToken.balanceOf(newRecipient); + uint256 balanceBefore = paymentToken.balanceOf(newRecipient); vm.prank(grantee); VestingAllocation(vestingAllocation).withdraw(100 ether); - assertEq(zkToken.balanceOf(newRecipient), balanceBefore + 100 ether); + assertEq(paymentToken.balanceOf(newRecipient), balanceBefore + 100 ether); } function test_RevertIf_UpdateRecipientNonGrantee() public { diff --git a/test/amendement.t.sol b/test/amendement.t.sol index 9b300ad..d0bcbee 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -8,10 +8,9 @@ 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 +// TODO WIP: non-VestingAllocation tests are disabled until reviewed with new design with CyberAgreementRegistry contract MetaVestControllerTest is MetaVesTControllerTestBase { address public authority = guardianSafe; address public dao = guardianSafe; @@ -27,7 +26,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { function setUp() public override { MetaVesTControllerTestBase.setUp(); - + vm.startPrank(deployer); // Deploy MetaVesT controller @@ -45,22 +44,10 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { ) ))); - // 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"); // Guardian SAFE to delegate signing to an EOA @@ -70,6 +57,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.stopPrank(); vestingAllocation = createDummyVestingAllocation(); + + // TODO WIP: review needed + paymentToken.mint(authority, 1000000e58); + + paymentToken.transfer(address(grantee), 1000e25); + + vm.prank(authority); + controller.createSet("testSet"); } function testProposeMetavestAmendment() public { @@ -380,7 +375,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, // = grantee BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 1000 ether, vestingCliffCredit: 100 ether, unlockingCliffCredit: 100 ether, @@ -394,11 +389,6 @@ 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 _granteeSignDeal( contractIdAlice, alice, // grantee diff --git a/test/controller.t.sol b/test/controller.t.sol index 46de3a6..74af0bc 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -8,16 +8,13 @@ pragma solidity ^0.8.20; 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"; import {ERC1967ProxyLib} from "./lib/ERC1967ProxyLib.sol"; -import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; contract MetaVestControllerTest is MetaVesTControllerTestBase { using ERC1967ProxyLib for address; - + address authority = guardianSafe; address dao = guardianSafe; address grantee = alice; @@ -48,28 +45,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { ) ))); - vm.startPrank(zkTokenAdmin); - - // 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)); - - // Simulate capped minter granting permission to MetaVesTcontroller - zkCappedMinter.grantRole(zkCappedMinter.MINTER_ROLE(), address(controller)); - vm.stopPrank(); vm.startPrank(guardianSafe); - controller.setZkCappedMinter(address(zkCappedMinter)); // Guardian SAFE to set capped minter on MetaVesTController controller.createSet("testSet"); vm.stopPrank(); @@ -86,15 +64,15 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), // 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 + vestingStartTime: uint48(block.timestamp), unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + unlockStartTime: uint48(block.timestamp) }), new BaseAllocation.Milestone[](0), "Alice", @@ -118,7 +96,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { // function testCreateTokenOptionAllocation() public { // BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ -// tokenContract: address(zkToken), +// tokenContract: address(paymentToken), // tokenStreamTotal: 1000e18, // vestingCliffCredit: 100e18, // unlockingCliffCredit: 100e18, @@ -147,13 +125,13 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { // 0 // ); // -// assertEq(zkToken.balanceOf(address(tokenOptionAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// assertEq(paymentToken.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), +// tokenContract: address(token), // tokenStreamTotal: 1000e18, // vestingCliffCredit: 100e18, // unlockingCliffCredit: 100e18, @@ -171,6 +149,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { // conditionContracts: new address[](0) // }); // +// token.approve(address(controller), 1100e18); +// // address restrictedTokenAward = controller.createMetavest( // metavestController.metavestType.RestrictedTokenAward, // grantee, @@ -183,7 +163,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { // // ); // -// assertEq(zkToken.balanceOf(address(restrictedTokenAward)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// assertEq(token.balanceOf(restrictedTokenAward), 1100e18); // //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); // } @@ -566,7 +546,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, // = grantee BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 1000 ether, vestingCliffCredit: 100 ether, unlockingCliffCredit: 100 ether, @@ -607,7 +587,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, // = grantee BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 1000 ether, vestingCliffCredit: 100 ether, unlockingCliffCredit: 100 ether, @@ -647,7 +627,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, // = grantee BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 1000 ether, vestingCliffCredit: 100 ether, unlockingCliffCredit: 100 ether, @@ -681,7 +661,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, // = grantee BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 1000 ether, vestingCliffCredit: 0 ether, unlockingCliffCredit: 0 ether, @@ -715,7 +695,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, // = grantee BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 1000 ether, vestingCliffCredit: 0 ether, unlockingCliffCredit: 0 ether, @@ -902,7 +882,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { function testTerminateVestAndRecovers() public { address vestingAllocation = createDummyVestingAllocation(); - uint256 snapshot = zkToken.balanceOf(authority); + uint256 snapshot = paymentToken.balanceOf(authority); VestingAllocation(vestingAllocation).confirmMilestone(0); vm.warp(block.timestamp + 50 seconds); vm.prank(authority); @@ -910,12 +890,12 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(zkToken.balanceOf(authority), 0); + assertEq(paymentToken.balanceOf(authority), 0); } function testTerminateVestAndRecoverSlowUnlock() public { address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = zkToken.balanceOf(authority); + uint256 snapshot = paymentToken.balanceOf(authority); VestingAllocation(vestingAllocation).confirmMilestone(0); vm.warp(block.timestamp + 25 seconds); vm.prank(authority); @@ -925,24 +905,24 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.warp(block.timestamp + 25 seconds); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(zkToken.balanceOf(vestingAllocation), 0); + assertEq(paymentToken.balanceOf(vestingAllocation), 0); } function testTerminateRecoverAll() public { address vestingAllocation = createDummyVestingAllocationLarge(); - uint256 snapshot = zkToken.balanceOf(authority); + uint256 snapshot = paymentToken.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); + assertEq(paymentToken.balanceOf(authority), 0); } function testTerminateRecoverChunksBefore() public { address vestingAllocation = createDummyVestingAllocationLarge(); - uint256 snapshot = zkToken.balanceOf(authority); + uint256 snapshot = paymentToken.balanceOf(authority); vm.warp(block.timestamp + 25 seconds); vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); @@ -953,7 +933,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(zkToken.balanceOf(authority), 0); + assertEq(paymentToken.balanceOf(authority), 0); } // function testConfirmingMilestoneRestrictedTokenAllocation() public { @@ -981,7 +961,7 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { function testUnlockMilestoneNotUnlocked() public { address vestingAllocation = createDummyVestingAllocationNoUnlock(); - uint256 snapshot = zkToken.balanceOf(authority); + uint256 snapshot = paymentToken.balanceOf(authority); VestingAllocation(vestingAllocation).confirmMilestone(0); vm.warp(block.timestamp + 50 seconds); vm.startPrank(grantee); @@ -1388,39 +1368,40 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { controller.updateFunctionCondition(address(condition), functionSig); } - function test_RevertIf_ExceedCap() public { - // Add a large grant that exceeds the cap - bytes32 contractIdChad = _proposeAndSignDeal( - templateId, - block.timestamp, // salt - delegatePrivateKey, - chad, - 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), - "Chad", - cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible - ); - VestingAllocation vestingAllocationChad = VestingAllocation(_granteeSignDeal( - contractIdChad, - chad, // grantee - chad, // recipient - chadPrivateKey, - "Chad" - )); - - vm.prank(chad); - vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(controller), 2001 ether)); - vestingAllocationChad.withdraw(2001 ether); - } + // TODO deprecated: do we still need this? +// function test_RevertIf_ExceedCap() public { +// // Add a large grant that exceeds the cap +// bytes32 contractIdChad = _proposeAndSignDeal( +// templateId, +// block.timestamp, // salt +// delegatePrivateKey, +// chad, +// BaseAllocation.Allocation({ +// tokenContract: address(paymentToken), +// tokenStreamTotal: 2001 ether, +// vestingCliffCredit: 2001 ether, +// unlockingCliffCredit: 2001 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(_granteeSignDeal( +// contractIdChad, +// chad, // grantee +// chad, // recipient +// chadPrivateKey, +// "Chad" +// )); +// +// vm.prank(chad); +// vm.expectRevert(abi.encodeWithSelector(IZkCappedMinterV2.ZkCappedMinterV2__CapExceeded.selector, address(controller), 2001 ether)); +// vestingAllocationChad.withdraw(2001 ether); +// } function test_RevertIf_IncorrectGrantorSignature() public { // Should not be able to propose a deal without grantor's authorization @@ -1430,14 +1411,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { alicePrivateKey, // Should fail because Alice is not delegated by the grantor alice, // grantee BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 100 ether, vestingCliffCredit: 10 ether, unlockingCliffCredit: 10 ether, vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + vestingStartTime: uint48(block.timestamp), unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + unlockStartTime: uint48(block.timestamp) }), new BaseAllocation.Milestone[](0), "Alice", @@ -1453,14 +1434,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 100 ether, vestingCliffCredit: 10 ether, unlockingCliffCredit: 10 ether, vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + vestingStartTime: uint48(block.timestamp), unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + unlockStartTime: uint48(block.timestamp) }), new BaseAllocation.Milestone[](0), "Alice", @@ -1491,14 +1472,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 100 ether, vestingCliffCredit: 10 ether, unlockingCliffCredit: 10 ether, vestingRate: 1 ether, - vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + vestingStartTime: uint48(block.timestamp), unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + unlockStartTime: uint48(block.timestamp) }), new BaseAllocation.Milestone[](0), "Alice", @@ -1520,68 +1501,70 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { assertFalse(registry.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); } - function test_TogglePauseMinting() public { - 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(controllerOwnedMinter.paused(), "minter should be paused now"); - - vm.prank(authority); - controller.unpauseZkCappedMinter(); - assertFalse(controllerOwnedMinter.paused(), "minter should be unpaused now"); - } - - 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(); - } - - function test_CloseMinting() public { - 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(controllerOwnedMinter.closed(), "minter should be closed now"); - } - - 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(); - } + // TODO WIP: re-purpose it for withdrawing funds from active metavest +// function test_TogglePauseMinting() public { +// IZkCappedMinterV2 controllerOwnedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( +// address(paymentToken), +// 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(controllerOwnedMinter.paused(), "minter should be paused now"); +// +// vm.prank(authority); +// controller.unpauseZkCappedMinter(); +// assertFalse(controllerOwnedMinter.paused(), "minter should be unpaused now"); +// } +// +// 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(); +// } +// +// function test_CloseMinting() public { +// IZkCappedMinterV2 controllerOwnedMinter = IZkCappedMinterV2(zkCappedMinterFactory.createCappedMinter( +// address(paymentToken), +// 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(controllerOwnedMinter.closed(), "minter should be closed now"); +// } +// +// 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(); +// } - 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); - } + // TODO deprecated: can we re-purpose it? +// 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 { // Deploy new implementation @@ -1606,15 +1589,15 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), // 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 + vestingStartTime: uint48(block.timestamp), unlockRate: 1 ether, - unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + unlockStartTime: uint48(block.timestamp) }), new BaseAllocation.Milestone[](0), "Alice", diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index a9877a7..e1f3ed2 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -4,23 +4,15 @@ 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 {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.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"; +import {MockERC20} from "../mocks/MockERC20.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; + // TODO WIP: provision fundings + MockERC20 paymentToken = new MockERC20("Payment Token", "PAY"); address deployer = address(0x2); address guardianSafe = address(0x3); @@ -95,15 +87,13 @@ contract MetaVesTControllerTestBase is Test { function _granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) internal { address grantee = vestingAllocation.grantee(); - uint256 balanceBefore = zkToken.balanceOf(grantee); + uint256 balanceBefore = paymentToken.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)"))); + assertEq(paymentToken.balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); + assertEq(paymentToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); } function _proposeAndSignDeal( diff --git a/test/mocks/MockERC20.sol b/test/mocks/MockERC20.sol new file mode 100644 index 0000000..0f2a40c --- /dev/null +++ b/test/mocks/MockERC20.sol @@ -0,0 +1,9 @@ +import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20 { + constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {} + + function mint(address to, uint256 amount) public { + _mint(to, amount); + } +} From 15bdb4f13b6422ebc5cc8bf0573f2e6af9d5f04c Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 10 Oct 2025 11:26:54 -0700 Subject: [PATCH 04/14] fix: non-minter-based metavest should withdraw to recipient, too --- src/BaseAllocation.sol | 2 +- test/VestingAllocation.t.sol | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index c157913..d5ae8f6 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -320,7 +320,7 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ if (_amount == 0) revert MetaVesT_ZeroAmount(); if (_amount > getAmountWithdrawable() || _amount > IERC20M(allocation.tokenContract).balanceOf(address(this))) revert MetaVesT_MoreThanAvailable(); tokensWithdrawn += _amount; - safeTransfer(allocation.tokenContract, msg.sender, _amount); + safeTransfer(allocation.tokenContract, recipient, _amount); emit MetaVesT_Withdrawn(msg.sender, recipient, allocation.tokenContract, _amount); } diff --git a/test/VestingAllocation.t.sol b/test/VestingAllocation.t.sol index dd2dee7..4cb86a7 100644 --- a/test/VestingAllocation.t.sol +++ b/test/VestingAllocation.t.sol @@ -36,6 +36,7 @@ contract VestingAllocationTest is Test { VestingAllocation vestingAllocation; function setUp() public { + // Provision payment token paymentToken = new MockERC20("Payment Token", "PAY"); // Create mock controller @@ -49,6 +50,7 @@ contract VestingAllocationTest is Test { conditionContracts: new address[](0) }); + // Provision the vesting contract vestingAllocation = new VestingAllocation( grantee, recipient, @@ -65,6 +67,10 @@ contract VestingAllocationTest is Test { }), milestones ); + paymentToken.mint( + address(vestingAllocation), + 1000 ether + 2000 ether // allocation.tokenStreamTotal + milestones[].milestoneAward + ); } function test_Metadata() public { @@ -114,7 +120,10 @@ contract VestingAllocationTest is Test { 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 + emit BaseAllocation.MetaVesT_Terminated( + grantee, + 2900 ether // 1000 + 2000 - 100 (vested cliff) + ); vestingAllocation.terminate(); assertTrue(vestingAllocation.terminated(), "vesting contract should be terminated"); } From e2630fe9acd1a3c7c719dde3e82f68c1f92224a7 Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 10 Oct 2025 11:56:43 -0700 Subject: [PATCH 05/14] test: fix MetaVestControllerTest --- test/AuditBaseC.t.sol | 2 +- test/AuditBaseC3.t.sol | 2 +- test/amendement.t.sol | 15 ++++++------ test/controller.t.sol | 55 +++++++++++++++++++++++++++++++++++------- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/test/AuditBaseC.t.sol b/test/AuditBaseC.t.sol index 646e8f7..8bb8b9b 100644 --- a/test/AuditBaseC.t.sol +++ b/test/AuditBaseC.t.sol @@ -7,7 +7,7 @@ import "../test/controller.t.sol"; // TODO WIP: non-VestingAllocation tests are disabled until reviewed with new design with CyberAgreementRegistry contract Audit is MetaVestControllerTest { - function testFailAuditTerminateFailAfterWithdraw() public { + function test_RevertIf_AuditTerminateFailAfterWithdraw() public { // template from testTerminateVestAndRecoverSlowUnlock address vestingAllocation = createDummyVestingAllocationSlowUnlock(); uint256 snapshot = paymentToken.balanceOf(authority); diff --git a/test/AuditBaseC3.t.sol b/test/AuditBaseC3.t.sol index 8a562fa..418fe93 100644 --- a/test/AuditBaseC3.t.sol +++ b/test/AuditBaseC3.t.sol @@ -7,7 +7,7 @@ import "../test/controller.t.sol"; // TODO WIP: non-VestingAllocation tests are disabled until reviewed with new design with CyberAgreementRegistry contract Audit is MetaVestControllerTest { - function testFailAuditTerminateFailAfterWithdraw() public { + function test_RevertIf_AuditTerminateFailAfterWithdraw() public { // template from testTerminateVestAndRecoverSlowUnlock address vestingAllocation = createDummyVestingAllocationSlowUnlock(); uint256 snapshot = paymentToken.balanceOf(authority); diff --git a/test/amendement.t.sol b/test/amendement.t.sol index d0bcbee..48e7d19 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -46,9 +46,15 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.stopPrank(); - vm.startPrank(guardianSafe); + // Prepare funds + paymentToken.mint( + address(guardianSafe), + 9999 ether + ); + vm.prank(address(guardianSafe)); + paymentToken.approve(address(controller), 9999 ether); - controller.createSet("testSet"); + vm.startPrank(guardianSafe); // 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 @@ -58,11 +64,6 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vestingAllocation = createDummyVestingAllocation(); - // TODO WIP: review needed - paymentToken.mint(authority, 1000000e58); - - paymentToken.transfer(address(grantee), 1000e25); - vm.prank(authority); controller.createSet("testSet"); } diff --git a/test/controller.t.sol b/test/controller.t.sol index 74af0bc..a63f233 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -47,6 +47,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.stopPrank(); + // Prepare funds + paymentToken.mint( + address(guardianSafe), + 9999 ether + ); + vm.prank(address(guardianSafe)); + paymentToken.approve(address(controller), 9999 ether); + vm.startPrank(guardianSafe); controller.createSet("testSet"); vm.stopPrank(); @@ -65,7 +73,6 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { alice, BaseAllocation.Allocation({ tokenContract: address(paymentToken), - // 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, @@ -315,17 +322,23 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { address vestingAllocation = createDummyVestingAllocation(); BaseAllocation.Milestone memory newMilestone = BaseAllocation.Milestone({ - milestoneAward: 50e18, + milestoneAward: 50 ether, unlockOnCompletion: true, complete: false, conditionContracts: new address[](0) }); + uint256 balanceBefore = paymentToken.balanceOf(address(vestingAllocation)); vm.prank(authority); controller.addMetavestMilestone(vestingAllocation, newMilestone); + assertEq( + paymentToken.balanceOf(address(vestingAllocation)) - balanceBefore, + 50 ether, + "vesting contract should receive token amount add by the milestone" + ); - // BaseAllocation.Milestone memory addedMilestone = BaseAllocation(vestingAllocation).milestones[0]; - // assertEq(addedMilestone.milestoneAward, 50e18); + (uint256 milestoneAward, , ) = BaseAllocation(vestingAllocation).milestones(1); + assertEq(milestoneAward, 50 ether, "milestone should be added"); } function testUpdateUnlockRate() public { @@ -885,12 +898,20 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { uint256 snapshot = paymentToken.balanceOf(authority); VestingAllocation(vestingAllocation).confirmMilestone(0); vm.warp(block.timestamp + 50 seconds); + + uint256 authorityBalanceBefore = paymentToken.balanceOf(address(authority)); vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); + assertEq(paymentToken.balanceOf( + address(authority)) - authorityBalanceBefore, + 400 ether, // 1000 + 1000 - 1000 - 100 - 10 * 50 + "authority should receive unvested funds" + ); + vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(paymentToken.balanceOf(authority), 0); + assertEq(paymentToken.balanceOf(vestingAllocation), 0); } function testTerminateVestAndRecoverSlowUnlock() public { @@ -911,16 +932,24 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { function testTerminateRecoverAll() public { address vestingAllocation = createDummyVestingAllocationLarge(); uint256 snapshot = paymentToken.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); + vm.warp(block.timestamp + 25 seconds); + + uint256 authorityBalanceBefore = paymentToken.balanceOf(address(authority)); vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); + assertEq(paymentToken.balanceOf( + address(authority)) - authorityBalanceBefore, + 750 ether, // 1000 - 10 * 25 + "authority should receive unvested funds" + ); + vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(paymentToken.balanceOf(authority), 0); + assertEq(paymentToken.balanceOf(vestingAllocation), 0); } - function testTerminateRecoverChunksBefore() public { + function testTerminateRecoverChunksBefore() public { address vestingAllocation = createDummyVestingAllocationLarge(); uint256 snapshot = paymentToken.balanceOf(authority); vm.warp(block.timestamp + 25 seconds); @@ -928,12 +957,20 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); vm.warp(block.timestamp + 25 seconds); + + uint256 authorityBalanceBefore = paymentToken.balanceOf(address(authority)); vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); + assertEq(paymentToken.balanceOf( + address(authority)) - authorityBalanceBefore, + 500 ether, // 1000 - 10 * 50 + "authority should receive unvested funds" + ); + vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(paymentToken.balanceOf(authority), 0); + assertEq(paymentToken.balanceOf(vestingAllocation), 0); } // function testConfirmingMilestoneRestrictedTokenAllocation() public { From 18361c3b5b5fd5569107ecb4639fe5a90aa967cc Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 10 Oct 2025 13:49:36 -0700 Subject: [PATCH 06/14] fix: metavestController size too large --- src/MetaVesTController.sol | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 4ad61a1..d89d6c4 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -589,19 +589,19 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { BaseAllocation(_grant).updateTransferability(_isTransferable); } - /// @notice for the controller to update either exercisePrice or repurchasePrice for a '_grantee' and their transferees, as applicable depending on the '_grantee''s MetaVesTType - /// @param _grant address of grantee whose applicable price is being updated - /// @param _newPrice new exercisePrice (if token option) or (repurchase price if restricted token award) as 'paymentToken' per 1 metavested token in vesting token decimals but only up to payment decimal precision - function updateExerciseOrRepurchasePrice( - address _grant, - uint256 _newPrice - ) external onlyAuthority conditionCheck consentCheck(_grant, msg.data) { - if (_newPrice == 0) revert MetaVesTController_ZeroPrice(); - IPriceAllocation grant = IPriceAllocation(_grant); - if(grant.getVestingType()!=2 && grant.getVestingType()!=3) revert MetaVesTController_IncorrectMetaVesTType(); - _resetAmendmentParams(_grant, msg.sig); - grant.updatePrice(_newPrice); - } +// /// @notice for the controller to update either exercisePrice or repurchasePrice for a '_grantee' and their transferees, as applicable depending on the '_grantee''s MetaVesTType +// /// @param _grant address of grantee whose applicable price is being updated +// /// @param _newPrice new exercisePrice (if token option) or (repurchase price if restricted token award) as 'paymentToken' per 1 metavested token in vesting token decimals but only up to payment decimal precision +// function updateExerciseOrRepurchasePrice( +// address _grant, +// uint256 _newPrice +// ) external onlyAuthority conditionCheck consentCheck(_grant, msg.data) { +// if (_newPrice == 0) revert MetaVesTController_ZeroPrice(); +// IPriceAllocation grant = IPriceAllocation(_grant); +// if(grant.getVestingType()!=2 && grant.getVestingType()!=3) revert MetaVesTController_IncorrectMetaVesTType(); +// _resetAmendmentParams(_grant, msg.sig); +// grant.updatePrice(_newPrice); +// } /// @notice removes a milestone from '_grantee''s MetaVesT if such milestone has not yet been confirmed, also making the corresponding 'milestoneAward' tokens withdrawable by controller /// @param _grant address of grantee whose MetaVesT is being updated From fb2086b66c49dab9560e966cb33790ba89019af4 Mon Sep 17 00:00:00 2001 From: detoo Date: Fri, 10 Oct 2025 16:31:15 -0700 Subject: [PATCH 07/14] chore: migrate deploy scripts and tests --- lib/zk-governance | 1 - scripts/createAllTemplates.s.sol | 53 +-- scripts/deploy.s.sol | 51 --- scripts/deployTestZkCappedMinter.s.sol | 87 ---- ....sol => deployYearnBorgCompensation.s.sol} | 73 ++- ...yYearnBorgCompensationPrerequisites.s.sol} | 46 +- ...sol => YearnBorgCompensation2025_2026.sol} | 134 ++---- .../ZkSyncGuardianCompensation2025_2026.sol | 54 --- ...ncGuardianCompensationSepolia2024_2025.sol | 59 --- .../proposeAllGuardiansMetavestDeals.s.sol | 70 +-- scripts/proposeBorgResolution.s.sol | 109 ----- scripts/proposeMetavestDeal.s.sol | 53 +-- scripts/signDealAndCreateMetavest.s.sol | 26 +- src/interfaces/zk-governance/IMintable.sol | 6 - .../zk-governance/IMintableAndDelegatable.sol | 10 - .../zk-governance/IZkCappedMinterV2.sol | 26 -- .../IZkCappedMinterV2Factory.sol | 13 - src/interfaces/zk-governance/IZkTokenV1.sol | 10 - test/YearnBorgCompensation.t.sol | 275 +++++++++++ test/ZkSyncGuardianCompensation.t.sol | 427 ------------------ ...ZkSyncGuardianCompensationAcceptance.t.sol | 133 ------ 21 files changed, 429 insertions(+), 1287 deletions(-) delete mode 160000 lib/zk-governance delete mode 100644 scripts/deploy.s.sol delete mode 100644 scripts/deployTestZkCappedMinter.s.sol rename scripts/{deployZkSyncGuardianCompensation.s.sol => deployYearnBorgCompensation.s.sol} (54%) rename scripts/{deployZkSyncGuardianCompensationPrerequisites.s.sol => deployYearnBorgCompensationPrerequisites.s.sol} (57%) rename scripts/lib/{ZkSyncGuardianCompensation2024_2025.sol => YearnBorgCompensation2025_2026.sol} (55%) delete mode 100644 scripts/lib/ZkSyncGuardianCompensation2025_2026.sol delete mode 100644 scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol delete mode 100644 scripts/proposeBorgResolution.s.sol delete mode 100644 src/interfaces/zk-governance/IMintable.sol delete mode 100644 src/interfaces/zk-governance/IMintableAndDelegatable.sol delete mode 100644 src/interfaces/zk-governance/IZkCappedMinterV2.sol delete mode 100644 src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol delete mode 100644 src/interfaces/zk-governance/IZkTokenV1.sol create mode 100644 test/YearnBorgCompensation.t.sol delete mode 100644 test/ZkSyncGuardianCompensation.t.sol delete mode 100644 test/ZkSyncGuardianCompensationAcceptance.t.sol diff --git a/lib/zk-governance b/lib/zk-governance deleted file mode 160000 index f9915cb..0000000 --- a/lib/zk-governance +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f9915cb59ff1ab8f2819f3ec3f6189a71e3b65f0 diff --git a/scripts/createAllTemplates.s.sol b/scripts/createAllTemplates.s.sol index 31cbe86..0db7f72 100644 --- a/scripts/createAllTemplates.s.sol +++ b/scripts/createAllTemplates.s.sol @@ -1,14 +1,11 @@ // 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 {YearnBorgCompensation2025_2026} from "./lib/YearnBorgCompensation2025_2026.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 {ERC1967Proxy} from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; import {Script} from "forge-std/Script.sol"; import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; @@ -19,54 +16,24 @@ contract CreateAllTemplatesScript is SafeTxHelper, Script { /// @dev For running from `forge script` function run() public virtual { // zkSync mainnet - run(ZkSyncGuardianCompensation2024_2025.getDefault(vm)); + run(YearnBorgCompensation2025_2026.getDefault(vm)); } /// @dev For running in tests function run( - ZkSyncGuardianCompensation2024_2025.Config memory config + YearnBorgCompensation2025_2026.Config memory config ) public virtual returns(GnosisTransaction[] memory) { IGnosisSafe safe; - ZkSyncGuardianCompensation2024_2025.Config memory config; + YearnBorgCompensation2025_2026.Config memory config; // zkSync Era (zkSync Guardians) - config = ZkSyncGuardianCompensation2024_2025.getDefault(vm); + config = YearnBorgCompensation2025_2026.getDefault(vm); - safe = config.guardianSafe; + safe = config.borgSafe; - // 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]; + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](config.compRecipients.length); + for (uint i = 0; i < config.compRecipients.length ; i++) { + YearnBorgCompensation2025_2026.CompInfo memory guardian = config.compRecipients[i]; safeTxs[i] = GnosisTransaction({ to: address(config.registry), value: 0 ether, diff --git a/scripts/deploy.s.sol b/scripts/deploy.s.sol deleted file mode 100644 index b731650..0000000 --- a/scripts/deploy.s.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; - -import {Script} from "forge-std/Script.sol"; -import {console} from "forge-std/console.sol"; -import "../src/VestingAllocationFactory.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "../src/MetaVesTController.sol"; -import "../src/MetaVesTFactory.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkCappedMinterFactory.sol"; - -contract BaseScript is Script { - address deployerAddress; - address metaVesTController; - - - - function run() public { - deployerAddress = vm.addr(vm.envUint("PRIVATE_KEY_DEPLOY")); - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY_DEPLOY"); - address dao = 0xda0d1a30949b870a1FA7B2792B03070395720Da0; - address borg = 0x9EfbfE69a522aC81685e21EEb52aFBd5398b2CBc; - - vm.startBroadcast(deployerPrivateKey); - VestingAllocationFactory vestingFactory = new VestingAllocationFactory(); - TokenOptionFactory tokenOptionFactory = new TokenOptionFactory(); - RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); - MetaVesTFactory factory = new MetaVesTFactory(); - - ZkTokenV2 zkToken = new ZkTokenV2(); - zkToken.initialize(dao, dao, 0); - ZkCappedMinterFactory zkMinterFactory = new ZkCappedMinterFactory(0x073749a0f8ed0d49b1acfd4e0efdc59328c83d0c2eed9ee099a3979f0c332ff8); - - - metaVesTController = factory.deployMetavestAndController(borg, borg, address(vestingFactory), address(tokenOptionFactory), address(restrictedTokenFactory), address(zkMinterFactory), address(zkToken)); - // metaVesTController = new metavestController(dao, deployerAddress, address(vestingFactory), address(tokenOptionFactory), address(restrictedTokenFactory)); - vm.stopBroadcast(); - console.log("Deployer: ", deployerAddress); - console.log("Deployed"); - console.log("Addresses:"); - console.log("VestingAllocationFactory: ", address(vestingFactory)); - console.log("TokenOptionFactory: ", address(tokenOptionFactory)); - console.log("RestrictedTokenFactory: ", address(restrictedTokenFactory)); - console.log("MetaVesTController: ", metaVesTController); - console.log("MetaVesTFactory: ", address(factory)); - console.log("ZkToken: ", address(zkToken)); - console.log("ZkCappedMinterFactory: ", address(zkMinterFactory)); - } -} \ No newline at end of file diff --git a/scripts/deployTestZkCappedMinter.s.sol b/scripts/deployTestZkCappedMinter.s.sol deleted file mode 100644 index 49b5e2e..0000000 --- a/scripts/deployTestZkCappedMinter.s.sol +++ /dev/null @@ -1,87 +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 {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(vm) - - // zkSync Sepolia for 2025-2026 -// "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.2025-2026", -// IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E), -// ZkSyncGuardianCompensationSepolia2025_2026.getDefault(vm) - ); - } - - /// @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/deployYearnBorgCompensation.s.sol similarity index 54% rename from scripts/deployZkSyncGuardianCompensation.s.sol rename to scripts/deployYearnBorgCompensation.s.sol index 9edc0b0..ec86e7a 100644 --- a/scripts/deployZkSyncGuardianCompensation.s.sol +++ b/scripts/deployYearnBorgCompensation.s.sol @@ -1,41 +1,26 @@ // 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 {YearnBorgCompensation2025_2026} from "./lib/YearnBorgCompensation2025_2026.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 {ERC1967Proxy} from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.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 DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { +contract DeployYearnBorgCompensationScript is SafeTxHelper, Script { /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { deployCompensation( vm.envUint("DEPLOYER_PRIVATE_KEY"), - // zkSync Era for 2024-2025 -// "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2024-2025", -// ZkSyncGuardianCompensation2024_2025.getDefault(vm) - - // zkSync Era for 2025-2026 -// "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2025-2026", -// ZkSyncGuardianCompensation2025_2026.getDefault(vm) - - // zkSync Sepolia for 2024-2025 - "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.1.2024-2025", - ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) - - // zkSync Sepolia for 2025-2026 -// "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.2025-2026", -// ZkSyncGuardianCompensationSepolia2025_2026.getDefault(vm) + // Ethereum mainnet for 2025-2026 + "MetaLexMetaVestYearnBorgCompensationLaunchV1.0.2025-2026", + YearnBorgCompensation2025_2026.getDefault(vm) ); } @@ -43,7 +28,7 @@ contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { function deployCompensation( uint256 deployerPrivateKey, string memory saltStr, - ZkSyncGuardianCompensation2024_2025.Config memory config + YearnBorgCompensation2025_2026.Config memory config ) public virtual returns( metavestController, GnosisTransaction[] memory @@ -51,11 +36,10 @@ contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { address deployer = vm.addr(deployerPrivateKey); console2.log(""); - console2.log("=== DeployZkSyncGuardianCompensationScript ==="); + console2.log("=== DeployYearnBorgCompensationScript ==="); console2.log("Deployer: ", deployer); console2.log("Salt string: ", saltStr); - console2.log("ZkCappedMinterV2: ", address(config.zkCappedMinter)); - console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("Guardian Safe: ", address(config.borgSafe)); console2.log("CyberAgreementRegistry: ", address(config.registry)); console2.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); console2.log(""); @@ -70,8 +54,8 @@ contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { address(new metavestController{salt: salt}()), abi.encodeWithSelector( metavestController.initialize.selector, - address(config.guardianSafe), - address(config.guardianSafe), + address(config.borgSafe), + address(config.borgSafe), address(config.registry), address(config.vestingAllocationFactory) ) @@ -79,27 +63,28 @@ contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { vm.stopBroadcast(); + // TODO WIP: re-purpose to provision USDC funds // 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(config.zkCappedMinter), - value: 0, - data: abi.encodeWithSelector( - IZkCappedMinterV2.grantRole.selector, - config.zkCappedMinter.MINTER_ROLE(), - address(controller) - ) - }); - safeTxs[1] = GnosisTransaction({ - to: address(controller), - value: 0, - data: abi.encodeWithSelector( - controller.setZkCappedMinter.selector, - address(config.zkCappedMinter) - ) - }); +// safeTxs[0] = GnosisTransaction({ +// to: address(config.zkCappedMinter), +// value: 0, +// data: abi.encodeWithSelector( +// IZkCappedMinterV2.grantRole.selector, +// config.zkCappedMinter.MINTER_ROLE(), +// address(controller) +// ) +// }); +// safeTxs[1] = GnosisTransaction({ +// to: address(controller), +// value: 0, +// data: abi.encodeWithSelector( +// controller.setZkCappedMinter.selector, +// address(config.zkCappedMinter) +// ) +// }); // Output logs diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployYearnBorgCompensationPrerequisites.s.sol similarity index 57% rename from scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol rename to scripts/deployYearnBorgCompensationPrerequisites.s.sol index 2c5b002..f4b153b 100644 --- a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol +++ b/scripts/deployYearnBorgCompensationPrerequisites.s.sol @@ -1,37 +1,28 @@ // 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 {YearnBorgCompensation2025_2026} from "./lib/YearnBorgCompensation2025_2026.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 {ERC1967Proxy} from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.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 SafeTxHelper, Script { - using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; +contract DeployYearnBorgCompensationPrerequisitesScript is SafeTxHelper, Script { + using YearnBorgCompensation2025_2026 for YearnBorgCompensation2025_2026.Config; /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { deployPrerequisites( vm.envUint("DEPLOYER_PRIVATE_KEY"), - // zkSync Era - "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0", - ZkSyncGuardianCompensation2024_2025.getDefault(vm) - -// // zkSync Sepolia -// "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.1", -// ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) + // Ethereum mainnet + "MetaLexMetaVestYearnBorgCompensationLaunchV1.0", + YearnBorgCompensation2025_2026.getDefault(vm) ); } @@ -39,7 +30,7 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Sc function deployPrerequisites( uint256 deployerPrivateKey, string memory saltStr, - ZkSyncGuardianCompensation2024_2025.Config memory config + YearnBorgCompensation2025_2026.Config memory config ) public virtual returns( BorgAuth, CyberAgreementRegistry, @@ -48,7 +39,7 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Sc address deployer = vm.addr(deployerPrivateKey); console2.log(""); - console2.log("=== DeployZkSyncGuardianCompensationPrerequisitesScript ==="); + console2.log("=== DeployYearnBorgCompensationPrerequisitesScript ==="); console2.log("Deployer: ", deployer); console2.log("Salt string: ", saltStr); console2.log("MetaLeX SAFE: ", address(config.metalexSafe)); @@ -70,25 +61,6 @@ contract DeployZkSyncGuardianCompensationPrerequisitesScript is SafeTxHelper, Sc ) ))); - // 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 auth.updateRole(address(config.metalexSafe), auth.OWNER_ROLE()); diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/YearnBorgCompensation2025_2026.sol similarity index 55% rename from scripts/lib/ZkSyncGuardianCompensation2024_2025.sol rename to scripts/lib/YearnBorgCompensation2025_2026.sol index 2ec0cfc..459793e 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/YearnBorgCompensation2025_2026.sol @@ -3,28 +3,24 @@ 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 { +library YearnBorgCompensation2025_2026 { struct Config { - // ZK Governance + // External dependencies - IZkTokenV1 zkToken; - IZkCappedMinterV2 zkCappedMinter; + address paymentToken; - // zkSync Guardians + // Yearn BORG - IGnosisSafe guardianSafe; - PartyInfo guardianSafeInfo; + IGnosisSafe borgSafe; + PartyInfo borgSafeInfo; // MetaLeX @@ -33,14 +29,9 @@ library ZkSyncGuardianCompensation2024_2025 { VestingAllocationFactory vestingAllocationFactory; metavestController controller; - // TODO deprecated -// // zkSync Guardian BORG Resolution -// -// TemplateInfo borgResolutionTemplate; + // Yearn BORG Director Compensation Agreement (one template per director for now) - // zkSync Guardian Compensation Agreement (one template per guardian for now) - - GuardianCompInfo[] guardians; + CompInfo[] compRecipients; uint256 fixedAnnualCompensation; uint48 metavestVestingAndUnlockStartTime; BaseAllocation.Milestone[] milestones; @@ -59,100 +50,75 @@ library ZkSyncGuardianCompensation2024_2025 { address evmAddress; } - struct GuardianCompInfo { + struct CompInfo { PartyInfo partyInfo; TemplateInfo compTemplate; bytes signature; } function getDefault(Vm vm) internal view returns(Config memory) { - IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); - IGnosisSafe metalexSafe = IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc); + IGnosisSafe borgSafe = IGnosisSafe(0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52); + IGnosisSafe metalexSafe = IGnosisSafe(0x68Ab3F79622cBe74C9683aA54D7E1BBdCAE8003C); return Config({ - // ZK Governance - - zkToken: IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E), - // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 - zkCappedMinter: IZkCappedMinterV2(0xE555FC98E45637D1B45e60E4fc05cF0F22836156), + // External dependencies - // zkSync Guardians + paymentToken: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, + + // Yearn BORG - guardianSafe: guardianSafe, - guardianSafeInfo: PartyInfo({ - name: "ZKsync Guardians", - evmAddress: address(guardianSafe) + borgSafe: borgSafe, + borgSafeInfo: PartyInfo({ + name: "Yearn BORG", + evmAddress: address(borgSafe) }), // MetaLeX metalexSafe: metalexSafe, - registry: CyberAgreementRegistry(0x07E0a0BeC742f90f7879830bC917E783dA6a6357), - vestingAllocationFactory: VestingAllocationFactory(0xD1c48D8866d81CC0f6567f3E3fbbb8eF48E30D99), - controller: metavestController(0xD509349AF986E7202f2Bc4ae49C203E354faafCD), + registry: CyberAgreementRegistry(0xa9E808B8eCBB60Bb19abF026B5b863215BC4c134), + vestingAllocationFactory: VestingAllocationFactory(0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF), // TODO TBD + controller: metavestController(0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF), // TODO TBD - // TODO deprecated -// // zkSync Guardian BORG Resolution -// -// borgResolutionTemplate: loadBorgResolutionTemplate(vm), + // Yearn BORG Compensation Agreement - // zkSync Guardian Compensation Agreement - - 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) + compRecipients: loadGuardianAndComps(vm), + fixedAnnualCompensation: 5000e6, // 5000 USDC + metavestVestingAndUnlockStartTime: 1756684800, // TODO TBD: for now it is 2025/09/01 00:00 UTC milestones: new BaseAllocation.Milestone[](0) }); } - // 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) { - uint256 numGuardians = vm.envOr("NUM_GUARDIANS", uint256(0)); - - GuardianCompInfo[] memory guardians = new GuardianCompInfo[](numGuardians); - for (uint i = 0; i < guardians.length ; i++) { - guardians[i] = GuardianCompInfo({ + function loadGuardianAndComps(Vm vm) internal view returns(CompInfo[] memory) { + uint256 numRecipients = vm.envOr("NUM_RECIPIENTS", uint256(0)); + + CompInfo[] memory compRecipients = new CompInfo[](numRecipients); + for (uint i = 0; i < compRecipients.length ; i++) { + compRecipients[i] = CompInfo({ 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)))))) + name: vm.envString(string(abi.encodePacked("RECIPIENT_NAME_", vm.toString(i)))), + evmAddress: address(uint160(vm.envUint(string(abi.encodePacked("RECIPIENT_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)))), + id: bytes32(vm.envUint(string(abi.encodePacked("RECIPIENT_TEMPLATE_ID_", vm.toString(i))))), + agreementUri: vm.envString(string(abi.encodePacked("RECIPIENT_AGREEMENT_URI_", vm.toString(i)))), + name: vm.envString(string(abi.encodePacked("RECIPIENT_TEMPLATE_NAME_", vm.toString(i)))), globalFields: getCompGlobalFields(), partyFields: getCompPartyFields() }), - signature: vm.envBytes(string(abi.encodePacked("GUARDIAN_SAFE_DELEGATE_SIGNATURE_", vm.toString(i)))) + signature: vm.envBytes(string(abi.encodePacked("RECIPIENT_SAFE_DELEGATE_SIGNATURE_", vm.toString(i)))) }); } - return guardians; + return compRecipients; } function parseAllocation(Config memory config) internal view returns(BaseAllocation.Allocation memory) { return BaseAllocation.Allocation({ - tokenContract: address(config.zkToken), - // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenContract: address(config.paymentToken), tokenStreamTotal: config.fixedAnnualCompensation, - vestingCliffCredit: 0e3 ether, - unlockingCliffCredit: 0e3 ether, + vestingCliffCredit: 0, // assume no cliff + unlockingCliffCredit: 0, // assume no cliff vestingRate: uint160(config.fixedAnnualCompensation / 365 days), vestingStartTime: config.metavestVestingAndUnlockStartTime, unlockRate: uint160(config.fixedAnnualCompensation / 365 days), @@ -192,15 +158,15 @@ 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.borgSafeInfo.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) - 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[4] = vm.toString(allocation.tokenStreamTotal / 1e6); //tokenStreamTotal (human-readable) (USDC) + globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1e6); // vestingCliffCredit (human-readable) (USDC) + globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1e6); // unlockingCliffCredit (human-readable) (USDC) + globalValues[7] = vm.toString(config.fixedAnnualCompensation / 1e6); // vestingRate (annually) (human-readable) (USDC) globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime - globalValues[9] = vm.toString(config.fixedAnnualCompensation / 1 ether); // unlockRate (annually) (human-readable) + globalValues[9] = vm.toString(config.fixedAnnualCompensation / 1e6); // unlockRate (annually) (human-readable) (USDC) globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime return globalValues; } @@ -221,11 +187,11 @@ library ZkSyncGuardianCompensation2024_2025 { function formatPartyValues( Vm vm, - PartyInfo memory guardianSafeInfo, + PartyInfo memory borgSafeInfo, PartyInfo memory guardianInfo ) internal view returns(string[][] memory) { string[][] memory partyValues = new string[][](2); - partyValues[0] = formatPartyValues(vm, guardianSafeInfo); + partyValues[0] = formatPartyValues(vm, borgSafeInfo); partyValues[1] = formatPartyValues(vm, guardianInfo); return partyValues; } diff --git a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol deleted file mode 100644 index f49a7c0..0000000 --- a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol +++ /dev/null @@ -1,54 +0,0 @@ -// 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(Vm vm) internal returns(ZkSyncGuardianCompensation2024_2025.Config memory) { - ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(vm); - - return ZkSyncGuardianCompensation2024_2025.Config({ - - // ZK Governance - - zkToken: defaultConfig.zkToken, - // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 - zkCappedMinter: IZkCappedMinterV2(0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A), - - // zkSync Guardians - - guardianSafe: defaultConfig.guardianSafe, - guardianSafeInfo: defaultConfig.guardianSafeInfo, - - // MetaLeX - - metalexSafe: defaultConfig.metalexSafe, - registry: defaultConfig.registry, - vestingAllocationFactory: defaultConfig.vestingAllocationFactory, - controller: metavestController(0x570d31F59bD0C96a9e1CC889E7E4dBd585D6915b), - - // TODO deprecated -// // zkSync Guardian BORG Resolution -// -// borgResolutionTemplate: defaultConfig.borgResolutionTemplate, - - // zkSync Guardian Compensation Agreement - - 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 deleted file mode 100644 index 5e7d447..0000000 --- a/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol +++ /dev/null @@ -1,59 +0,0 @@ -// 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(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 - - 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) - }), - - // MetaLeX - - metalexSafe: metalexSafe, - registry: CyberAgreementRegistry(0x7BD5EBE57e64AA6D9904caE90A192E76d818b49e), - vestingAllocationFactory: VestingAllocationFactory(0x3fFd990dB0E398235456A720501E6007003a6cdf), - controller: metavestController(0x856A8Aea8a37A338e2490384Bb790cD87b5CaaE4), - - // TODO deprecated -// // zkSync Guardian BORG Resolution -// -// borgResolutionTemplate: ZkSyncGuardianCompensation2024_2025.loadBorgResolutionTemplate(vm), - - // zkSync Guardian Compensation Agreement - - 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 4c56d3c..7415123 100644 --- a/scripts/proposeAllGuardiansMetavestDeals.s.sol +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -6,62 +6,34 @@ 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 {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 {YearnBorgCompensation2025_2026} from "./lib/YearnBorgCompensation2025_2026.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 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 + // Ethereum mainnet for 2025-2026 vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey uint256(0), // delegate will sign offline - uint256(keccak256("MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2024-2025")), // agreementSalt - ZkSyncGuardianCompensation2024_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) + uint256(keccak256("MetaLexMetaVestYearnBorgCompensationLaunchV1.0.2025-2026")), // agreementSalt + YearnBorgCompensation2025_2026.getDefault(vm) ); } /// @dev For running in tests function runAll( uint256 proposerPrivateKey, - uint256 guardianSafeDelegatePrivateKey, + uint256 borgSafeDelegatePrivateKey, uint256 agreementSalt, - ZkSyncGuardianCompensation2024_2025.Config memory config + YearnBorgCompensation2025_2026.Config memory config ) public virtual returns(bytes32[] memory) { address proposer = vm.addr(proposerPrivateKey); @@ -74,18 +46,18 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { console2.log("MetavesTController: ", address(config.controller)); console2.log(""); - bytes32[] memory agreementIds = new bytes32[](config.guardians.length); + bytes32[] memory agreementIds = new bytes32[](config.compRecipients.length); - for (uint256 i = 0; i < config.guardians.length; i++) { + for (uint256 i = 0; i < config.compRecipients.length; i++) { console2.log("Proposing to Guardian #%d", i + 1); - console2.log(" name:", config.guardians[i].partyInfo.name); - console2.log(" address:", config.guardians[i].partyInfo.evmAddress); + console2.log(" name:", config.compRecipients[i].partyInfo.name); + console2.log(" address:", config.compRecipients[i].partyInfo.evmAddress); console2.log(""); agreementIds[i] = runSingle( proposerPrivateKey, - guardianSafeDelegatePrivateKey, - config.guardians[i], + borgSafeDelegatePrivateKey, + config.compRecipients[i], agreementSalt, config ); @@ -103,14 +75,14 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { function runSingle( uint256 proposerPrivateKey, - uint256 guardianSafeDelegatePrivateKey, - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 borgSafeDelegatePrivateKey, + YearnBorgCompensation2025_2026.CompInfo memory guardianInfo, uint256 agreementSalt, - ZkSyncGuardianCompensation2024_2025.Config memory config + YearnBorgCompensation2025_2026.Config memory config ) public override returns(bytes32) { return ProposeMetaVestDealScript.runSingle( proposerPrivateKey, - guardianSafeDelegatePrivateKey, + borgSafeDelegatePrivateKey, guardianInfo, agreementSalt, config @@ -119,15 +91,15 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { function runSingle( uint256 proposerPrivateKey, - uint256 guardianSafeDelegatePrivateKey, - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 borgSafeDelegatePrivateKey, + YearnBorgCompensation2025_2026.CompInfo memory guardianInfo, uint256 agreementSalt, BaseAllocation.Allocation memory allocation, - ZkSyncGuardianCompensation2024_2025.Config memory config + YearnBorgCompensation2025_2026.Config memory config ) public override returns(bytes32) { return ProposeMetaVestDealScript.runSingle( proposerPrivateKey, - guardianSafeDelegatePrivateKey, + borgSafeDelegatePrivateKey, guardianInfo, agreementSalt, allocation, diff --git a/scripts/proposeBorgResolution.s.sol b/scripts/proposeBorgResolution.s.sol deleted file mode 100644 index 9f51941..0000000 --- a/scripts/proposeBorgResolution.s.sol +++ /dev/null @@ -1,109 +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 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(vm) - - // zkSync Sepolia - ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) - ); - } - - /// @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.borgResolutionTemplate.id, - agreementSalt, // salt, - globalValues, - parties - ) - ); - - bytes memory signature = CyberAgreementUtils.signAgreementTypedData( - vm, - config.registry.DOMAIN_SEPARATOR(), - config.registry.SIGNATUREDATA_TYPEHASH(), - expectedContractId, - config.borgResolutionTemplate.agreementUri, - config.borgResolutionTemplate.globalFields, - config.borgResolutionTemplate.partyFields, - globalValues, - partyValues[0], - proposerPrivateKey - ); - - vm.startBroadcast(proposerPrivateKey); - - bytes32 contractId = config.registry.createContract( - config.borgResolutionTemplate.id, - 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 eeb1705..f95e087 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -1,33 +1,29 @@ // 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 {YearnBorgCompensation2025_2026} from "./lib/YearnBorgCompensation2025_2026.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 {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"; -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 ProposeMetaVestDealScript is SafeTxHelper, Script { - using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; + using YearnBorgCompensation2025_2026 for YearnBorgCompensation2025_2026.Config; /// @dev For running from `forge script`. Provide the deployer private key through env var. function run() public virtual { - ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm); + YearnBorgCompensation2025_2026.Config memory defaultConfig = YearnBorgCompensation2025_2026.getDefault(vm); runSingle( vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey - vm.envOr("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY", uint256(0)), // guardianSafeDelegatePrivateKey - defaultConfig.guardians[0], + vm.envOr("BORG_DELEGATE_PRIVATE_KEY", uint256(0)), // borgSafeDelegatePrivateKey + defaultConfig.compRecipients[0], vm.envUint("AGREEMENT_SALT"), // agreementSalt defaultConfig ); @@ -36,15 +32,15 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running in tests function runSingle( uint256 proposerPrivateKey, - uint256 guardianSafeDelegatePrivateKey, - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 borgSafeDelegatePrivateKey, + YearnBorgCompensation2025_2026.CompInfo memory recipientInfo, uint256 agreementSalt, - ZkSyncGuardianCompensation2024_2025.Config memory config + YearnBorgCompensation2025_2026.Config memory config ) public virtual returns(bytes32) { return runSingle( proposerPrivateKey, - guardianSafeDelegatePrivateKey, - guardianInfo, + borgSafeDelegatePrivateKey, + recipientInfo, agreementSalt, // Default guardian allocations config.parseAllocation(), @@ -55,28 +51,27 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { /// @dev For running in tests function runSingle( uint256 proposerPrivateKey, - uint256 guardianSafeDelegatePrivateKey, - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 borgSafeDelegatePrivateKey, + YearnBorgCompensation2025_2026.CompInfo memory guardianInfo, uint256 agreementSalt, BaseAllocation.Allocation memory allocation, - ZkSyncGuardianCompensation2024_2025.Config memory config + YearnBorgCompensation2025_2026.Config memory config ) public virtual returns(bytes32) { - address guardianSafeDelegate = guardianSafeDelegatePrivateKey != 0 - ? vm.addr(guardianSafeDelegatePrivateKey) + address borgSafeDelegate = borgSafeDelegatePrivateKey != 0 + ? vm.addr(borgSafeDelegatePrivateKey) : 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("Guardian SAFE Delegate (if private key available): ", borgSafeDelegate); + console2.log("Guardian Safe: ", address(config.borgSafe)); + console2.log("Payment token: ", address(config.paymentToken)); 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 @@ -86,13 +81,13 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { uint48 startTime = config.metavestVestingAndUnlockStartTime; address[] memory parties = new address[](2); - parties[0] = address(config.guardianSafe); + parties[0] = address(config.borgSafe); parties[1] = guardianInfo.partyInfo.evmAddress; string[] memory globalValues = config.formatCompGlobalValues(vm, guardianInfo.partyInfo.evmAddress); - string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues( + string[][] memory partyValues = YearnBorgCompensation2025_2026.formatPartyValues( vm, - config.guardianSafeInfo, + config.borgSafeInfo, guardianInfo.partyInfo ); @@ -107,7 +102,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { (string memory agreementUri, ) = config.registry.templates(guardianInfo.compTemplate.id); - bytes memory signature = (guardianSafeDelegatePrivateKey != 0) + bytes memory signature = (borgSafeDelegatePrivateKey != 0) ? CyberAgreementUtils.signAgreementTypedData( config.registry, expectedContractId, @@ -116,7 +111,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { guardianInfo.compTemplate.partyFields, globalValues, partyValues[0], - guardianSafeDelegatePrivateKey + borgSafeDelegatePrivateKey ) : guardianInfo.signature; diff --git a/scripts/signDealAndCreateMetavest.s.sol b/scripts/signDealAndCreateMetavest.s.sol index 8b2a1c9..305737f 100644 --- a/scripts/signDealAndCreateMetavest.s.sol +++ b/scripts/signDealAndCreateMetavest.s.sol @@ -1,43 +1,39 @@ // 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 {YearnBorgCompensation2025_2026} from "./lib/YearnBorgCompensation2025_2026.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 {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 SignDealAndCreateMetavestScript is SafeTxHelper, Script { - using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; + using YearnBorgCompensation2025_2026 for YearnBorgCompensation2025_2026.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"); - ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(vm); + YearnBorgCompensation2025_2026.Config memory defaultConfig = YearnBorgCompensation2025_2026.getDefault(vm); run( // granteePrivateKey, // 0x0000000000000000000000000000000000000000000000000000000000000000, // TODO TBD -// ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD +// YearnBorgCompensation2024_2025.PartyInfo({ // TODO TBD // name: "Alice", // evmAddress: vm.addr(granteePrivateKey) // }), -// ZkSyncGuardianCompensation2024_2025.getDefault(vm) +// YearnBorgCompensation2024_2025.getDefault(vm) // zkSync Sepolia granteePrivateKey, 0xd0d7610ca18b8a76a36c7e1241929641c06cce69ddc1161beebe69b72dae6cbf, - defaultConfig.guardians[0], + defaultConfig.compRecipients[0], defaultConfig ); } @@ -46,8 +42,8 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { function run( uint256 granteePrivateKey, bytes32 agreementId, - ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory granteeInfo, - ZkSyncGuardianCompensation2024_2025.Config memory config + YearnBorgCompensation2025_2026.CompInfo memory granteeInfo, + YearnBorgCompensation2025_2026.Config memory config ) public virtual returns(address) { address signer = vm.addr(granteePrivateKey); @@ -57,7 +53,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { console2.log("Signer: ", signer); console2.log("Grantee: ", granteeInfo.partyInfo.evmAddress); console2.log("Grantee Name: ", granteeInfo.partyInfo.name); - console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("Guardian Safe: ", address(config.borgSafe)); console2.log("CyberAgreementRegistry: ", address(config.registry)); console2.log("MetavesTController: ", address(config.controller)); console2.log("Agreement ID:"); @@ -68,7 +64,7 @@ contract SignDealAndCreateMetavestScript is SafeTxHelper, Script { (string memory agreementUri, ) = config.registry.templates(granteeInfo.compTemplate.id); - string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, granteeInfo.partyInfo); + string[] memory granteePartyValues = YearnBorgCompensation2025_2026.formatPartyValues(vm, granteeInfo.partyInfo); bytes memory signature = CyberAgreementUtils.signAgreementTypedData( vm, config.registry.DOMAIN_SEPARATOR(), diff --git a/src/interfaces/zk-governance/IMintable.sol b/src/interfaces/zk-governance/IMintable.sol deleted file mode 100644 index a80461f..0000000 --- a/src/interfaces/zk-governance/IMintable.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -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 deleted file mode 100644 index 3e72b8c..0000000 --- a/src/interfaces/zk-governance/IMintableAndDelegatable.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {IMintable} from "./IMintable.sol"; - -interface IMintableAndDelegatable is IMintable { - function DOMAIN_SEPARATOR() external view returns (bytes32); - function delegateOnBehalf(address _signer, address _delegatee, uint256 _expiry, bytes calldata _signature) external; - function delegates(address _account) external view returns (address); -} diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2.sol b/src/interfaces/zk-governance/IZkCappedMinterV2.sol deleted file mode 100644 index 522fc8d..0000000 --- a/src/interfaces/zk-governance/IZkCappedMinterV2.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -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 EXPIRATION_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; - - function pause() external; - function unpause() external; - function paused() external view returns (bool); - - function close() external; - function closed() external view returns (bool); -} diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol b/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol deleted file mode 100644 index 901e958..0000000 --- a/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol +++ /dev/null @@ -1,13 +0,0 @@ -// 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/src/interfaces/zk-governance/IZkTokenV1.sol b/src/interfaces/zk-governance/IZkTokenV1.sol deleted file mode 100644 index 83c03ac..0000000 --- a/src/interfaces/zk-governance/IZkTokenV1.sol +++ /dev/null @@ -1,10 +0,0 @@ -// 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/YearnBorgCompensation.t.sol b/test/YearnBorgCompensation.t.sol new file mode 100644 index 0000000..94abd9c --- /dev/null +++ b/test/YearnBorgCompensation.t.sol @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import "../src/MetaVesTController.sol"; +import "../src/VestingAllocationFactory.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 {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; +import {DeployYearnBorgCompensationPrerequisitesScript} from "../scripts/deployYearnBorgCompensationPrerequisites.s.sol"; +import {DeployYearnBorgCompensationScript} from "../scripts/deployYearnBorgCompensation.s.sol"; +import {CreateAllTemplatesScript} from "../scripts/createAllTemplates.s.sol"; +import {ProposeAllGuardiansMetaVestDealScript} from "../scripts/proposeAllGuardiansMetavestDeals.s.sol"; +import {SignDealAndCreateMetavestScript} from "../scripts/signDealAndCreateMetavest.s.sol"; +import {YearnBorgCompensation2025_2026} from "../scripts/lib/YearnBorgCompensation2025_2026.sol"; +import {GnosisTransaction} from "./lib/safe.sol"; + +// Test with fresh deployment (except third-party dependencies) +// - Use third-party dependencies on Ethereum mainnet +// - Does not need to be run with environment variables +contract YearnBorgCompensationTest is + DeployYearnBorgCompensationPrerequisitesScript, + DeployYearnBorgCompensationScript, + CreateAllTemplatesScript, + ProposeAllGuardiansMetaVestDealScript, + SignDealAndCreateMetavestScript, + Test +{ + string saltStr = "YearnBorgCompensationTest"; + uint256 agreementSalt = block.timestamp; + + // Randomly generated to avoid contaminated common test address + uint256 privateKeySalt = 0x4425fdf88097e51c669a66d392c4019ad555544e966908af6ee3cec32f53ab77; + + uint256 deployerPrivateKey = privateKeySalt + 0; + address deployer = vm.addr(deployerPrivateKey); + uint256 metalexDelegatePrivateKey = privateKeySalt + 1; + address metalexDelegate = vm.addr(metalexDelegatePrivateKey); + uint256 borgDelegatePrivateKey = privateKeySalt + 2; + address borgDelegate = vm.addr(borgDelegatePrivateKey); + uint256 chadPrivateKey = privateKeySalt + 3; + address chad = vm.addr(chadPrivateKey); + uint256[] borgRecipientPrivateKeys; + + YearnBorgCompensation2025_2026.Config config2025_2026; + + BorgAuth auth; + + function setUp() virtual public { + // Prepare funds for accounts used by the actual deployment scripts + deal(deployer, 1 ether); + deal(metalexDelegate, 1 ether); + deal(borgDelegate, 1 ether); + deal(chad, 1 ether); + + CyberAgreementRegistry registry; + VestingAllocationFactory vestingAllocationFactory; + metavestController controller; + GnosisTransaction[] memory safeTxsCreateAllTemplates; + GnosisTransaction[] memory safeTxs2024_2025; + GnosisTransaction[] memory safeTxs2025_2026; + + config2025_2026 = YearnBorgCompensation2025_2026.getDefault(vm); + + // Override guardian info for tests + + borgRecipientPrivateKeys = new uint256[](1); + borgRecipientPrivateKeys[0] = privateKeySalt + 100; + config2025_2026.compRecipients = new YearnBorgCompensation2025_2026.CompInfo[](1); + config2025_2026.compRecipients[0] = YearnBorgCompensation2025_2026.CompInfo({ + partyInfo: YearnBorgCompensation2025_2026.PartyInfo({ + name: "Alice", + evmAddress: vm.addr(borgRecipientPrivateKeys[0]) + }), + compTemplate: YearnBorgCompensation2025_2026.TemplateInfo({ + id: bytes32(uint256(999001)), + agreementUri: "ipfs://bafkreidefnk2tf6req4tn3bya7pkfkt45i6cppmannb5fz7ncv6mfg6vj4", + name: "Alice template", + globalFields: YearnBorgCompensation2025_2026.getCompGlobalFields(), + partyFields: YearnBorgCompensation2025_2026.getCompPartyFields() + }), + signature: "" + }); + deal(config2025_2026.compRecipients[0].partyInfo.evmAddress, 1 ether); // Prepare gas for compRecipients + + // Deploy prerequisites + + (auth, registry, vestingAllocationFactory) = DeployYearnBorgCompensationPrerequisitesScript.deployPrerequisites( + deployerPrivateKey, + saltStr, + config2025_2026 + ); + + // Update configs with deployed contracts + config2025_2026.registry = registry; + config2025_2026.vestingAllocationFactory = vestingAllocationFactory; + + // Deploy 2025-2026 compensation contracts + (controller, safeTxs2025_2026) = DeployYearnBorgCompensationScript.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(config2025_2026.metalexSafe)); + (safeTxsCreateAllTemplates[i].to).call{value: safeTxsCreateAllTemplates[i].value}(safeTxsCreateAllTemplates[i].data); + } + + // Simulate BORG SAFE to execute txs as instructed + + for (uint256 i = 0; i < safeTxs2024_2025.length; i++) { + vm.prank(address(config2025_2026.borgSafe)); + (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.borgSafe)); + (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(config2025_2026.metalexSafe)); + + for (uint256 i = 0; i < config2025_2026.compRecipients.length; i ++) { + YearnBorgCompensation2025_2026.CompInfo memory compRecipient = config2025_2026.compRecipients[i]; + config2025_2026.registry.createTemplate( + compRecipient.compTemplate.id, + compRecipient.compTemplate.name, + compRecipient.compTemplate.agreementUri, + compRecipient.compTemplate.globalFields, + compRecipient.compTemplate.partyFields + ); + } + + vm.stopPrank(); + + // Simulate BORG SAFE to delegate signing to an EOA + vm.prank(address(config2025_2026.borgSafe)); + config2025_2026.registry.setDelegation(borgDelegate, block.timestamp + 60 days); // A bit longer to accommodate test cases + assertTrue(config2025_2026.registry.isValidDelegate(address(config2025_2026.borgSafe), borgDelegate), "delegate should be BORG SAFE's delegate"); + + // Simulate fund BORG with USDC + deal(config2025_2026.paymentToken, address(config2025_2026.borgSafe), 5000e6); + vm.prank(address(config2025_2026.borgSafe)); + ERC20(config2025_2026.paymentToken).approve(address(config2025_2026.controller), 5000e6); + } + + function run() public override( + DeployYearnBorgCompensationPrerequisitesScript, + DeployYearnBorgCompensationScript, + CreateAllTemplatesScript, + ProposeAllGuardiansMetaVestDealScript, + SignDealAndCreateMetavestScript + ) { + // No-op, we don't use this part of the scripts + } + + function test_metadata() public { + // MetaVesT pre-requisites + + auth.onlyRole(auth.OWNER_ROLE(), address(config2025_2026.metalexSafe)); // MetaLeX SAFE should own core auth + vm.assertEq(auth.userRoles(deployer), 0, "deployer should revoke core auth ownership"); + vm.assertEq(address(config2025_2026.registry.AUTH()), address(auth), "Unexpected CyberAgreementRegistry auth"); + + for (uint256 i = 0; i < config2025_2026.compRecipients.length; i ++) { + YearnBorgCompensation2025_2026.CompInfo memory guardian = config2025_2026.compRecipients[i]; + _assertTemplate( + config2025_2026.registry, + guardian.compTemplate.id, + guardian.compTemplate.agreementUri, + guardian.compTemplate.name, + guardian.compTemplate.globalFields, + guardian.compTemplate.partyFields + ); + } + + // MetaVesT deployments + + vm.assertEq(config2025_2026.controller.authority(), address(config2025_2026.borgSafe), "2025-2026 MetaVesTController's authority should be BORG SAFE"); + vm.assertEq(config2025_2026.controller.dao(), address(config2025_2026.borgSafe), "2025-2026 MetaVesTController's DAO should be BORG 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"); + } + + function test_AgreementDeadline() public { + // Run scripts to propose deals + bytes32 agreementId = ProposeAllGuardiansMetaVestDealScript.runSingle( + deployerPrivateKey, + borgDelegatePrivateKey, + config2025_2026.compRecipients[0], // alice + agreementSalt, + config2025_2026 + ); + + (, , , , , , uint256 agreementExpiry) = config2025_2026.registry.agreements(agreementId); + assertGt(agreementExpiry, config2025_2026.metavestVestingAndUnlockStartTime + 365 days, "Agreement expiry should be at least one year after vesting start"); + } + + function test_GuardianCompensation() public { + address[] memory metavestAddresses2025_2026 = _proposeAndFinalizeAllGuardianDeals(); + + VestingAllocation vestingAllocationAlice2025_2026 = VestingAllocation(metavestAddresses2025_2026[0]); + + // 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.paymentToken, vestingAllocationAlice2025_2026, 2500e3, "Alice 2025-2026 half"); + + // 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.paymentToken, vestingAllocationAlice2025_2026, 2500e3, "Alice 2025-2026 remaining"); + } + + function _proposeAndFinalizeAllGuardianDeals() internal returns(address[] memory) { + // Run scripts to propose deals for all compRecipients + + bytes32[] memory agreementIds2025_2026 = ProposeAllGuardiansMetaVestDealScript.runAll( + deployerPrivateKey, + borgDelegatePrivateKey, + agreementSalt, + config2025_2026 + ); + + // Simulate guardian counter-sign and finalize the deal + + address[] memory metavests2025_2026 = new address[](borgRecipientPrivateKeys.length); + + for (uint256 i = 0; i < metavests2025_2026.length; i++) { + metavests2025_2026[i] = SignDealAndCreateMetavestScript.run( + borgRecipientPrivateKeys[i], + agreementIds2025_2026[i], + config2025_2026.compRecipients[i], + config2025_2026 + ); + } + + return metavests2025_2026; + } + + 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(address paymentToken, VestingAllocation vestingAllocation, uint256 amount, string memory assertName) internal { + address grantee = vestingAllocation.grantee(); + uint256 balanceBefore = ERC20(paymentToken).balanceOf(grantee); + + vm.prank(grantee); + vestingAllocation.withdraw(amount); + + assertEq(ERC20(paymentToken).balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); + } +} diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol deleted file mode 100644 index c60728c..0000000 --- a/test/ZkSyncGuardianCompensation.t.sol +++ /dev/null @@ -1,427 +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/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 {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"; -import {ZkSyncGuardianCompensation2025_2026} from "../scripts/lib/ZkSyncGuardianCompensation2025_2026.sol"; -import {GnosisTransaction} from "./lib/safe.sol"; - -// 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, - ProposeAllGuardiansMetaVestDealScript, - SignDealAndCreateMetavestScript, - Test -{ - // zkSync Era mainnet @ 63631890 - address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; - - string saltStr = "ZkSyncGuardianCompensationTest"; - uint256 agreementSalt = block.timestamp; - - // Randomly generated to avoid contaminated common test address - uint256 privateKeySalt = 0x4425fdf88097e51c669a66d392c4019ad555544e966908af6ee3cec32f53ab77; - - uint256 deployerPrivateKey = privateKeySalt + 0; - address deployer = vm.addr(deployerPrivateKey); - uint256 metalexDelegatePrivateKey = privateKeySalt + 1; - address metalexDelegate = vm.addr(metalexDelegatePrivateKey); - uint256 guardianDelegatePrivateKey = privateKeySalt + 2; - address guardianDelegate = vm.addr(guardianDelegatePrivateKey); - uint256 chadPrivateKey = privateKeySalt + 3; - address chad = vm.addr(chadPrivateKey); - uint256[] guardianPrivateKeys; - - IZkCappedMinterV2 masterMinter; - - ZkSyncGuardianCompensation2024_2025.Config config2024_2025; - ZkSyncGuardianCompensation2024_2025.Config config2025_2026; - - BorgAuth auth; - metavestController controller2024_2025; - metavestController controller2025_2026; - - 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(chad, 1 ether); - - CyberAgreementRegistry registry; - VestingAllocationFactory vestingAllocationFactory; - metavestController controller; - GnosisTransaction[] memory safeTxsCreateAllTemplates; - GnosisTransaction[] memory safeTxs2024_2025; - GnosisTransaction[] memory safeTxs2025_2026; - - config2024_2025 = ZkSyncGuardianCompensation2024_2025.getDefault(vm); - config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(vm); - - // Override guardian info for tests - - 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: vm.addr(guardianPrivateKeys[0]) - }), - compTemplate: ZkSyncGuardianCompensation2024_2025.TemplateInfo({ - id: bytes32(uint256(999001)), - agreementUri: "ipfs://bafkreidefnk2tf6req4tn3bya7pkfkt45i6cppmannb5fz7ncv6mfg6vj4", - name: "Alice template", - globalFields: ZkSyncGuardianCompensation2024_2025.getCompGlobalFields(), - partyFields: ZkSyncGuardianCompensation2024_2025.getCompPartyFields() - }), - signature: "" - }); - 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, - 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 - - 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 MetaLeX SAFE create templates for Guardian Compensation and Service Agreement - - 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()); - - // No longer needed 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(); - - // 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( - DeployZkSyncGuardianCompensationPrerequisitesScript, - DeployZkSyncGuardianCompensationScript, - CreateAllTemplatesScript, -// ProposeBorgResolutionScript, - ProposeAllGuardiansMetaVestDealScript, - SignDealAndCreateMetavestScript - ) { - // No-op, we don't use this part of the scripts - } - - function test_metadata() public { - // ZK governance pre-requisites - - 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(config2024_2025.metalexSafe)); // MetaLeX SAFE should own BorgAuth - vm.assertEq(auth.userRoles(deployer), 0, "deployer should revoke BorgAuth ownership"); - vm.assertEq(address(config2024_2025.registry.AUTH()), address(auth), "Unexpected CyberAgreementRegistry auth"); - - // 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( - config2024_2025.registry, - guardian.compTemplate.id, - guardian.compTemplate.agreementUri, - guardian.compTemplate.name, - guardian.compTemplate.globalFields, - guardian.compTemplate.partyFields - ); - } - - // MetaVesT deployments - - 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 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 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_AgreementDeadline() public { - // Run scripts to propose deals - bytes32 agreementId = ProposeAllGuardiansMetaVestDealScript.runSingle( - deployerPrivateKey, - guardianDelegatePrivateKey, - config2024_2025.guardians[0], // alice - agreementSalt, - 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"); - } - - // TODO deprecated -// function test_ProposeBorgResolution() public { -// // 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[] memory metavestAddresses2024_2025, address[] memory metavestAddresses2025_2026) = _proposeAndFinalizeAllGuardianDeals(); - - 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 - _granteeWithdrawAndAsserts(config2024_2025.zkToken, config2024_2025.zkCappedMinter, vestingAllocationAlice2024_2025, 615e3 ether, "Alice 2024-2025 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"); - - // 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"); - - // 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() virtual public { - (address[] memory metavestAddresses2024_2025, ) = _proposeAndFinalizeAllGuardianDeals(); - VestingAllocation vestingAllocationAlice = VestingAllocation(metavestAddresses2024_2025[0]); - - // 2024-2025 Vesting starts - vm.warp(1756684800); // 2025/09/01 00:00 UTC - - _granteeWithdrawAndAsserts(config2024_2025.zkToken, config2024_2025.zkCappedMinter, vestingAllocationAlice, 300e3 ether, "Alice partial"); - - // A month has passed - skip(30 days); - - // Add new grantee for admin/tooling compensation - - 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({ - name: "Chad", - evmAddress: chad - }), - signature: "" // No offline signature needed since we will sign with Chad's private key - }); - bytes32 contractIdChad = ProposeAllGuardiansMetaVestDealScript.runSingle( - deployerPrivateKey, - guardianDelegatePrivateKey, - chadInfo, - agreementSalt, - BaseAllocation.Allocation({ - tokenContract: address(config2024_2025.zkToken), - // 10k ZK total in one cliff - tokenStreamTotal: 10e3 ether, - vestingCliffCredit: 10e3 ether, - unlockingCliffCredit: 10e3 ether, - vestingRate: 0, - vestingStartTime: 0, - unlockRate: 0, - unlockStartTime: 0 - }), - config2024_2025 - ); - VestingAllocation vestingAllocationChad = VestingAllocation(SignDealAndCreateMetavestScript.run( - chadPrivateKey, - contractIdChad, - chadInfo, - config2024_2025 - )); - _granteeWithdrawAndAsserts(config2024_2025.zkToken, config2024_2025.zkCappedMinter, vestingAllocationChad, 10e3 ether, "Chad cliff"); - } - - function _proposeAndFinalizeAllGuardianDeals() internal returns(address[] memory, address[] memory) { - // Run scripts to propose deals for all guardians - - bytes32[] memory agreementIds2024_2025 = ProposeAllGuardiansMetaVestDealScript.runAll( - deployerPrivateKey, - guardianDelegatePrivateKey, - agreementSalt, - config2024_2025 - ); - bytes32[] memory agreementIds2025_2026 = ProposeAllGuardiansMetaVestDealScript.runAll( - deployerPrivateKey, - guardianDelegatePrivateKey, - agreementSalt, - config2025_2026 - ); - - // Simulate guardian counter-sign and finalize the deal - - 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 (metavests2024_2025, metavests2025_2026); - } - - 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)"))); - } -} diff --git a/test/ZkSyncGuardianCompensationAcceptance.t.sol b/test/ZkSyncGuardianCompensationAcceptance.t.sol deleted file mode 100644 index f006f53..0000000 --- a/test/ZkSyncGuardianCompensationAcceptance.t.sol +++ /dev/null @@ -1,133 +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/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); - - // Use real Guardians SAFE delegate. We don't have his private key and will use offline signatures instead - guardianDelegate = 0xa376AaF645dbd9b4f501B2A8a97bc21DcA15B001; - guardianDelegatePrivateKey = 0; - - // Prepare funds for accounts used by the actual deployment scripts - deal(deployer, 1 ether); - deal(chad, 1 ether); - - GnosisTransaction[] memory guardianSafeTxs; - - config2024_2025 = ZkSyncGuardianCompensation2024_2025.getDefault(vm); - config2025_2026 = ZkSyncGuardianCompensation2025_2026.getDefault(vm); - - // Override guardian info for tests - - // 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); - - 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"; - - // 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 Guardian SAFE to execute txs as instructed (payloads are copied directly from a recent production deployment) - - guardianSafeTxs = new GnosisTransaction[](5); - guardianSafeTxs[0] = GnosisTransaction({ - to: 0x07E0a0BeC742f90f7879830bC917E783dA6a6357, - value: 0, - data: hex"e988dc91000000000000000000000000a376aaf645dbd9b4f501b2a8a97bc21dca15b0010000000000000000000000000000000000000000000000000000000068db1d80" - }); - guardianSafeTxs[1] = GnosisTransaction({ - to: 0xE555FC98E45637D1B45e60E4fc05cF0F22836156, - value: 0, - data: hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000d509349af986e7202f2bc4ae49c203e354faafcd" - }); - guardianSafeTxs[2] = GnosisTransaction({ - to: 0xD509349AF986E7202f2Bc4ae49C203E354faafCD, - value: 0, - data: hex"66e26184000000000000000000000000e555fc98e45637d1b45e60e4fc05cf0f22836156" - }); - guardianSafeTxs[3] = GnosisTransaction({ - to: 0x1358F460bD147C4a6BfDaB75aD2B78C837a11D4A, - value: 0, - data: hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000570d31f59bd0c96a9e1cc889e7e4dbd585d6915b" - }); - guardianSafeTxs[4] = GnosisTransaction({ - to: 0x570d31F59bD0C96a9e1CC889E7E4dBd585D6915b, - value: 0, - data: hex"66e261840000000000000000000000001358f460bd147c4a6bfdab75ad2b78c837a11d4a" - }); - - for (uint256 i = 0; i < guardianSafeTxs.length; i++) { - vm.prank(address(config2024_2025.guardianSafe)); - (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()); - } - - function test_AdminToolingCompensation() override public { - // No-op: disabled since we won't have signatures for it - } -} From 85b577bde1937ddde0d89320f7352a8b26464730 Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 13 Oct 2025 10:30:22 -0700 Subject: [PATCH 08/14] chore: update scripts and configs --- scripts/createAllTemplates.s.sol | 22 +++----- scripts/deployYearnBorgCompensation.s.sol | 43 +++++++-------- ...oyYearnBorgCompensationPrerequisites.s.sol | 24 +-------- .../lib/YearnBorgCompensation2025_2026.sol | 8 ++- test/YearnBorgCompensation.t.sol | 53 ++++++------------- 5 files changed, 53 insertions(+), 97 deletions(-) diff --git a/scripts/createAllTemplates.s.sol b/scripts/createAllTemplates.s.sol index 0db7f72..8fa7902 100644 --- a/scripts/createAllTemplates.s.sol +++ b/scripts/createAllTemplates.s.sol @@ -23,27 +23,19 @@ contract CreateAllTemplatesScript is SafeTxHelper, Script { function run( YearnBorgCompensation2025_2026.Config memory config ) public virtual returns(GnosisTransaction[] memory) { - IGnosisSafe safe; - YearnBorgCompensation2025_2026.Config memory config; - - // zkSync Era (zkSync Guardians) - config = YearnBorgCompensation2025_2026.getDefault(vm); - - safe = config.borgSafe; - GnosisTransaction[] memory safeTxs = new GnosisTransaction[](config.compRecipients.length); for (uint i = 0; i < config.compRecipients.length ; i++) { - YearnBorgCompensation2025_2026.CompInfo memory guardian = config.compRecipients[i]; + YearnBorgCompensation2025_2026.CompInfo memory compRecipient = config.compRecipients[i]; safeTxs[i] = 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 + compRecipient.compTemplate.id, + compRecipient.compTemplate.name, + compRecipient.compTemplate.agreementUri, + compRecipient.compTemplate.globalFields, + compRecipient.compTemplate.partyFields ) }); } @@ -52,7 +44,7 @@ contract CreateAllTemplatesScript is SafeTxHelper, Script { console2.log(""); console2.log("=== CreateAllTemplatesScript ==="); - console2.log("Safe: ", address(safe)); + console2.log("Safe: ", address(config.metalexSafe)); console2.log("Safe TXs:"); for (uint256 i = 0 ; i < safeTxs.length ; i++) { console2.log(" #", i); diff --git a/scripts/deployYearnBorgCompensation.s.sol b/scripts/deployYearnBorgCompensation.s.sol index ec86e7a..3c0fbb3 100644 --- a/scripts/deployYearnBorgCompensation.s.sol +++ b/scripts/deployYearnBorgCompensation.s.sol @@ -6,6 +6,7 @@ import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/saf 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 {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; import {Script} from "forge-std/Script.sol"; import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; @@ -63,28 +64,28 @@ contract DeployYearnBorgCompensationScript is SafeTxHelper, Script { vm.stopBroadcast(); - // TODO WIP: re-purpose to provision USDC funds - // Prepare Guardian SAFE txs to: - // 1. Grant MetaVesT Controller MINTER ROLE - // 2. Set MetaVesT Controller's ZK Capped Minter + // Prepare BORG SAFE txs to: + // 1. Approve paymentToken spending from metavestController + // 2. Delegate agreement signing to an EOA GnosisTransaction[] memory safeTxs = new GnosisTransaction[](2); -// safeTxs[0] = GnosisTransaction({ -// to: address(config.zkCappedMinter), -// value: 0, -// data: abi.encodeWithSelector( -// IZkCappedMinterV2.grantRole.selector, -// config.zkCappedMinter.MINTER_ROLE(), -// address(controller) -// ) -// }); -// safeTxs[1] = GnosisTransaction({ -// to: address(controller), -// value: 0, -// data: abi.encodeWithSelector( -// controller.setZkCappedMinter.selector, -// address(config.zkCappedMinter) -// ) -// }); + safeTxs[0] = GnosisTransaction({ + to: address(config.paymentToken), + value: 0, + data: abi.encodeWithSelector( + ERC20.approve.selector, + address(controller), + config.paymentTokenApprovalCap + ) + }); + safeTxs[1] = GnosisTransaction({ + to: address(config.registry), + value: 0, + data: abi.encodeWithSelector( + CyberAgreementRegistry.setDelegation.selector, + config.borgAgreementDelegate, + block.timestamp + 14 days + ) + }); // Output logs diff --git a/scripts/deployYearnBorgCompensationPrerequisites.s.sol b/scripts/deployYearnBorgCompensationPrerequisites.s.sol index f4b153b..973c187 100644 --- a/scripts/deployYearnBorgCompensationPrerequisites.s.sol +++ b/scripts/deployYearnBorgCompensationPrerequisites.s.sol @@ -32,8 +32,6 @@ contract DeployYearnBorgCompensationPrerequisitesScript is SafeTxHelper, Script string memory saltStr, YearnBorgCompensation2025_2026.Config memory config ) public virtual returns( - BorgAuth, - CyberAgreementRegistry, VestingAllocationFactory ) { address deployer = vm.addr(deployerPrivateKey); @@ -42,30 +40,12 @@ contract DeployYearnBorgCompensationPrerequisitesScript is SafeTxHelper, Script console2.log("=== DeployYearnBorgCompensationPrerequisitesScript ==="); console2.log("Deployer: ", deployer); console2.log("Salt string: ", saltStr); - console2.log("MetaLeX SAFE: ", address(config.metalexSafe)); console2.log(""); bytes32 salt = keccak256(bytes(saltStr)); 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) - ) - ))); - - // Transfer CyberAgreementRegistry ownership to MetaLeX SAFE - - auth.updateRole(address(config.metalexSafe), auth.OWNER_ROLE()); - auth.zeroOwner(); - // Deploy MetaVesT pre-requisites VestingAllocationFactory vestingAllocationFactory = new VestingAllocationFactory{salt: salt}(); @@ -75,11 +55,9 @@ contract DeployYearnBorgCompensationPrerequisitesScript is SafeTxHelper, Script // Output logs 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); + return vestingAllocationFactory; } } diff --git a/scripts/lib/YearnBorgCompensation2025_2026.sol b/scripts/lib/YearnBorgCompensation2025_2026.sol index 459793e..eaf4327 100644 --- a/scripts/lib/YearnBorgCompensation2025_2026.sol +++ b/scripts/lib/YearnBorgCompensation2025_2026.sol @@ -15,12 +15,13 @@ library YearnBorgCompensation2025_2026 { // External dependencies - address paymentToken; + address paymentToken; // USDC // Yearn BORG IGnosisSafe borgSafe; PartyInfo borgSafeInfo; + address borgAgreementDelegate; // Delegate EOA for signing agreement on BORG's behalf // MetaLeX @@ -32,7 +33,8 @@ library YearnBorgCompensation2025_2026 { // Yearn BORG Director Compensation Agreement (one template per director for now) CompInfo[] compRecipients; - uint256 fixedAnnualCompensation; + uint256 paymentTokenApprovalCap; // Maximum `paymentToken` allowance borgSafe should approve metavestController to spend + uint256 fixedAnnualCompensation; // Expected annual compensation (in `paymentToken`) per recipient uint48 metavestVestingAndUnlockStartTime; BaseAllocation.Milestone[] milestones; } @@ -73,6 +75,7 @@ library YearnBorgCompensation2025_2026 { name: "Yearn BORG", evmAddress: address(borgSafe) }), + borgAgreementDelegate: 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF, // TODO TBD // MetaLeX @@ -84,6 +87,7 @@ library YearnBorgCompensation2025_2026 { // Yearn BORG Compensation Agreement compRecipients: loadGuardianAndComps(vm), + paymentTokenApprovalCap: 5000e6, // 5000 USDC * 1 recipient fixedAnnualCompensation: 5000e6, // 5000 USDC metavestVestingAndUnlockStartTime: 1756684800, // TODO TBD: for now it is 2025/09/01 00:00 UTC milestones: new BaseAllocation.Milestone[](0) diff --git a/test/YearnBorgCompensation.t.sol b/test/YearnBorgCompensation.t.sol index 94abd9c..690476d 100644 --- a/test/YearnBorgCompensation.t.sol +++ b/test/YearnBorgCompensation.t.sol @@ -54,16 +54,14 @@ contract YearnBorgCompensationTest is deal(borgDelegate, 1 ether); deal(chad, 1 ether); - CyberAgreementRegistry registry; VestingAllocationFactory vestingAllocationFactory; metavestController controller; GnosisTransaction[] memory safeTxsCreateAllTemplates; - GnosisTransaction[] memory safeTxs2024_2025; GnosisTransaction[] memory safeTxs2025_2026; config2025_2026 = YearnBorgCompensation2025_2026.getDefault(vm); - // Override guardian info for tests + // Override recipient info for tests borgRecipientPrivateKeys = new uint256[](1); borgRecipientPrivateKeys[0] = privateKeySalt + 100; @@ -84,18 +82,22 @@ contract YearnBorgCompensationTest is }); deal(config2025_2026.compRecipients[0].partyInfo.evmAddress, 1 ether); // Prepare gas for compRecipients - // Deploy prerequisites + // Update known info + auth = config2025_2026.registry.AUTH(); - (auth, registry, vestingAllocationFactory) = DeployYearnBorgCompensationPrerequisitesScript.deployPrerequisites( + // Deploy prerequisites + vestingAllocationFactory = DeployYearnBorgCompensationPrerequisitesScript.deployPrerequisites( deployerPrivateKey, saltStr, config2025_2026 ); // Update configs with deployed contracts - config2025_2026.registry = registry; config2025_2026.vestingAllocationFactory = vestingAllocationFactory; + // Update configs with test BORG delegate + config2025_2026.borgAgreementDelegate = borgDelegate; + // Deploy 2025-2026 compensation contracts (controller, safeTxs2025_2026) = DeployYearnBorgCompensationScript.deployCompensation( deployerPrivateKey, @@ -114,42 +116,13 @@ contract YearnBorgCompensationTest is } // Simulate BORG SAFE to execute txs as instructed - - for (uint256 i = 0; i < safeTxs2024_2025.length; i++) { - vm.prank(address(config2025_2026.borgSafe)); - (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.borgSafe)); (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(config2025_2026.metalexSafe)); - - for (uint256 i = 0; i < config2025_2026.compRecipients.length; i ++) { - YearnBorgCompensation2025_2026.CompInfo memory compRecipient = config2025_2026.compRecipients[i]; - config2025_2026.registry.createTemplate( - compRecipient.compTemplate.id, - compRecipient.compTemplate.name, - compRecipient.compTemplate.agreementUri, - compRecipient.compTemplate.globalFields, - compRecipient.compTemplate.partyFields - ); - } - - vm.stopPrank(); - - // Simulate BORG SAFE to delegate signing to an EOA - vm.prank(address(config2025_2026.borgSafe)); - config2025_2026.registry.setDelegation(borgDelegate, block.timestamp + 60 days); // A bit longer to accommodate test cases - assertTrue(config2025_2026.registry.isValidDelegate(address(config2025_2026.borgSafe), borgDelegate), "delegate should be BORG SAFE's delegate"); - - // Simulate fund BORG with USDC + // Simulate BORG SAFE to prepare USDC deal(config2025_2026.paymentToken, address(config2025_2026.borgSafe), 5000e6); - vm.prank(address(config2025_2026.borgSafe)); - ERC20(config2025_2026.paymentToken).approve(address(config2025_2026.controller), 5000e6); } function run() public override( @@ -187,6 +160,14 @@ contract YearnBorgCompensationTest is vm.assertEq(config2025_2026.controller.dao(), address(config2025_2026.borgSafe), "2025-2026 MetaVesTController's DAO should be BORG 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"); + + // BORG provisioning + assertTrue(config2025_2026.registry.isValidDelegate(address(config2025_2026.borgSafe), borgDelegate), "delegate should be BORG SAFE's delegate"); + assertEq( + ERC20(config2025_2026.paymentToken).allowance(address(config2025_2026.borgSafe), address(config2025_2026.controller)), + config2025_2026.paymentTokenApprovalCap, + "BORG should approve metavestController to transfer USDC" + ); } function test_AgreementDeadline() public { From 5f1a729d6d5801c919ddd8500e75943b8ff3e977 Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 13 Oct 2025 10:58:18 -0700 Subject: [PATCH 09/14] chore: add deploy configs for testnet --- .../YearnBorgCompensationSepolia2025_2026.sol | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 scripts/lib/YearnBorgCompensationSepolia2025_2026.sol diff --git a/scripts/lib/YearnBorgCompensationSepolia2025_2026.sol b/scripts/lib/YearnBorgCompensationSepolia2025_2026.sol new file mode 100644 index 0000000..9512f85 --- /dev/null +++ b/scripts/lib/YearnBorgCompensationSepolia2025_2026.sol @@ -0,0 +1,50 @@ +// 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 {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.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"; +import {YearnBorgCompensation2025_2026} from "./YearnBorgCompensation2025_2026.sol"; + +library YearnBorgCompensationSepolia2025_2026 { + + function getDefault(Vm vm) internal view returns(YearnBorgCompensation2025_2026.Config memory) { + IGnosisSafe borgSafe = IGnosisSafe(0x4F22ba82a6B71F7305d1be7Ae7323811f9D555Ab); // dev safe + IGnosisSafe metalexSafe = IGnosisSafe(0x4F22ba82a6B71F7305d1be7Ae7323811f9D555Ab); // dev safe + + return YearnBorgCompensation2025_2026.Config({ + + // External dependencies + + paymentToken: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238, // testnet USDC by Circle + + // Yearn BORG + + borgSafe: borgSafe, + borgSafeInfo: YearnBorgCompensation2025_2026.PartyInfo({ + name: "Yearn BORG Test", + evmAddress: address(borgSafe) + }), + borgAgreementDelegate: 0x5ff4e90Efa2B88cf3cA92D63d244a78a88219Abf, + + // MetaLeX + + metalexSafe: metalexSafe, + registry: CyberAgreementRegistry(0xa9E808B8eCBB60Bb19abF026B5b863215BC4c134), + vestingAllocationFactory: VestingAllocationFactory(0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF), // TODO TBD + controller: metavestController(0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF), // TODO TBD + + // Yearn BORG Compensation Agreement + + compRecipients: YearnBorgCompensation2025_2026.loadGuardianAndComps(vm), + paymentTokenApprovalCap: 5000e6, // 5000 USDC * 1 recipient + fixedAnnualCompensation: 5000e6, // 5000 USDC + metavestVestingAndUnlockStartTime: 1756684800, // 2025/09/01 00:00 UTC + milestones: new BaseAllocation.Milestone[](0) + }); + } +} From d67aff00e3ad65ad50fc6fe834da14d9b06dbb47 Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 13 Oct 2025 12:06:13 -0700 Subject: [PATCH 10/14] chore: deploy to testnet. Add acceptance tests --- scripts/createAllTemplates.s.sol | 10 +- scripts/deployYearnBorgCompensation.s.sol | 11 ++- ...oyYearnBorgCompensationPrerequisites.s.sol | 11 ++- .../YearnBorgCompensationSepolia2025_2026.sol | 6 +- .../proposeAllGuardiansMetavestDeals.s.sol | 11 ++- scripts/proposeMetavestDeal.s.sol | 2 +- test/YearnBorgCompensation.t.sol | 8 +- test/YearnBorgCompensationAcceptance.t.sol | 92 +++++++++++++++++++ 8 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 test/YearnBorgCompensationAcceptance.t.sol diff --git a/scripts/createAllTemplates.s.sol b/scripts/createAllTemplates.s.sol index 8fa7902..74df36d 100644 --- a/scripts/createAllTemplates.s.sol +++ b/scripts/createAllTemplates.s.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import {YearnBorgCompensation2025_2026} from "./lib/YearnBorgCompensation2025_2026.sol"; +import {YearnBorgCompensationSepolia2025_2026} from "./lib/YearnBorgCompensationSepolia2025_2026.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,8 +16,13 @@ import {metavestController} from "../src/MetaVesTController.sol"; contract CreateAllTemplatesScript is SafeTxHelper, Script { /// @dev For running from `forge script` function run() public virtual { - // zkSync mainnet - run(YearnBorgCompensation2025_2026.getDefault(vm)); + run( +// // Ethereum mainnet +// YearnBorgCompensation2025_2026.getDefault(vm) + + // Sepolia testnet + YearnBorgCompensationSepolia2025_2026.getDefault(vm) + ); } /// @dev For running in tests diff --git a/scripts/deployYearnBorgCompensation.s.sol b/scripts/deployYearnBorgCompensation.s.sol index 3c0fbb3..7935855 100644 --- a/scripts/deployYearnBorgCompensation.s.sol +++ b/scripts/deployYearnBorgCompensation.s.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import {YearnBorgCompensation2025_2026} from "./lib/YearnBorgCompensation2025_2026.sol"; +import {YearnBorgCompensationSepolia2025_2026} from "./lib/YearnBorgCompensationSepolia2025_2026.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"; @@ -19,9 +20,13 @@ contract DeployYearnBorgCompensationScript is SafeTxHelper, Script { deployCompensation( vm.envUint("DEPLOYER_PRIVATE_KEY"), - // Ethereum mainnet for 2025-2026 - "MetaLexMetaVestYearnBorgCompensationLaunchV1.0.2025-2026", - YearnBorgCompensation2025_2026.getDefault(vm) +// // Ethereum mainnet for 2025-2026 +// "MetaLexMetaVestYearnBorgCompensationLaunchV1.0.2025-2026", +// YearnBorgCompensation2025_2026.getDefault(vm) + + // Sepolia testnet + "MetaLexMetaVestYearnBorgCompensationLaunch-testnet-V0.1", + YearnBorgCompensationSepolia2025_2026.getDefault(vm) ); } diff --git a/scripts/deployYearnBorgCompensationPrerequisites.s.sol b/scripts/deployYearnBorgCompensationPrerequisites.s.sol index 973c187..73e6b1f 100644 --- a/scripts/deployYearnBorgCompensationPrerequisites.s.sol +++ b/scripts/deployYearnBorgCompensationPrerequisites.s.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import {YearnBorgCompensation2025_2026} from "./lib/YearnBorgCompensation2025_2026.sol"; +import {YearnBorgCompensationSepolia2025_2026} from "./lib/YearnBorgCompensationSepolia2025_2026.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"; @@ -20,9 +21,13 @@ contract DeployYearnBorgCompensationPrerequisitesScript is SafeTxHelper, Script deployPrerequisites( vm.envUint("DEPLOYER_PRIVATE_KEY"), - // Ethereum mainnet - "MetaLexMetaVestYearnBorgCompensationLaunchV1.0", - YearnBorgCompensation2025_2026.getDefault(vm) +// // Ethereum mainnet +// "MetaLexMetaVestYearnBorgCompensationLaunchV1.0", +// YearnBorgCompensation2025_2026.getDefault(vm) + + // Sepolia testnet + "MetaLexMetaVestYearnBorgCompensationLaunch-testnet-V0.1", + YearnBorgCompensationSepolia2025_2026.getDefault(vm) ); } diff --git a/scripts/lib/YearnBorgCompensationSepolia2025_2026.sol b/scripts/lib/YearnBorgCompensationSepolia2025_2026.sol index 9512f85..b8de1d6 100644 --- a/scripts/lib/YearnBorgCompensationSepolia2025_2026.sol +++ b/scripts/lib/YearnBorgCompensationSepolia2025_2026.sol @@ -20,7 +20,7 @@ library YearnBorgCompensationSepolia2025_2026 { // External dependencies - paymentToken: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238, // testnet USDC by Circle + paymentToken: 0xF450eF4F268eaF2d3D8F9eD0354852E255A5EAEF, // mintable test USDC // Yearn BORG @@ -35,8 +35,8 @@ library YearnBorgCompensationSepolia2025_2026 { metalexSafe: metalexSafe, registry: CyberAgreementRegistry(0xa9E808B8eCBB60Bb19abF026B5b863215BC4c134), - vestingAllocationFactory: VestingAllocationFactory(0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF), // TODO TBD - controller: metavestController(0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF), // TODO TBD + vestingAllocationFactory: VestingAllocationFactory(0x87dC5e3FBFE8B5F2B74C64eE34da8bdc9fedCb0f), + controller: metavestController(0xFa5Ab18bD5E02B1d6430e91C32C5CB5e7F43bB65), // Yearn BORG Compensation Agreement diff --git a/scripts/proposeAllGuardiansMetavestDeals.s.sol b/scripts/proposeAllGuardiansMetavestDeals.s.sol index 7415123..21433c1 100644 --- a/scripts/proposeAllGuardiansMetavestDeals.s.sol +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -13,6 +13,7 @@ import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; import {Script} from "forge-std/Script.sol"; import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; import {YearnBorgCompensation2025_2026} from "./lib/YearnBorgCompensation2025_2026.sol"; +import {YearnBorgCompensationSepolia2025_2026} from "./lib/YearnBorgCompensationSepolia2025_2026.sol"; import {console2} from "forge-std/console2.sol"; import {metavestController} from "../src/MetaVesTController.sol"; @@ -21,10 +22,16 @@ contract ProposeAllGuardiansMetaVestDealScript is ProposeMetaVestDealScript { function run() public virtual override { runAll( // Ethereum mainnet for 2025-2026 +// vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey +// uint256(0), // delegate will sign offline +// uint256(keccak256("MetaLexMetaVestYearnBorgCompensationLaunchV1.0.2025-2026")), // agreementSalt +// YearnBorgCompensation2025_2026.getDefault(vm) + + // Sepolia testnet for 2025-2026 vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey uint256(0), // delegate will sign offline - uint256(keccak256("MetaLexMetaVestYearnBorgCompensationLaunchV1.0.2025-2026")), // agreementSalt - YearnBorgCompensation2025_2026.getDefault(vm) + uint256(keccak256("MetaLexMetaVestYearnBorgCompensationLaunch-testnet-V0.1")), // agreementSalt + YearnBorgCompensationSepolia2025_2026.getDefault(vm) ); } diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index f95e087..6e32995 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -159,7 +159,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { )); console2.log("==== JSON data end ===="); - return bytes32(0); + return expectedContractId; } } } diff --git a/test/YearnBorgCompensation.t.sol b/test/YearnBorgCompensation.t.sol index 690476d..3893825 100644 --- a/test/YearnBorgCompensation.t.sol +++ b/test/YearnBorgCompensation.t.sol @@ -190,12 +190,12 @@ contract YearnBorgCompensationTest is VestingAllocation vestingAllocationAlice2025_2026 = VestingAllocation(metavestAddresses2025_2026[0]); // 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.paymentToken, vestingAllocationAlice2025_2026, 2500e3, "Alice 2025-2026 half"); + vm.warp(1772496000 + 1 days); // 2026/03/03 00:00 UTC + margin for precision errors + _granteeWithdrawAndAsserts(config2025_2026.paymentToken, vestingAllocationAlice2025_2026, 2500e6, "Alice 2025-2026 half"); // 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.paymentToken, vestingAllocationAlice2025_2026, 2500e3, "Alice 2025-2026 remaining"); + vm.warp(1793491199 + 1 days); // 2026/10/31 23:59:59 UTC + margin for precision errors + _granteeWithdrawAndAsserts(config2025_2026.paymentToken, vestingAllocationAlice2025_2026, 2500e6, "Alice 2025-2026 remaining"); } function _proposeAndFinalizeAllGuardianDeals() internal returns(address[] memory) { diff --git a/test/YearnBorgCompensationAcceptance.t.sol b/test/YearnBorgCompensationAcceptance.t.sol new file mode 100644 index 0000000..aaaa2ca --- /dev/null +++ b/test/YearnBorgCompensationAcceptance.t.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import {console2} from "forge-std/console2.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {YearnBorgCompensationTest} from "./YearnBorgCompensation.t.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {MetaVesTControllerTestBase} from "./lib/MetaVesTControllerTestBase.sol"; +import {GnosisTransaction} from "./lib/safe.sol"; +import {CreateAllTemplatesScript} from "../scripts/createAllTemplates.s.sol"; +import {DeployYearnBorgCompensationPrerequisitesScript} from "../scripts/deployYearnBorgCompensationPrerequisites.s.sol"; +import {DeployYearnBorgCompensationScript} from "../scripts/deployYearnBorgCompensation.s.sol"; +import {ProposeAllGuardiansMetaVestDealScript} from "../scripts/proposeAllGuardiansMetavestDeals.s.sol"; +import {ProposeMetaVestDealScript} from "../scripts/proposeMetavestDeal.s.sol"; +import {SignDealAndCreateMetavestScript} from "../scripts/signDealAndCreateMetavest.s.sol"; +import {YearnBorgCompensation2025_2026} from "../scripts/lib/YearnBorgCompensation2025_2026.sol"; +import {YearnBorgCompensationSepolia2025_2026} from "../scripts/lib/YearnBorgCompensationSepolia2025_2026.sol"; + +// Test with existing deployment +// - Assume existing deployment on Sepolia testnet +// - 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 YearnBorgCompensationAcceptanceTest is YearnBorgCompensationTest { + + function setUp() override public { + agreementSalt = 1760138399; // 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); + + // Prepare funds for accounts used by the actual deployment scripts + deal(deployer, 1 ether); + deal(chad, 1 ether); + + GnosisTransaction[] memory borgSafeTxs; + + config2025_2026 = YearnBorgCompensationSepolia2025_2026.getDefault(vm); + + // Use real Guardians SAFE delegate. We don't have his private key and will use offline signatures instead + borgDelegate = config2025_2026.borgAgreementDelegate; + borgDelegatePrivateKey = 0; + + // Override recipient info for tests + + // There will be only one recipient for test + borgRecipientPrivateKeys = new uint256[](1); + borgRecipientPrivateKeys[0] = privateKeySalt + 100; + address recipient = vm.addr(borgRecipientPrivateKeys[0]); + // Prepare funds for guardians + deal(recipient, 1 ether); + + YearnBorgCompensation2025_2026.CompInfo memory tempCompInfo; + + // Reduce guardians to the first one + tempCompInfo = config2025_2026.compRecipients[0]; + config2025_2026.compRecipients = new YearnBorgCompensation2025_2026.CompInfo[](1); + config2025_2026.compRecipients[0] = tempCompInfo; + // Override recipient address with one we control, and its offline signature + config2025_2026.compRecipients[0].partyInfo.evmAddress = recipient; + // {"domain":{"name":"CyberAgreementRegistry","version":"1","chainId":11155111,"verifyingContract":"0xa9E808B8eCBB60Bb19abF026B5b863215BC4c134"},"message":{"contractId":"0x335ee80c3cd43c1d2e607f145879510e17e385b4cf7d7fbf1f734e70a102d717","legalContractUri":"ipfs://bafkreidefnk2tf6req4tn3bya7pkfkt45i6cppmannb5fz7ncv6mfg6vj4","globalFields":["metavestType","grantor","grantee","tokenContract","tokenStreamTotal","vestingCliffCredit","unlockingCliffCredit","vestingRate","vestingStartTime","unlockRate","unlockStartTime"],"partyFields":["name","evmAddress"],"globalValues":["0","0x4F22ba82a6B71F7305d1be7Ae7323811f9D555Ab","0x600cbFB6b453b1Cd26796eb8f0B4020118638386","0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238","10","0","0","10","1756684800","10","1756684800"],"partyValues":["Yearn BORG Test","0x4F22ba82a6B71F7305d1be7Ae7323811f9D555Ab"]},"primaryType":"SignatureData","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[]"}]}} + config2025_2026.compRecipients[0].signature = hex"e8032b150a9af0099daf927799793c84d827ec9a239f0cf6c0b15cbdc0839ad5387a5b9f3a9b3441e2c01352ded51b405bbc6b5e3c34ba66918b4a1597e346d31c"; + + // Assume prerequisites have been deployed + auth = config2025_2026.registry.AUTH(); + + // Assume 2025-2026 compensation contracts have been deployed + + // Assume all all templates have been deployed + + // TODO Uncomment to simulate BORG SAFE to execute txs as instructed (payloads are copied directly from a recent production deployment) +// borgSafeTxs = new GnosisTransaction[](2); +// borgSafeTxs[0] = GnosisTransaction({ +// to: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238, +// value: 0, +// data: hex"095ea7b3000000000000000000000000fa5ab18bd5e02b1d6430e91c32c5cb5e7f43bb650000000000000000000000000000000000000000000000000000000000989680" +// }); +// borgSafeTxs[1] = GnosisTransaction({ +// to: 0xa9E808B8eCBB60Bb19abF026B5b863215BC4c134, +// value: 0, +// data: hex"e988dc910000000000000000000000005ff4e90efa2b88cf3ca92d63d244a78a88219abf0000000000000000000000000000000000000000000000000000000068ffb4dc" +// }); +// for (uint256 i = 0; i < borgSafeTxs.length; i++) { +// vm.prank(address(config2025_2026.borgSafe)); +// (borgSafeTxs[i].to).call{value: borgSafeTxs[i].value}(borgSafeTxs[i].data); +// } + } +} From 9e592798298a99adcf7b930efa1cf561e287437a Mon Sep 17 00:00:00 2001 From: detoo Date: Mon, 13 Oct 2025 15:49:17 -0700 Subject: [PATCH 11/14] chore: add README and remove unused codes --- .env-template | 15 +++++++++++++++ README.md | 35 ++++++++++++++++++++++++++++++++--- src/MetaVesTController.sol | 2 -- 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 .env-template diff --git a/.env-template b/.env-template new file mode 100644 index 0000000..88e78d7 --- /dev/null +++ b/.env-template @@ -0,0 +1,15 @@ +DEPLOYER_PRIVATE_KEY=0x + +ETHERSCAN_API_KEY= + +AGREEMENT_SALT= + +NUM_RECIPIENTS=1 + +# X = [0..NUM_RECIPIENTS) +RECIPIENT_NAME_X="Alice" +RECIPIENT_ADDR_X=0x +RECIPIENT_AGREEMENT_URI_X=ipfs:// +RECIPIENT_TEMPLATE_ID_X= +RECIPIENT_TEMPLATE_NAME_X="" +RECIPIENT_SAFE_DELEGATE_SIGNATURE_X=0x diff --git a/README.md b/README.md index b773038..8f22450 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ Before you begin, ensure you have the following installed: - [Node.js](https://nodejs.org/) - [Foundry](https://book.getfoundry.sh/getting-started/installation.html) -- solc v0.8.20 +- solc v0.8.28 ## Installation @@ -176,6 +176,35 @@ To set up the project locally, follow these steps: ``` 3. **Compile Contracts** - ```base - forge build --optimize --optimizer-runs 200 --use solc:0.8.20 + ```bash + forge build --via-ir --optimize --optimizer-runs 200 --use solc:0.8.28 ``` + +## Deployment + +- `scripts/`: Scripts for deployment and maintenance. Some of them are integrated in tests to keep consistency +- `scripts/lib`: Utilities and configs. Configs are separate by deploy targets (e.g. projects & networks) +- `.env*`: Env vars for sensitive data. Required for most configs. Also separate by deploy targets + +```bash +# You can find end-to-end tests against the following scripts in test/YearnBorgCompensation.t.sol + +# Setup env vars for your target +cp .env.your-target .env + +# Deploy prerequisites contracts if needed (ex. factories) +forge script scripts/deployYearnBorgCompensationPrerequisites.s.sol --rpc-url --use solc:0.8.28 --via-ir --optimize --optimizer-runs 200 --broadcast +# Deploy project-specific contracts (ex. controllers) +forge script scripts/deployYearnBorgCompensation.s.sol --rpc-url --use solc:0.8.28 --via-ir --optimize --optimizer-runs 200 --broadcast +# Create template agreements if needed +forge script scripts/createAllTemplates.s.sol --rpc-url --use solc:0.8.28 --via-ir --optimize --optimizer-runs 200 --broadcast +# Propose MetaVesT deals per configs +forge script scripts/proposeAllGuardiansMetavestDeals.s.sol --rpc-url --use solc:0.8.28 --via-ir --optimize --optimizer-runs 200 --broadcast +``` + +## Tests + +To run tests: +```bash +forge test --via-ir --fork-url -vvv +``` diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index d89d6c4..6c83a09 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -41,8 +41,6 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { address public vestingFactory; // address public tokenOptionFactory; // address public restrictedTokenFactory; - address public zkCappedMinter; - address public ZkTokenAddress; address internal _pendingAuthority; address internal _pendingDao; From 9de638794908e5d8a665323ecd48691189eb7f0a Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 14 Oct 2025 15:10:17 -0700 Subject: [PATCH 12/14] chore: add comments to low-decimal edge cases --- src/BaseAllocation.sol | 6 +++++ test/VestingAllocation.t.sol | 34 ++++++++++++++++++++++++- test/lib/MetaVesTControllerTestBase.sol | 3 +-- test/mocks/MockERC20.sol | 14 +++++++++- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index d5ae8f6..520438c 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -148,8 +148,14 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ uint128 vestingCliffCredit; // lump sum of tokens which become vested at 'startTime' and will be added to '_linearVested' uint128 unlockingCliffCredit; // lump sum of tokens which become unlocked at 'startTime' and will be added to '_linearUnlocked' uint160 vestingRate; // tokens per second that become vested; if RESTRICTED this amount corresponds to 'lapse rate' for tokens that become non-repurchasable + // WARNING: since it uses the token's native decimals, there's a possibility of underflow if both the rate and decimals are low + // For example, 10 tokens/year would mean 10 / (365 * 24 * 3600) = 0.0000003170979198 token/sec. + // If decimals are only 6, the rate would be truncated to 0 and leads to unexpected results. uint48 vestingStartTime; // if RESTRICTED this amount corresponds to 'lapse start time' uint160 unlockRate; // tokens per second that become unlocked; + // WARNING: since it uses the token's native decimals, there's a possibility of underflow if both the rate and decimals are low + // For example, 10 tokens/year would mean 10 / (365 * 24 * 3600) = 0.0000003170979198 token/sec. + // If decimals are only 6, the rate would be truncated to 0 and leads to unexpected results. uint48 unlockStartTime; // start of the linear unlock address tokenContract; // contract address of the ERC20 token included in the MetaVesT } diff --git a/test/VestingAllocation.t.sol b/test/VestingAllocation.t.sol index 4cb86a7..7c06459 100644 --- a/test/VestingAllocation.t.sol +++ b/test/VestingAllocation.t.sol @@ -37,7 +37,7 @@ contract VestingAllocationTest is Test { function setUp() public { // Provision payment token - paymentToken = new MockERC20("Payment Token", "PAY"); + paymentToken = new MockERC20("Payment Token", "PAY", 18); // Create mock controller mockController = new MockMetaVesTController(address(this)); @@ -78,6 +78,38 @@ contract VestingAllocationTest is Test { assertEq(vestingAllocation.recipient(), recipient, "Unexpected recipient"); } + /// @notice Since MetaVesT uses ERC20 token's native precision, one must beware of precision loss + /// when calculating the vesting/unlocking rates + function test_LowPrecisionLowAmount() public { + MockERC20 lowPrecisionPaymentToken = new MockERC20("Low Precision Payment Token", "LPAY", 6); + + // Provision the vesting contract + + // Seemingly innocent rate of 10 tokens over a year = 10 / (365 * 24 * 3600) = 0.0000003170979198 token / sec. + // However, it would be truncated to 0 if represented in 6 decimals + uint160 rate = uint160(10e6) / 365 days; + + vestingAllocation = new VestingAllocation( + grantee, + recipient, + address(mockController), + BaseAllocation.Allocation({ + tokenContract: address(lowPrecisionPaymentToken), + tokenStreamTotal: 10e6, + vestingCliffCredit: 0e6, + unlockingCliffCredit: 0e6, + vestingRate: rate, + vestingStartTime: uint48(block.timestamp), + unlockRate: rate, + unlockStartTime: uint48(block.timestamp) + }), + new BaseAllocation.Milestone[](0) + ); + + skip(30 days); + assertEq(vestingAllocation.getAmountWithdrawable(), 0e6, "Expect low rate & decimals to lead to underflow"); + } + function test_Withdraw() public { // Should withdraw to recipient by default uint256 balanceBefore = paymentToken.balanceOf(recipient); diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol index e1f3ed2..8b07847 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -11,8 +11,7 @@ import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreement import {MockERC20} from "../mocks/MockERC20.sol"; contract MetaVesTControllerTestBase is Test { - // TODO WIP: provision fundings - MockERC20 paymentToken = new MockERC20("Payment Token", "PAY"); + MockERC20 paymentToken = new MockERC20("Payment Token", "PAY", 18); address deployer = address(0x2); address guardianSafe = address(0x3); diff --git a/test/mocks/MockERC20.sol b/test/mocks/MockERC20.sol index 0f2a40c..5aa2d2c 100644 --- a/test/mocks/MockERC20.sol +++ b/test/mocks/MockERC20.sol @@ -1,7 +1,19 @@ import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; contract MockERC20 is ERC20 { - constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {} + uint8 _decimals; + + constructor( + string memory _name, + string memory _symbol, + uint8 __decimals + ) ERC20(_name, _symbol) { + _decimals = __decimals; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } function mint(address to, uint256 amount) public { _mint(to, amount); From 04bcc5f24b0371bddfe7bd44295d3fc6f4b5c013 Mon Sep 17 00:00:00 2001 From: detoo Date: Tue, 14 Oct 2025 16:56:24 -0700 Subject: [PATCH 13/14] fix: add a lower bound to the rate to maintain precision --- src/BaseAllocation.sol | 1 + src/VestingAllocation.sol | 1 + test/VestingAllocation.t.sol | 6 ++---- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index 520438c..f08e07b 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -111,6 +111,7 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ error MetaVesT_OnlyAuthority(); error MetaVesT_ZeroAddress(); error MetaVesT_RateTooHigh(); + error MetaVesT_RateTooLow(); error MetaVesT_ZeroAmount(); error MetaVesT_MilestoneIndexOutOfRange(); error MetaVesT_NotTerminated(); diff --git a/src/VestingAllocation.sol b/src/VestingAllocation.sol index 680eb0c..9d8726e 100644 --- a/src/VestingAllocation.sol +++ b/src/VestingAllocation.sol @@ -28,6 +28,7 @@ contract VestingAllocation is BaseAllocation { 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(); + if (_allocation.vestingRate < 100 || _allocation.unlockRate < 100) revert MetaVesT_RateTooLow(); //set vesting allocation variables allocation.tokenContract = _allocation.tokenContract; diff --git a/test/VestingAllocation.t.sol b/test/VestingAllocation.t.sol index 7c06459..4fcad9e 100644 --- a/test/VestingAllocation.t.sol +++ b/test/VestingAllocation.t.sol @@ -80,7 +80,7 @@ contract VestingAllocationTest is Test { /// @notice Since MetaVesT uses ERC20 token's native precision, one must beware of precision loss /// when calculating the vesting/unlocking rates - function test_LowPrecisionLowAmount() public { + function test_RevertIf_LowPrecisionLowAmount() public { MockERC20 lowPrecisionPaymentToken = new MockERC20("Low Precision Payment Token", "LPAY", 6); // Provision the vesting contract @@ -89,6 +89,7 @@ contract VestingAllocationTest is Test { // However, it would be truncated to 0 if represented in 6 decimals uint160 rate = uint160(10e6) / 365 days; + vm.expectRevert(BaseAllocation.MetaVesT_RateTooLow.selector); vestingAllocation = new VestingAllocation( grantee, recipient, @@ -105,9 +106,6 @@ contract VestingAllocationTest is Test { }), new BaseAllocation.Milestone[](0) ); - - skip(30 days); - assertEq(vestingAllocation.getAmountWithdrawable(), 0e6, "Expect low rate & decimals to lead to underflow"); } function test_Withdraw() public { From 7948519aa8bd1e083b56a7ef9538ea257fd4d987 Mon Sep 17 00:00:00 2001 From: detoo Date: Thu, 23 Oct 2025 10:29:56 -0700 Subject: [PATCH 14/14] fix: stack too deep --- scripts/proposeMetavestDeal.s.sol | 41 +++++++++++++------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/scripts/proposeMetavestDeal.s.sol b/scripts/proposeMetavestDeal.s.sol index 6e32995..7ab3e0d 100644 --- a/scripts/proposeMetavestDeal.s.sol +++ b/scripts/proposeMetavestDeal.s.sol @@ -58,15 +58,12 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { YearnBorgCompensation2025_2026.Config memory config ) public virtual returns(bytes32) { - address borgSafeDelegate = borgSafeDelegatePrivateKey != 0 - ? vm.addr(borgSafeDelegatePrivateKey) - : 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): ", borgSafeDelegate); + console2.log("Proposer: ", vm.addr(proposerPrivateKey)); + console2.log("Guardian SAFE Delegate (if private key available): ", borgSafeDelegatePrivateKey != 0 + ? vm.addr(borgSafeDelegatePrivateKey) + : address(0)); console2.log("Guardian Safe: ", address(config.borgSafe)); console2.log("Payment token: ", address(config.paymentToken)); console2.log("CyberAgreementRegistry: ", address(config.registry)); @@ -78,8 +75,6 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { // Propose a new deal - uint48 startTime = config.metavestVestingAndUnlockStartTime; - address[] memory parties = new address[](2); parties[0] = address(config.borgSafe); parties[1] = guardianInfo.partyInfo.evmAddress; @@ -102,20 +97,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { (string memory agreementUri, ) = config.registry.templates(guardianInfo.compTemplate.id); - bytes memory signature = (borgSafeDelegatePrivateKey != 0) - ? CyberAgreementUtils.signAgreementTypedData( - config.registry, - expectedContractId, - agreementUri, - guardianInfo.compTemplate.globalFields, - guardianInfo.compTemplate.partyFields, - globalValues, - partyValues[0], - borgSafeDelegatePrivateKey - ) - : guardianInfo.signature; - - if (signature.length > 0) { + if (borgSafeDelegatePrivateKey != 0 || guardianInfo.signature.length > 0) { // has signature // Has valid signature, proceed to proposal vm.startBroadcast(proposerPrivateKey); @@ -129,7 +111,18 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { globalValues, parties, partyValues, - signature, + (borgSafeDelegatePrivateKey != 0) + ? CyberAgreementUtils.signAgreementTypedData( + config.registry, + expectedContractId, + agreementUri, + guardianInfo.compTemplate.globalFields, + guardianInfo.compTemplate.partyFields, + globalValues, + partyValues[0], + borgSafeDelegatePrivateKey + ) + : guardianInfo.signature, bytes32(0), // no secrets block.timestamp + 365 days * 2 // 2 years after deployment );