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/.gitmodules b/.gitmodules index 9e651f1..357827c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,12 @@ [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 +[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/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/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/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/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/scripts/createAllTemplates.s.sol b/scripts/createAllTemplates.s.sol index 31cbe86..74df36d 100644 --- a/scripts/createAllTemplates.s.sol +++ b/scripts/createAllTemplates.s.sol @@ -1,14 +1,12 @@ // 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 {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"; -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"; @@ -18,65 +16,32 @@ import {metavestController} from "../src/MetaVesTController.sol"; contract CreateAllTemplatesScript is SafeTxHelper, Script { /// @dev For running from `forge script` function run() public virtual { - // zkSync mainnet - run(ZkSyncGuardianCompensation2024_2025.getDefault(vm)); + run( +// // Ethereum mainnet +// YearnBorgCompensation2025_2026.getDefault(vm) + + // Sepolia testnet + YearnBorgCompensationSepolia2025_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; - - // zkSync Era (zkSync Guardians) - config = ZkSyncGuardianCompensation2024_2025.getDefault(vm); - - safe = config.guardianSafe; - - // 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 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 ) }); } @@ -85,7 +50,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/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 57% rename from scripts/deployZkSyncGuardianCompensation.s.sol rename to scripts/deployYearnBorgCompensation.s.sol index 9edc0b0..7935855 100644 --- a/scripts/deployZkSyncGuardianCompensation.s.sol +++ b/scripts/deployYearnBorgCompensation.s.sol @@ -1,41 +1,32 @@ // 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 {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"; -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 {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"; 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) +// // Ethereum mainnet for 2025-2026 +// "MetaLexMetaVestYearnBorgCompensationLaunchV1.0.2025-2026", +// YearnBorgCompensation2025_2026.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) + // Sepolia testnet + "MetaLexMetaVestYearnBorgCompensationLaunch-testnet-V0.1", + YearnBorgCompensationSepolia2025_2026.getDefault(vm) ); } @@ -43,7 +34,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 +42,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 +60,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,25 +69,26 @@ contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { vm.stopBroadcast(); - // 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), + to: address(config.paymentToken), value: 0, data: abi.encodeWithSelector( - IZkCappedMinterV2.grantRole.selector, - config.zkCappedMinter.MINTER_ROLE(), - address(controller) + ERC20.approve.selector, + address(controller), + config.paymentTokenApprovalCap ) }); safeTxs[1] = GnosisTransaction({ - to: address(controller), + to: address(config.registry), value: 0, data: abi.encodeWithSelector( - controller.setZkCappedMinter.selector, - address(config.zkCappedMinter) + CyberAgreementRegistry.setDelegation.selector, + config.borgAgreementDelegate, + block.timestamp + 14 days ) }); diff --git a/scripts/deployYearnBorgCompensationPrerequisites.s.sol b/scripts/deployYearnBorgCompensationPrerequisites.s.sol new file mode 100644 index 0000000..73e6b1f --- /dev/null +++ b/scripts/deployYearnBorgCompensationPrerequisites.s.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: AGPL-3.0-only +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"; +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 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"), + +// // Ethereum mainnet +// "MetaLexMetaVestYearnBorgCompensationLaunchV1.0", +// YearnBorgCompensation2025_2026.getDefault(vm) + + // Sepolia testnet + "MetaLexMetaVestYearnBorgCompensationLaunch-testnet-V0.1", + YearnBorgCompensationSepolia2025_2026.getDefault(vm) + ); + } + + /// @dev For running in tests + function deployPrerequisites( + uint256 deployerPrivateKey, + string memory saltStr, + YearnBorgCompensation2025_2026.Config memory config + ) public virtual returns( + VestingAllocationFactory + ) { + address deployer = vm.addr(deployerPrivateKey); + + console2.log(""); + console2.log("=== DeployYearnBorgCompensationPrerequisitesScript ==="); + console2.log("Deployer: ", deployer); + console2.log("Salt string: ", saltStr); + console2.log(""); + + bytes32 salt = keccak256(bytes(saltStr)); + + vm.startBroadcast(deployerPrivateKey); + + // Deploy MetaVesT pre-requisites + + VestingAllocationFactory vestingAllocationFactory = new VestingAllocationFactory{salt: salt}(); + + vm.stopBroadcast(); + + // Output logs + + console2.log("Deployed addresses:"); + console2.log(" VestingAllocationFactory: ", address(vestingAllocationFactory)); + console2.log(""); + + return vestingAllocationFactory; + } +} diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol deleted file mode 100644 index 2c5b002..0000000 --- a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.24; - -import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; -import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; -import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; -import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; -import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; -import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; -import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; -import {Script} from "forge-std/Script.sol"; -import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; -import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; -import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import {console2} from "forge-std/console2.sol"; -import {metavestController} from "../src/MetaVesTController.sol"; - -contract DeployZkSyncGuardianCompensationPrerequisitesScript is 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 { - deployPrerequisites( - vm.envUint("DEPLOYER_PRIVATE_KEY"), - - // zkSync Era - "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0", - ZkSyncGuardianCompensation2024_2025.getDefault(vm) - -// // zkSync Sepolia -// "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.1", -// ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) - ); - } - - /// @dev For running in tests - function deployPrerequisites( - uint256 deployerPrivateKey, - string memory saltStr, - ZkSyncGuardianCompensation2024_2025.Config memory config - ) public virtual returns( - BorgAuth, - CyberAgreementRegistry, - VestingAllocationFactory - ) { - address deployer = vm.addr(deployerPrivateKey); - - console2.log(""); - console2.log("=== DeployZkSyncGuardianCompensationPrerequisitesScript ==="); - 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) - ) - ))); - - // 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()); - auth.zeroOwner(); - - // Deploy MetaVesT pre-requisites - - VestingAllocationFactory vestingAllocationFactory = new VestingAllocationFactory{salt: salt}(); - - vm.stopBroadcast(); - - // 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); - } -} 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..eaf4327 100644 --- a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol +++ b/scripts/lib/YearnBorgCompensation2025_2026.sol @@ -3,28 +3,25 @@ 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; // USDC - // zkSync Guardians + // Yearn BORG - IGnosisSafe guardianSafe; - PartyInfo guardianSafeInfo; + IGnosisSafe borgSafe; + PartyInfo borgSafeInfo; + address borgAgreementDelegate; // Delegate EOA for signing agreement on BORG's behalf // MetaLeX @@ -33,15 +30,11 @@ 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; - uint256 fixedAnnualCompensation; + CompInfo[] compRecipients; + 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; } @@ -59,100 +52,77 @@ 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) }), + borgAgreementDelegate: 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF, // TODO TBD // 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), + 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) }); } - // 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 +162,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 +191,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/YearnBorgCompensationSepolia2025_2026.sol b/scripts/lib/YearnBorgCompensationSepolia2025_2026.sol new file mode 100644 index 0000000..b8de1d6 --- /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: 0xF450eF4F268eaF2d3D8F9eD0354852E255A5EAEF, // mintable test USDC + + // 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(0x87dC5e3FBFE8B5F2B74C64eE34da8bdc9fedCb0f), + controller: metavestController(0xFa5Ab18bD5E02B1d6430e91C32C5CB5e7F43bB65), + + // 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) + }); + } +} 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..21433c1 100644 --- a/scripts/proposeAllGuardiansMetavestDeals.s.sol +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -6,62 +6,41 @@ 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 {YearnBorgCompensationSepolia2025_2026} from "./lib/YearnBorgCompensationSepolia2025_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 - 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 + // Ethereum mainnet 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) + + // Sepolia testnet for 2025-2026 + vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey + uint256(0), // delegate will sign offline + uint256(keccak256("MetaLexMetaVestYearnBorgCompensationLaunch-testnet-V0.1")), // agreementSalt + YearnBorgCompensationSepolia2025_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 +53,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 +82,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 +98,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..7ab3e0d 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,44 +51,38 @@ 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(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("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)); console2.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); console2.log("MetavesTController: ", address(config.controller)); - console2.log("ZkCappedMinterV2: ", address(config.zkCappedMinter)); console2.log(""); // Assume Guardian SAFE already delegate signing to the deployer // Propose a new deal - 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,20 +97,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { (string memory agreementUri, ) = config.registry.templates(guardianInfo.compTemplate.id); - bytes memory signature = (guardianSafeDelegatePrivateKey != 0) - ? CyberAgreementUtils.signAgreementTypedData( - config.registry, - expectedContractId, - agreementUri, - guardianInfo.compTemplate.globalFields, - guardianInfo.compTemplate.partyFields, - globalValues, - partyValues[0], - guardianSafeDelegatePrivateKey - ) - : guardianInfo.signature; - - if (signature.length > 0) { + if (borgSafeDelegatePrivateKey != 0 || guardianInfo.signature.length > 0) { // has signature // Has valid signature, proceed to proposal vm.startBroadcast(proposerPrivateKey); @@ -134,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 ); @@ -164,7 +152,7 @@ contract ProposeMetaVestDealScript is SafeTxHelper, Script { )); console2.log("==== JSON data end ===="); - return bytes32(0); + return expectedContractId; } } } 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/BaseAllocation.sol b/src/BaseAllocation.sol index bbd3ec8..f08e07b 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.28; /// @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) @@ -114,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(); @@ -151,8 +149,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 } @@ -271,7 +275,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 +325,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, recipient, _amount); emit MetaVesT_Withdrawn(msg.sender, recipient, allocation.tokenContract, _amount); } diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 42c7b11..6c83a09 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -9,13 +9,11 @@ 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"; import "./interfaces/IPriceAllocation.sol"; -import "./interfaces/zk-governance/IZkCappedMinterV2.sol"; -import "./interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; import "./lib/EnumberableSet.sol"; //interface deleted @@ -43,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; @@ -117,7 +113,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 +127,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 @@ -288,8 +283,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, @@ -298,14 +293,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 ); @@ -340,6 +335,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, @@ -390,7 +406,6 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { { revert MetaVesTController_IncorrectMetaVesTType(); } - // Grant MetaVesT minter privilege metavestAgreementIds[deal.metavest] = agreementId; return deal.metavest; @@ -429,6 +444,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 +500,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 +513,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { 0, 0 ); + safeTransferFrom(_allocation.tokenContract, authority, vestingAllocation, _total); return vestingAllocation; } @@ -502,7 +526,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 +537,7 @@ contract metavestController is UUPSUpgradeable, SafeTransferLib { // _milestones // ); // -// //safeTransferFrom(_allocation.tokenContract, authority, tokenOptionAllocation, _total); +// safeTransferFrom(_allocation.tokenContract, authority, tokenOptionAllocation, _total); // return tokenOptionAllocation; // } // @@ -524,7 +548,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 +559,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(); @@ -573,19 +587,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 @@ -609,9 +623,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 +916,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]; } 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/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/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..8bb8b9b 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 test_RevertIf_AuditTerminateFailAfterWithdraw() 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..418fe93 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 test_RevertIf_AuditTerminateFailAfterWithdraw() 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..4fcad9e 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,17 @@ 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)); + // Provision payment token + paymentToken = new MockERC20("Payment Token", "PAY", 18); // 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({ @@ -76,12 +50,13 @@ contract VestingAllocationTest is Test { conditionContracts: new address[](0) }); + // Provision the vesting contract vestingAllocation = new VestingAllocation( grantee, recipient, address(mockController), BaseAllocation.Allocation({ - tokenContract: address(zkToken), + tokenContract: address(paymentToken), tokenStreamTotal: 1000 ether, vestingCliffCredit: 100 ether, unlockingCliffCredit: 100 ether, @@ -92,6 +67,10 @@ contract VestingAllocationTest is Test { }), milestones ); + paymentToken.mint( + address(vestingAllocation), + 1000 ether + 2000 ether // allocation.tokenStreamTotal + milestones[].milestoneAward + ); } function test_Metadata() public { @@ -99,16 +78,46 @@ 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_RevertIf_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; + + vm.expectRevert(BaseAllocation.MetaVesT_RateTooLow.selector); + 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) + ); + } + 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 +134,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 { @@ -141,7 +150,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"); } diff --git a/test/YearnBorgCompensation.t.sol b/test/YearnBorgCompensation.t.sol new file mode 100644 index 0000000..3893825 --- /dev/null +++ b/test/YearnBorgCompensation.t.sol @@ -0,0 +1,256 @@ +// 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); + + VestingAllocationFactory vestingAllocationFactory; + metavestController controller; + GnosisTransaction[] memory safeTxsCreateAllTemplates; + GnosisTransaction[] memory safeTxs2025_2026; + + config2025_2026 = YearnBorgCompensation2025_2026.getDefault(vm); + + // Override recipient 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 + + // Update known info + auth = config2025_2026.registry.AUTH(); + + // Deploy prerequisites + vestingAllocationFactory = DeployYearnBorgCompensationPrerequisitesScript.deployPrerequisites( + deployerPrivateKey, + saltStr, + config2025_2026 + ); + + // Update configs with deployed contracts + 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, + 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 < 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 BORG SAFE to prepare USDC + deal(config2025_2026.paymentToken, address(config2025_2026.borgSafe), 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"); + + // 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 { + // 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 + 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 + 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) { + // 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/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); +// } + } +} 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 - } -} diff --git a/test/amendement.t.sol b/test/amendement.t.sol index 9b300ad..48e7d19 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,23 +44,17 @@ 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); + // Prepare funds + paymentToken.mint( + address(guardianSafe), + 9999 ether + ); + vm.prank(address(guardianSafe)); + paymentToken.approve(address(controller), 9999 ether); - controller.setZkCappedMinter(address(zkCappedMinter)); - 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 @@ -70,6 +63,9 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { vm.stopPrank(); vestingAllocation = createDummyVestingAllocation(); + + vm.prank(authority); + controller.createSet("testSet"); } function testProposeMetavestAmendment() public { @@ -380,7 +376,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 +390,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..a63f233 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,17 @@ 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(); + // Prepare funds + paymentToken.mint( + address(guardianSafe), + 9999 ether + ); + vm.prank(address(guardianSafe)); + paymentToken.approve(address(controller), 9999 ether); + vm.startPrank(guardianSafe); - controller.setZkCappedMinter(address(zkCappedMinter)); // Guardian SAFE to set capped minter on MetaVesTController controller.createSet("testSet"); vm.stopPrank(); @@ -86,15 +72,14 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { delegatePrivateKey, alice, BaseAllocation.Allocation({ - tokenContract: address(zkToken), - // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenContract: address(paymentToken), 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 +103,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 +132,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 +156,8 @@ contract MetaVestControllerTest is MetaVesTControllerTestBase { // conditionContracts: new address[](0) // }); // +// token.approve(address(controller), 1100e18); +// // address restrictedTokenAward = controller.createMetavest( // metavestController.metavestType.RestrictedTokenAward, // grantee, @@ -183,7 +170,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); // } @@ -335,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 { @@ -566,7 +559,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 +600,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 +640,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 +674,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 +708,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,20 +895,28 @@ 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); + + 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(zkToken.balanceOf(authority), 0); + assertEq(paymentToken.balanceOf(vestingAllocation), 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,35 +926,51 @@ 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); - vm.warp(block.timestamp + 25 seconds); + uint256 snapshot = paymentToken.balanceOf(authority); + 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(zkToken.balanceOf(authority), 0); + assertEq(paymentToken.balanceOf(vestingAllocation), 0); } - function testTerminateRecoverChunksBefore() public { + 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()); 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(zkToken.balanceOf(authority), 0); + assertEq(paymentToken.balanceOf(vestingAllocation), 0); } // function testConfirmingMilestoneRestrictedTokenAllocation() public { @@ -981,7 +998,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 +1405,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 +1448,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 +1471,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 +1509,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 +1538,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 +1626,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..8b07847 100644 --- a/test/lib/MetaVesTControllerTestBase.sol +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -4,23 +4,14 @@ 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; + MockERC20 paymentToken = new MockERC20("Payment Token", "PAY", 18); address deployer = address(0x2); address guardianSafe = address(0x3); @@ -95,15 +86,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..5aa2d2c --- /dev/null +++ b/test/mocks/MockERC20.sol @@ -0,0 +1,21 @@ +import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20 { + 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); + } +}