diff --git a/.gitignore b/.gitignore index 8345343..359e274 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ cache/ out/ .env +.env.* test-command.txt broadcast/ .DS_Store diff --git a/.gitmodules b/.gitmodules index 6022e5c..9e651f1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/zk-governance"] path = lib/zk-governance url = https://github.com/zksync-association/zk-governance +[submodule "lib/cybercorps-contracts"] + path = lib/cybercorps-contracts + url = https://github.com/MetaLex-Tech/cybercorps-contracts diff --git a/lib/cybercorps-contracts b/lib/cybercorps-contracts new file mode 160000 index 0000000..e2afc8b --- /dev/null +++ b/lib/cybercorps-contracts @@ -0,0 +1 @@ +Subproject commit e2afc8b1fbecff89f97b8b23d6134508ed00437a diff --git a/lib/zk-governance b/lib/zk-governance index 663418c..f9915cb 160000 --- a/lib/zk-governance +++ b/lib/zk-governance @@ -1 +1 @@ -Subproject commit 663418c83bd6e0190976bad3c37374213d8c004f +Subproject commit f9915cb59ff1ab8f2819f3ec3f6189a71e3b65f0 diff --git a/remappings.txt b/remappings.txt index 217f5fe..8aeadcc 100644 --- a/remappings.txt +++ b/remappings.txt @@ -8,4 +8,5 @@ openzeppelin-contracts/=lib/zk-governance/l1-contracts/lib/openzeppelin-contract @openzeppelin/contracts-upgradeable=lib/zk-governance/l2-contracts/lib/openzeppelin-contracts-upgradeable/contracts @openzeppelin/contracts/=lib/zk-governance/l2-contracts/lib/openzeppelin-contracts/contracts/ solmate/=lib/zk-governance/l2-contracts/lib/flexible-voting/lib/solmate/src/ -zk-governance/=lib/zk-governance/ \ No newline at end of file +zk-governance/=lib/zk-governance/ +cybercorps-contracts/=lib/cybercorps-contracts/ diff --git a/scripts/createAllTemplates.s.sol b/scripts/createAllTemplates.s.sol new file mode 100644 index 0000000..31cbe86 --- /dev/null +++ b/scripts/createAllTemplates.s.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IZkCappedMinterV2} from "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract CreateAllTemplatesScript is SafeTxHelper, Script { + /// @dev For running from `forge script` + function run() public virtual { + // zkSync mainnet + run(ZkSyncGuardianCompensation2024_2025.getDefault(vm)); + } + + /// @dev For running in tests + function run( + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns(GnosisTransaction[] memory) { + IGnosisSafe safe; + ZkSyncGuardianCompensation2024_2025.Config memory config; + + // zkSync Era (zkSync Guardians) + config = ZkSyncGuardianCompensation2024_2025.getDefault(vm); + + safe = config.guardianSafe; + + // TODO deprecated +// GnosisTransaction[] memory safeTxs = new GnosisTransaction[](config.guardians.length + 1); +// safeTxs[0] = GnosisTransaction({ +// to: address(config.registry), +// value: 0 ether, +// data: abi.encodeWithSelector( +// CyberAgreementRegistry.createTemplate.selector, +// config.borgResolutionTemplate.id, +// config.borgResolutionTemplate.name, +// config.borgResolutionTemplate.agreementUri, +// config.borgResolutionTemplate.globalFields, +// config.borgResolutionTemplate.partyFields +// ) +// }); +// for (uint i = 0; i < config.guardians.length ; i++) { +// ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config.guardians[i]; +// safeTxs[i + 1] = GnosisTransaction({ +// to: address(config.registry), +// value: 0 ether, +// data: abi.encodeWithSelector( +// CyberAgreementRegistry.createTemplate.selector, +// guardian.compTemplate.id, +// guardian.compTemplate.name, +// guardian.compTemplate.agreementUri, +// guardian.compTemplate.globalFields, +// guardian.compTemplate.partyFields +// ) +// }); +// } + + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](config.guardians.length); + for (uint i = 0; i < config.guardians.length ; i++) { + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardian = config.guardians[i]; + safeTxs[i] = GnosisTransaction({ + to: address(config.registry), + value: 0 ether, + data: abi.encodeWithSelector( + CyberAgreementRegistry.createTemplate.selector, + guardian.compTemplate.id, + guardian.compTemplate.name, + guardian.compTemplate.agreementUri, + guardian.compTemplate.globalFields, + guardian.compTemplate.partyFields + ) + }); + } + + // Output logs + + console2.log(""); + console2.log("=== CreateAllTemplatesScript ==="); + console2.log("Safe: ", address(safe)); + console2.log("Safe TXs:"); + for (uint256 i = 0 ; i < safeTxs.length ; i++) { + console2.log(" #", i); + console2.log(" to:", safeTxs[i].to); + console2.log(" value:", safeTxs[i].value); + console2.log(" data:"); + console2.logBytes(safeTxs[i].data); + console2.log(""); + } + + return safeTxs; + } +} diff --git a/scripts/createSafeTx.s.sol b/scripts/createSafeTx.s.sol new file mode 100644 index 0000000..576f686 --- /dev/null +++ b/scripts/createSafeTx.s.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IZkCappedMinterV2} from "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract CreateSafeTxScript is SafeTxHelper, Script { + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + IGnosisSafe safe; + ZkSyncGuardianCompensation2024_2025.Config memory config; + + // zkSync Era (zkSync Guardians) + config = ZkSyncGuardianCompensation2024_2025.getDefault(vm); + safe = config.guardianSafe; + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](7); + safeTxs[0] = GnosisTransaction({ + to: address(config.registry), + value: 0 ether, + data: abi.encodeWithSelector( + CyberAgreementRegistry.createTemplate.selector, + vm.envUint("BORG_RESOLUTION_TEMPLATE_ID"), // template ID + vm.envString("BORG_RESOLUTION_TEMPLATE_NAME"), // template name + vm.envString("BORG_RESOLUTION_URI"), // agreement URI + config.borgResolutionGlobalFields, + config.borgResolutionPartyFields + ) + }); + for (uint i = 0; i < 6 ; i++) { + safeTxs[i + 1] = GnosisTransaction({ + to: address(config.registry), + value: 0 ether, + data: abi.encodeWithSelector( + CyberAgreementRegistry.createTemplate.selector, + vm.envUint(string(abi.encodePacked("GUARDIAN_TEMPLATE_ID_", vm.toString(i)))), // template ID + vm.envString(string(abi.encodePacked("GUARDIAN_TEMPLATE_NAME_", vm.toString(i)))), // template name + vm.envString(string(abi.encodePacked("GUARDIAN_AGREEMENT_URI_", vm.toString(i)))), // agreement URI + config.compGlobalFields, + config.compPartyFields + ) + }); + } + + // zkSync Sepolia +// config = ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm); +// safe = config.guardianSafe; +// GnosisTransaction[] memory safeTxs = new GnosisTransaction[](2); +// safeTxs[0] = GnosisTransaction({ +// to: address(config.registry), +// value: 0 ether, +// data: abi.encodeWithSelector( +// CyberAgreementRegistry.createTemplate.selector, +// config.compTemplateId, +// config.compTemplateName, +// config.compAgreementUri, +// config.compGlobalFields, +// config.compPartyFields +// ) +// }); +// safeTxs[1] = GnosisTransaction({ +// to: address(config.registry), +// value: 0 ether, +// data: abi.encodeWithSelector( +// CyberAgreementRegistry.createTemplate.selector, +// config.serviceTemplateId, +// config.serviceTemplateName, +// config.serviceAgreementUri, +// config.serviceGlobalFields, +// config.servicePartyFields +// ) +// }); + + // Output logs + + console2.log(""); + console2.log("=== CreateSafeTxScript ==="); + console2.log("Safe: ", address(safe)); + console2.log("Safe TXs:"); + for (uint256 i = 0 ; i < safeTxs.length ; i++) { + console2.log(" #", i); + console2.log(" to:", safeTxs[i].to); + console2.log(" value:", safeTxs[i].value); + console2.log(" data:"); + console2.logBytes(safeTxs[i].data); + console2.log(""); + } + } +} diff --git a/scripts/deployTestZkCappedMinter.s.sol b/scripts/deployTestZkCappedMinter.s.sol new file mode 100644 index 0000000..49b5e2e --- /dev/null +++ b/scripts/deployTestZkCappedMinter.s.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract DeployTestZkCappedMinterScript is SafeTxHelper, Script { + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + vm.envUint("DEPLOYER_PRIVATE_KEY"), + + // zkSync Sepolia for 2024-2025 + "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.2024-2025", + IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E), + ZkSyncGuardianCompensationSepolia2024_2025.getDefault(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/deployZkSyncGuardianCompensation.s.sol new file mode 100644 index 0000000..9edc0b0 --- /dev/null +++ b/scripts/deployZkSyncGuardianCompensation.s.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IZkCappedMinterV2} from "../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract DeployZkSyncGuardianCompensationScript is SafeTxHelper, Script { + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + deployCompensation( + vm.envUint("DEPLOYER_PRIVATE_KEY"), + + // zkSync Era for 2024-2025 +// "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2024-2025", +// ZkSyncGuardianCompensation2024_2025.getDefault(vm) + + // zkSync Era for 2025-2026 +// "MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2025-2026", +// ZkSyncGuardianCompensation2025_2026.getDefault(vm) + + // zkSync Sepolia for 2024-2025 + "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.1.2024-2025", + ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) + + // zkSync Sepolia for 2025-2026 +// "MetaLexMetaVestZkSyncGuardianCompensationTestnetV0.1.2025-2026", +// ZkSyncGuardianCompensationSepolia2025_2026.getDefault(vm) + ); + } + + /// @dev For running in tests + function deployCompensation( + uint256 deployerPrivateKey, + string memory saltStr, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns( + metavestController, + GnosisTransaction[] memory + ) { + address deployer = vm.addr(deployerPrivateKey); + + console2.log(""); + console2.log("=== DeployZkSyncGuardianCompensationScript ==="); + console2.log("Deployer: ", deployer); + console2.log("Salt string: ", saltStr); + console2.log("ZkCappedMinterV2: ", address(config.zkCappedMinter)); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); + console2.log(""); + + bytes32 salt = keccak256(bytes(saltStr)); + + vm.startBroadcast(deployerPrivateKey); + + // Deploy MetaVesT Controller + + metavestController controller = metavestController(address(new ERC1967Proxy{salt: salt}( + address(new metavestController{salt: salt}()), + abi.encodeWithSelector( + metavestController.initialize.selector, + address(config.guardianSafe), + address(config.guardianSafe), + address(config.registry), + address(config.vestingAllocationFactory) + ) + ))); + + vm.stopBroadcast(); + + // Prepare Guardian SAFE txs to: + // 1. Grant MetaVesT Controller MINTER ROLE + // 2. Set MetaVesT Controller's ZK Capped Minter + GnosisTransaction[] memory safeTxs = new GnosisTransaction[](2); + safeTxs[0] = GnosisTransaction({ + to: address(config.zkCappedMinter), + value: 0, + data: abi.encodeWithSelector( + IZkCappedMinterV2.grantRole.selector, + config.zkCappedMinter.MINTER_ROLE(), + address(controller) + ) + }); + safeTxs[1] = GnosisTransaction({ + to: address(controller), + value: 0, + data: abi.encodeWithSelector( + controller.setZkCappedMinter.selector, + address(config.zkCappedMinter) + ) + }); + + // Output logs + + console2.log("Deployed addresses:"); + console2.log(" MetavesTController: ", address(controller)); + console2.log(""); + + console2.log("Safe TXs:"); + for (uint256 i = 0 ; i < safeTxs.length ; i++) { + console2.log(" #", i); + console2.log(" to:", safeTxs[i].to); + console2.log(" value:", safeTxs[i].value); + console2.log(" data:"); + console2.logBytes(safeTxs[i].data); + console2.log(""); + } + + return (controller, safeTxs); + } +} diff --git a/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol new file mode 100644 index 0000000..2c5b002 --- /dev/null +++ b/scripts/deployZkSyncGuardianCompensationPrerequisites.s.sol @@ -0,0 +1,113 @@ +// 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/executeSafeTx.s.sol b/scripts/executeSafeTx.s.sol new file mode 100644 index 0000000..d7829ac --- /dev/null +++ b/scripts/executeSafeTx.s.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ISafeProxyFactory, IGnosisSafe, GnosisTransaction} from "../test/lib/safe.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract ExecuteSafeTxScript is SafeTxHelper, Script { + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + vm.envUint("DEPLOYER_PRIVATE_KEY"), + +// ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm).guardianSafe, // safe +// 0x6F26e588f28bf67C016EEA19CA90c4E41B70d499, // to +// 0, // value +// hex"2f2ff15d9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6000000000000000000000000856a8aea8a37a338e2490384bb790cd87b5caae4" // data + +// ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm).guardianSafe, // safe +// 0x856A8Aea8a37A338e2490384Bb790cD87b5CaaE4, // to +// 0, // value +// hex"66e261840000000000000000000000006f26e588f28bf67c016eea19ca90c4e41b70d499" // data + + ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm).guardianSafe, // safe + address(ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm).registry), // to + 0, // value + abi.encodeWithSelector( + CyberAgreementRegistry.setDelegation.selector, + 0xD63383fBf9F3FDf3759acA89dA00c4c0CF3A0865, + 1765401725 + ) // data + ); + } + + /// @dev For running in tests + function run( + uint256 deployerPrivateKey, + IGnosisSafe safe, + address to, + uint256 value, + bytes memory data + ) public virtual { + address deployer = vm.addr(deployerPrivateKey); + + console2.log(""); + console2.log("=== ExecuteSafeTxScript ==="); + console2.log("Deployer: ", deployer); + console2.log("SAFE: ", address(safe)); + console2.log("to: ", to); + console2.log("value: ", value); + console2.log("data: "); + console2.logBytes(data); + console2.log(""); + + vm.startBroadcast(deployerPrivateKey); + + // Guardian SAFE to set MetaVesT Controller's ZK Capped Minter + _signAndExecSafeTransaction( + deployerPrivateKey, + address(safe), + to, + value, + data + ); + + vm.stopBroadcast(); + } +} diff --git a/scripts/lib/CyberAgreementUtils.sol b/scripts/lib/CyberAgreementUtils.sol new file mode 100644 index 0000000..d945ef5 --- /dev/null +++ b/scripts/lib/CyberAgreementUtils.sol @@ -0,0 +1,186 @@ +/* .o. + .888. + .8"888. + .8' `888. + .88ooo8888. + .8' `888. +o88o o8888o + + + +ooo ooooo . ooooo ooooooo ooooo +`88. .888' .o8 `888' `8888 d8' + 888b d'888 .ooooo. .o888oo .oooo. 888 .ooooo. Y888..8P + 8 Y88. .P 888 d88' `88b 888 `P )88b 888 d88' `88b `8888' + 8 `888' 888 888ooo888 888 .oP"888 888 888ooo888 .8PY888. + 8 Y 888 888 .o 888 . d8( 888 888 o 888 .o d8' `888b +o8o o888o `Y8bod8P' "888" `Y888""8o o888ooooood8 `Y8bod8P' o888o o88888o + + + + .oooooo. .o8 .oooooo. + d8P' `Y8b "888 d8P' `Y8b +888 oooo ooo 888oooo. .ooooo. oooo d8b 888 .ooooo. oooo d8b oo.ooooo. +888 `88. .8' d88' `88b d88' `88b `888""8P 888 d88' `88b `888""8P 888' `88b +888 `88..8' 888 888 888ooo888 888 888 888 888 888 888 888 +`88b ooo `888' 888 888 888 .o 888 `88b ooo 888 888 888 888 888 .o. + `Y8bood8P' .8' `Y8bod8P' `Y8bod8P' d888b `Y8bood8P' `Y8bod8P' d888b 888bod8P' Y8P + .o..P' 888 + `Y8P' o888o +_______________________________________________________________________________________________________ + +All software, documentation and other files and information in this repository (collectively, the "Software") +are copyright MetaLeX Labs, Inc., a Delaware corporation. + +All rights reserved. + +The Software is proprietary and shall not, in part or in whole, be used, copied, modified, merged, published, +distributed, transmitted, sublicensed, sold, or otherwise used in any form or by any means, electronic or +mechanical, including photocopying, recording, or by any information storage and retrieval system, +except with the express prior written permission of the copyright holder.*/ + +pragma solidity 0.8.28; + +import {Vm} from "forge-std/Test.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; + +// Access hidden cheatcodes +interface EnhancedVm is Vm { + function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json); +} + +library CyberAgreementUtils { + EnhancedVm constant vm = EnhancedVm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + // Hard-coded since we don't have programmatic access to CyberAgreementRegistry's underlying types + string constant DOMAIN_SEPARATOR_TYPE = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; + string constant SIGNATUREDATA_TYPE = "SignatureData(bytes32 contractId,string legalContractUri,string[] globalFields,string[] partyFields,string[] globalValues,string[] partyValues)"; + + struct DomainSeparator { + string name; + string version; + uint256 chainId; + address verifyingContract; + } + + struct SignatureData { + bytes32 contractId; + string legalContractUri; + string[] globalFields; + string[] partyFields; + string[] globalValues; + string[] partyValues; + } + + function signAgreementTypedData( + CyberAgreementRegistry registry, + bytes32 contractId, + string memory contractUri, + string[] memory globalFields, + string[] memory partyFields, + string[] memory globalValues, + string[] memory partyValues, + uint256 privKey + ) internal view returns (bytes memory signature) { + // Hash string arrays the same way as the contract + bytes32 contractUriHash = keccak256(bytes(contractUri)); + bytes32 globalFieldsHash = _hashStringArray(globalFields); + bytes32 partyFieldsHash = _hashStringArray(partyFields); + bytes32 globalValuesHash = _hashStringArray(globalValues); + bytes32 partyValuesHash = _hashStringArray(partyValues); + + // Create the message hash using the same approach as the contract + bytes32 structHash = keccak256( + abi.encode( + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + contractUriHash, + globalFieldsHash, + partyFieldsHash, + globalValuesHash, + partyValuesHash + ) + ); + + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", registry.DOMAIN_SEPARATOR(), structHash) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, digest); + signature = abi.encodePacked(r, s, v); + return signature; + } + + function signVoidTypedData( + CyberAgreementRegistry registry, + bytes32 contractId, + address party, + uint256 privKey + ) internal view returns (bytes memory signature) { + // Create the message hash using the same approach as the contract + bytes32 structHash = keccak256( + abi.encode( + registry.VOIDSIGNATUREDATA_TYPEHASH(), + contractId, + party + ) + ); + + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", registry.DOMAIN_SEPARATOR(), structHash) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, digest); + signature = abi.encodePacked(r, s, v); + return signature; + } + + // Add this helper function to your test contract + function _hashStringArray( + string[] memory array + ) internal pure returns (bytes32) { + bytes32[] memory hashes = new bytes32[](array.length); + for (uint256 i = 0; i < array.length; i++) { + hashes[i] = keccak256(bytes(array[i])); + } + return keccak256(abi.encodePacked(hashes)); + } + + function formatAgreementTypedDataJson( + CyberAgreementRegistry registry, + bytes32 contractId, + string memory contractUri, + string[] memory globalFields, + string[] memory partyFields, + string[] memory globalValues, + string[] memory partyValues + ) internal returns (string memory) { + string memory domainSeparatorJson = vm.serializeJsonType( + DOMAIN_SEPARATOR_TYPE, + abi.encode(DomainSeparator({ + name: registry.name(), + version: registry.version(), + chainId: block.chainid, + verifyingContract: address(registry) + })) + ); + + string memory signatureDataJson = vm.serializeJsonType( + SIGNATUREDATA_TYPE, + abi.encode(SignatureData({ + contractId: contractId, + legalContractUri: contractUri, + globalFields: globalFields, + partyFields: partyFields, + globalValues: globalValues, + partyValues: partyValues + })) + ); + + // Build the json string with the temporary buffer at key "outputKey" + vm.serializeString("outputKey", "domain", domainSeparatorJson); + vm.serializeString("outputKey", "message", signatureDataJson); + vm.serializeString("outputKey", "primaryType", "SignatureData"); + return vm.serializeString("outputKey", "types", "{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"SignatureData\":[{\"name\":\"contractId\",\"type\":\"bytes32\"},{\"name\":\"legalContractUri\",\"type\":\"string\"},{\"name\":\"globalFields\",\"type\":\"string[]\"},{\"name\":\"partyFields\",\"type\":\"string[]\"},{\"name\":\"globalValues\",\"type\":\"string[]\"},{\"name\":\"partyValues\",\"type\":\"string[]\"}]}"); + } +} diff --git a/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol new file mode 100644 index 0000000..2ec0cfc --- /dev/null +++ b/scripts/lib/ZkSyncGuardianCompensation2024_2025.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.28; + +import {Vm} from "forge-std/Vm.sol"; +import {CommonBase} from "forge-std/Base.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {IZkTokenV1} from "../../src/interfaces/zk-governance/IZkTokenV1.sol"; +import {IZkCappedMinterV2} from "../../src/interfaces/zk-governance/IZkCappedMinterV2.sol"; +import {IGnosisSafe} from "../../test/lib/safe.sol"; +import {BaseAllocation} from "../../src/BaseAllocation.sol"; +import {VestingAllocationFactory} from "../../src/VestingAllocationFactory.sol"; +import {metavestController} from "../../src/MetaVesTController.sol"; + +library ZkSyncGuardianCompensation2024_2025 { + + struct Config { + + // ZK Governance + + IZkTokenV1 zkToken; + IZkCappedMinterV2 zkCappedMinter; + + // zkSync Guardians + + IGnosisSafe guardianSafe; + PartyInfo guardianSafeInfo; + + // MetaLeX + + IGnosisSafe metalexSafe; + CyberAgreementRegistry registry; + VestingAllocationFactory vestingAllocationFactory; + metavestController controller; + + // TODO deprecated +// // zkSync Guardian BORG Resolution +// +// TemplateInfo borgResolutionTemplate; + + // zkSync Guardian Compensation Agreement (one template per guardian for now) + + GuardianCompInfo[] guardians; + uint256 fixedAnnualCompensation; + uint48 metavestVestingAndUnlockStartTime; + BaseAllocation.Milestone[] milestones; + } + + struct TemplateInfo { + bytes32 id; + string agreementUri; + string name; + string[] globalFields; + string[] partyFields; + } + + struct PartyInfo { + string name; + address evmAddress; + } + + struct GuardianCompInfo { + PartyInfo partyInfo; + TemplateInfo compTemplate; + bytes signature; + } + + function getDefault(Vm vm) internal view returns(Config memory) { + IGnosisSafe guardianSafe = IGnosisSafe(0x06E19F3CEafBC373329973821ee738021A58F0E3); + IGnosisSafe metalexSafe = IGnosisSafe(0x99ba28257DbDB399b53bF59Aa5656480f3bdc5bc); + + return Config({ + + // ZK Governance + + zkToken: IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E), + // Vote: https://vote.zknation.io/dao/proposal/14920227315823844313255249182525601975564035647349569740836448589354658768084?govId=eip155:324:0xb83FF6501214ddF40C91C9565d095400f3F45746 + zkCappedMinter: IZkCappedMinterV2(0xE555FC98E45637D1B45e60E4fc05cF0F22836156), + + // zkSync Guardians + + guardianSafe: guardianSafe, + guardianSafeInfo: PartyInfo({ + name: "ZKsync Guardians", + evmAddress: address(guardianSafe) + }), + + // MetaLeX + + metalexSafe: metalexSafe, + registry: CyberAgreementRegistry(0x07E0a0BeC742f90f7879830bC917E783dA6a6357), + vestingAllocationFactory: VestingAllocationFactory(0xD1c48D8866d81CC0f6567f3E3fbbb8eF48E30D99), + controller: metavestController(0xD509349AF986E7202f2Bc4ae49C203E354faafCD), + + // TODO deprecated +// // zkSync Guardian BORG Resolution +// +// borgResolutionTemplate: loadBorgResolutionTemplate(vm), + + // 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) + 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({ + partyInfo: PartyInfo({ + name: vm.envString(string(abi.encodePacked("GUARDIAN_NAME_", vm.toString(i)))), + evmAddress: address(uint160(vm.envUint(string(abi.encodePacked("GUARDIAN_ADDR_", vm.toString(i)))))) + }), + compTemplate: TemplateInfo({ + id: bytes32(vm.envUint(string(abi.encodePacked("GUARDIAN_TEMPLATE_ID_", vm.toString(i))))), + agreementUri: vm.envString(string(abi.encodePacked("GUARDIAN_AGREEMENT_URI_", vm.toString(i)))), + name: vm.envString(string(abi.encodePacked("GUARDIAN_TEMPLATE_NAME_", vm.toString(i)))), + globalFields: getCompGlobalFields(), + partyFields: getCompPartyFields() + }), + signature: vm.envBytes(string(abi.encodePacked("GUARDIAN_SAFE_DELEGATE_SIGNATURE_", vm.toString(i)))) + }); + } + return guardians; + } + + 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 + tokenStreamTotal: config.fixedAnnualCompensation, + vestingCliffCredit: 0e3 ether, + unlockingCliffCredit: 0e3 ether, + vestingRate: uint160(config.fixedAnnualCompensation / 365 days), + vestingStartTime: config.metavestVestingAndUnlockStartTime, + unlockRate: uint160(config.fixedAnnualCompensation / 365 days), + unlockStartTime: config.metavestVestingAndUnlockStartTime + }); + } + + function getCompGlobalFields() internal pure returns(string[] memory) { + string[] memory compGlobalFields = new string[](11); + compGlobalFields[0] = "metavestType"; + compGlobalFields[1] = "grantor"; + compGlobalFields[2] = "grantee"; + compGlobalFields[3] = "tokenContract"; + compGlobalFields[4] = "tokenStreamTotal"; + compGlobalFields[5] = "vestingCliffCredit"; + compGlobalFields[6] = "unlockingCliffCredit"; + compGlobalFields[7] = "vestingRate"; + compGlobalFields[8] = "vestingStartTime"; + compGlobalFields[9] = "unlockRate"; + compGlobalFields[10] = "unlockStartTime"; + return compGlobalFields; + } + + function getCompPartyFields() internal pure returns(string[] memory) { + string[] memory compPartyFields = new string[](2); + compPartyFields[0] = "name"; + compPartyFields[1] = "evmAddress"; + return compPartyFields; + } + + function formatCompGlobalValues( + Config memory config, + Vm vm, + address grantee + ) internal view returns(string[] memory) { + BaseAllocation.Allocation memory allocation = parseAllocation(config); + + string[] memory globalValues = new string[](11); + globalValues[0] = "0"; // metavestType: Vesting + globalValues[1] = vm.toString(config.guardianSafeInfo.evmAddress); // grantor + globalValues[2] = vm.toString(grantee); // grantee + globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract + globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) + globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) + globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) + globalValues[7] = vm.toString(config.fixedAnnualCompensation / 1 ether); // vestingRate (annually) (human-readable) + globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime + globalValues[9] = vm.toString(config.fixedAnnualCompensation / 1 ether); // unlockRate (annually) (human-readable) + globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime + return globalValues; + } + + function formatBorgResolutionGlobalValues(Vm vm) internal view returns(string[] memory) { + return new string[](0); + } + + function formatPartyValues( + Vm vm, + PartyInfo memory partyInfo + ) internal view returns(string[] memory) { + string[] memory partyValues = new string[](2); + partyValues[0] = partyInfo.name; + partyValues[1] = vm.toString(partyInfo.evmAddress); + return partyValues; + } + + function formatPartyValues( + Vm vm, + PartyInfo memory guardianSafeInfo, + PartyInfo memory guardianInfo + ) internal view returns(string[][] memory) { + string[][] memory partyValues = new string[][](2); + partyValues[0] = formatPartyValues(vm, guardianSafeInfo); + partyValues[1] = formatPartyValues(vm, guardianInfo); + return partyValues; + } +} diff --git a/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol new file mode 100644 index 0000000..f49a7c0 --- /dev/null +++ b/scripts/lib/ZkSyncGuardianCompensation2025_2026.sol @@ -0,0 +1,54 @@ +// 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 new file mode 100644 index 0000000..5e7d447 --- /dev/null +++ b/scripts/lib/ZkSyncGuardianCompensationSepolia2024_2025.sol @@ -0,0 +1,59 @@ +// 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/lib/safeTxHelper.sol b/scripts/lib/safeTxHelper.sol new file mode 100644 index 0000000..ce8e536 --- /dev/null +++ b/scripts/lib/safeTxHelper.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.28; + +import {CommonBase} from "forge-std/Base.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../../test/lib/safe.sol"; + +contract SafeTxHelper is CommonBase { + function _signAndExecSafeTransaction(uint256 privateKey, address safe, address to, uint256 value, bytes memory data) internal { + uint8 operation = 0; // Call + uint256 safeTxGas = 0; + uint256 baseGas = 0; + uint256 gasPrice = 0; + address gasToken = address(0); + address refundReceiver = address(0); + uint256 nonce = IGnosisSafe(safe).nonce(); + + IGnosisSafe(safe).execTransaction( + to, + value, + data, + operation, + safeTxGas, + baseGas, + gasPrice, + gasToken, + refundReceiver, + _getSafeTxSignature( + privateKey, + safe, + to, + value, + data, + operation, + safeTxGas, + baseGas, + gasPrice, + gasToken, + refundReceiver, + nonce + ) + ); + } + + function _getSafeTxSignature( + uint256 privateKey, + address safe, + address to, + uint256 value, + bytes memory data, + uint8 operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address refundReceiver, + uint256 nonce + ) internal view returns (bytes memory) { + bytes memory txHashData = IGnosisSafe(safe).encodeTransactionData( + to, + value, + data, + operation, + safeTxGas, + baseGas, + gasPrice, + gasToken, + refundReceiver, + nonce + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, keccak256(txHashData)); + bytes memory signature = abi.encodePacked(r, s, v); + return signature; + } +} diff --git a/scripts/proposeAllGuardiansMetavestDeals.s.sol b/scripts/proposeAllGuardiansMetavestDeals.s.sol new file mode 100644 index 0000000..4c56d3c --- /dev/null +++ b/scripts/proposeAllGuardiansMetavestDeals.s.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {GnosisTransaction} from "../test/lib/safe.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +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 {ProposeMetaVestDealScript} from "./proposeMetavestDeal.s.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensation2025_2026} from "./lib/ZkSyncGuardianCompensation2025_2026.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +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 +// vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey +// uint256(0), // delegate will sign offline +// uint256(keccak256("MetaLexMetaVestZkSyncGuardianCompensationLaunchV1.0.2025-2026")), // agreementSalt +// ZkSyncGuardianCompensation2025_2026.getDefault(vm) + ); + } + + /// @dev For running in tests + function runAll( + uint256 proposerPrivateKey, + uint256 guardianSafeDelegatePrivateKey, + uint256 agreementSalt, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns(bytes32[] memory) { + + address proposer = vm.addr(proposerPrivateKey); + + console2.log(""); + console2.log("=== ProposeAllGuardiansMetaVestDealScript ==="); + console2.log("Proposer: ", proposer); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log("VestingAllocationFactory: ", address(config.vestingAllocationFactory)); + console2.log("MetavesTController: ", address(config.controller)); + console2.log(""); + + bytes32[] memory agreementIds = new bytes32[](config.guardians.length); + + for (uint256 i = 0; i < config.guardians.length; i++) { + console2.log("Proposing to Guardian #%d", i + 1); + console2.log(" name:", config.guardians[i].partyInfo.name); + console2.log(" address:", config.guardians[i].partyInfo.evmAddress); + console2.log(""); + + agreementIds[i] = runSingle( + proposerPrivateKey, + guardianSafeDelegatePrivateKey, + config.guardians[i], + agreementSalt, + config + ); + } + + console2.log("Created:"); + for (uint256 i = 0; i < agreementIds.length; i++) { + console2.log(" Agreement ID #%d:", i + 1); + console2.logBytes32(agreementIds[i]); + console2.log(""); + } + + return agreementIds; + } + + function runSingle( + uint256 proposerPrivateKey, + uint256 guardianSafeDelegatePrivateKey, + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 agreementSalt, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public override returns(bytes32) { + return ProposeMetaVestDealScript.runSingle( + proposerPrivateKey, + guardianSafeDelegatePrivateKey, + guardianInfo, + agreementSalt, + config + ); + } + + function runSingle( + uint256 proposerPrivateKey, + uint256 guardianSafeDelegatePrivateKey, + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 agreementSalt, + BaseAllocation.Allocation memory allocation, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public override returns(bytes32) { + return ProposeMetaVestDealScript.runSingle( + proposerPrivateKey, + guardianSafeDelegatePrivateKey, + guardianInfo, + agreementSalt, + allocation, + config + ); + } +} diff --git a/scripts/proposeBorgResolution.s.sol b/scripts/proposeBorgResolution.s.sol new file mode 100644 index 0000000..9f51941 --- /dev/null +++ b/scripts/proposeBorgResolution.s.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {CyberAgreementUtils} from "cybercorps-contracts/test/libs/CyberAgreementUtils.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract ProposeBorgResolutionScript is SafeTxHelper, Script { + using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; + + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), + + // zkSync Era +// ZkSyncGuardianCompensation2024_2025.getDefault(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 new file mode 100644 index 0000000..eeb1705 --- /dev/null +++ b/scripts/proposeMetavestDeal.s.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {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; + + /// @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); + runSingle( + vm.envUint("DEPLOYER_PRIVATE_KEY"), // proposerPrivateKey + vm.envOr("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY", uint256(0)), // guardianSafeDelegatePrivateKey + defaultConfig.guardians[0], + vm.envUint("AGREEMENT_SALT"), // agreementSalt + defaultConfig + ); + } + + /// @dev For running in tests + function runSingle( + uint256 proposerPrivateKey, + uint256 guardianSafeDelegatePrivateKey, + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 agreementSalt, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns(bytes32) { + return runSingle( + proposerPrivateKey, + guardianSafeDelegatePrivateKey, + guardianInfo, + agreementSalt, + // Default guardian allocations + config.parseAllocation(), + config + ); + } + + /// @dev For running in tests + function runSingle( + uint256 proposerPrivateKey, + uint256 guardianSafeDelegatePrivateKey, + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory guardianInfo, + uint256 agreementSalt, + BaseAllocation.Allocation memory allocation, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns(bytes32) { + + address guardianSafeDelegate = guardianSafeDelegatePrivateKey != 0 + ? vm.addr(guardianSafeDelegatePrivateKey) + : address(0); + address proposer = vm.addr(proposerPrivateKey); + + console2.log(""); + console2.log("=== ProposeMetaVestDealScript ==="); + console2.log("Proposer: ", proposer); + console2.log("Guardian SAFE Delegate (if private key available): ", guardianSafeDelegate); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("ZK token: ", address(config.zkToken)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + 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[1] = guardianInfo.partyInfo.evmAddress; + + string[] memory globalValues = config.formatCompGlobalValues(vm, guardianInfo.partyInfo.evmAddress); + string[][] memory partyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues( + vm, + config.guardianSafeInfo, + guardianInfo.partyInfo + ); + + bytes32 expectedContractId = keccak256( + abi.encode( + guardianInfo.compTemplate.id, + agreementSalt, // salt, + globalValues, + parties + ) + ); + + (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) { + // Has valid signature, proceed to proposal + vm.startBroadcast(proposerPrivateKey); + + bytes32 contractId = config.controller.proposeAndSignDeal( + guardianInfo.compTemplate.id, + agreementSalt, + metavestController.metavestType.Vesting, + guardianInfo.partyInfo.evmAddress, + allocation, + config.milestones, + globalValues, + parties, + partyValues, + signature, + bytes32(0), // no secrets + block.timestamp + 365 days * 2 // 2 years after deployment + ); + + vm.stopBroadcast(); + + console2.log("Created:"); + console2.log(" Agreement ID:"); + console2.logBytes32(contractId); + console2.log(""); + + return contractId; + + } else { + // Does not have valid signature, prompt for offline signing + console2.log("Signature required: please sign the following EIP-712 typed data:"); + console2.log(" (can be signed with command `cast wallet sign --data ''`)"); + console2.log("==== JSON data start ===="); + console2.log(CyberAgreementUtils.formatAgreementTypedDataJson( + config.registry, + expectedContractId, + agreementUri, + guardianInfo.compTemplate.globalFields, + guardianInfo.compTemplate.partyFields, + globalValues, + partyValues[0] + )); + console2.log("==== JSON data end ===="); + + return bytes32(0); + } + } +} diff --git a/scripts/signDealAndCreateMetavest.s.sol b/scripts/signDealAndCreateMetavest.s.sol new file mode 100644 index 0000000..8b2a1c9 --- /dev/null +++ b/scripts/signDealAndCreateMetavest.s.sol @@ -0,0 +1,104 @@ +// 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 SignDealAndCreateMetavestScript is SafeTxHelper, Script { + using ZkSyncGuardianCompensation2024_2025 for ZkSyncGuardianCompensation2024_2025.Config; + + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + uint256 granteePrivateKey = vm.envUint("GRANTEE_PRIVATE_KEY"); + ZkSyncGuardianCompensation2024_2025.Config memory defaultConfig = ZkSyncGuardianCompensation2024_2025.getDefault(vm); + run( +// granteePrivateKey, +// 0x0000000000000000000000000000000000000000000000000000000000000000, // TODO TBD +// ZkSyncGuardianCompensation2024_2025.PartyInfo({ // TODO TBD +// name: "Alice", +// evmAddress: vm.addr(granteePrivateKey) +// }), +// ZkSyncGuardianCompensation2024_2025.getDefault(vm) + + // zkSync Sepolia + granteePrivateKey, + 0xd0d7610ca18b8a76a36c7e1241929641c06cce69ddc1161beebe69b72dae6cbf, + defaultConfig.guardians[0], + defaultConfig + ); + } + + /// @dev For running in tests + function run( + uint256 granteePrivateKey, + bytes32 agreementId, + ZkSyncGuardianCompensation2024_2025.GuardianCompInfo memory granteeInfo, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual returns(address) { + + address signer = vm.addr(granteePrivateKey); + + console2.log(""); + console2.log("=== SignDealAndCreateMetavestScript ==="); + console2.log("Signer: ", signer); + console2.log("Grantee: ", granteeInfo.partyInfo.evmAddress); + console2.log("Grantee Name: ", granteeInfo.partyInfo.name); + console2.log("Guardian Safe: ", address(config.guardianSafe)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log("MetavesTController: ", address(config.controller)); + console2.log("Agreement ID:"); + console2.logBytes32(agreementId); + console2.log(""); + + // Sign the deal and create MetaVesT + + (string memory agreementUri, ) = config.registry.templates(granteeInfo.compTemplate.id); + + string[] memory granteePartyValues = ZkSyncGuardianCompensation2024_2025.formatPartyValues(vm, granteeInfo.partyInfo); + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( + vm, + config.registry.DOMAIN_SEPARATOR(), + config.registry.SIGNATUREDATA_TYPEHASH(), + agreementId, + agreementUri, + granteeInfo.compTemplate.globalFields, + granteeInfo.compTemplate.partyFields, + config.formatCompGlobalValues(vm, granteeInfo.partyInfo.evmAddress), + granteePartyValues, + granteePrivateKey + ); + + vm.startBroadcast(granteePrivateKey); + + address metavest = config.controller.signDealAndCreateMetavest( + granteeInfo.partyInfo.evmAddress, + granteeInfo.partyInfo.evmAddress, + agreementId, + granteePartyValues, + signature, + "" // no secrets + ); + + vm.stopBroadcast(); + + console2.log("Created:"); + console2.log(" MetavesT: ", address(metavest)); + console2.log(""); + + return address(metavest); + } +} diff --git a/scripts/voidAgreement.s.sol b/scripts/voidAgreement.s.sol new file mode 100644 index 0000000..5f7f949 --- /dev/null +++ b/scripts/voidAgreement.s.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import {ZkSyncGuardianCompensation2024_2025} from "./lib/ZkSyncGuardianCompensation2024_2025.sol"; +import {ZkSyncGuardianCompensationSepolia2024_2025} from "./lib/ZkSyncGuardianCompensationSepolia2024_2025.sol"; +import {BaseAllocation} from "../src/BaseAllocation.sol"; +import {BorgAuth} from "cybercorps-contracts/src/libs/auth.sol"; +import {CyberAgreementRegistry} from "cybercorps-contracts/src/CyberAgreementRegistry.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ISafeProxyFactory, IGnosisSafe} from "../test/lib/safe.sol"; +import {IZkCappedMinterV2Factory} from "../src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol"; +import {SafeTxHelper} from "./lib/SafeTxHelper.sol"; +import {CyberAgreementUtils} from "./lib/CyberAgreementUtils.sol"; +import {Script} from "forge-std/Script.sol"; +import {VestingAllocationFactory} from "../src/VestingAllocationFactory.sol"; +import {ZkCappedMinterV2} from "zk-governance/l2-contracts/src/ZkCappedMinterV2.sol"; +import {ZkTokenV2} from "zk-governance/l2-contracts/src/ZkTokenV2.sol"; +import {console2} from "forge-std/console2.sol"; +import {metavestController} from "../src/MetaVesTController.sol"; + +contract VoidAgreementScript is SafeTxHelper, Script { + /// @dev For running from `forge script`. Provide the deployer private key through env var. + function run() public virtual { + run( + // zkSync Era +// vm.envUint("GRANTEE_PRIVATE_KEY"), +// ZkSyncGuardianCompensation2024_2025.getDefault(vm) + + // zkSync Sepolia + vm.envUint("GUARDIAN_BORG_DELEGATE_PRIVATE_KEY"), +// vm.envUint("GRANTEE_PRIVATE_KEY"), + 0xc519e4ce6730ae9167f4e080f47ac1544405756cf301f0c8316578fc90f95e0a, + ZkSyncGuardianCompensationSepolia2024_2025.getDefault(vm) + ); + } + + /// @dev For running in tests + function run( + uint256 signerPrivateKey, + bytes32 agreementId, + ZkSyncGuardianCompensation2024_2025.Config memory config + ) public virtual { + + address signer = vm.addr(signerPrivateKey); + + console2.log(""); + console2.log("=== VoidAgreementScript ==="); + console2.log("Signer: ", address(signer)); + console2.log("CyberAgreementRegistry: ", address(config.registry)); + console2.log("Agreement ID:"); + console2.logBytes32(agreementId); + console2.log(""); + + bytes memory signature = CyberAgreementUtils.signVoidTypedData( + vm, + config.registry.DOMAIN_SEPARATOR(), + config.registry.VOIDSIGNATUREDATA_TYPEHASH(), + agreementId, + signer, + signerPrivateKey + ); + + vm.startBroadcast(signerPrivateKey); + + config.registry.voidContractFor( + agreementId, + signer, + signature + ); + + vm.stopBroadcast(); + } +} diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index 6638c9b..bbd3ec8 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; -import "./interfaces/IZkCappedMinter.sol"; +import "./interfaces/zk-governance/IZkCappedMinterV2.sol"; /// @notice interface to a MetaLeX condition contract /// @dev see https://github.com/MetaLex-Tech/BORG-CORE/tree/main/src/libs/conditions @@ -17,6 +17,7 @@ interface IERC20M { interface IController { function authority() external view returns (address); + function mint(address recipient, uint256 amount) external; } /// @notice Solady's SafeTransferLib 'SafeTransfer()' and 'SafeTransferFrom()'; (https://github.com/Vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) @@ -136,9 +137,10 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ event MetaVesT_TransferabilityUpdated(address indexed grantee, bool isTransferable); event MetaVest_TransferRightsPending(address indexed grantee, address indexed pendingGrantee); event MetaVesT_TransferredRights(address indexed grantee, address transferee); + event MetaVesT_UpdatedRecipient(address indexed grantee, address newRecipient); event MetaVesT_UnlockRateUpdated(address indexed grantee, uint208 unlockRate); event MetaVesT_VestingRateUpdated(address indexed grantee, uint208 vestingRate); - event MetaVesT_Withdrawn(address indexed grantee, address indexed tokenAddress, uint256 amount); + event MetaVesT_Withdrawn(address indexed grantee, address indexed recipient, address indexed tokenAddress, uint256 amount); event MetaVesT_PriceUpdated(address indexed grantee, uint256 exercisePrice); event MetaVesT_RepurchaseAndWithdrawal(address indexed grantee, address indexed tokenAddress, uint256 withdrawalAmount, uint256 repurchaseAmount); event MetaVesT_Terminated(address indexed grantee, uint256 tokensRecovered); @@ -160,6 +162,7 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ address public grantee; // grantee of the tokens address public pendingGrantee; // address of the pending grantee + address public recipient; // recipient of the tokens bool transferable; // whether grantee can transfer their MetaVesT in whole Milestone[] public milestones; // array of Milestone structs Allocation public allocation; // struct containing vesting and unlocking details @@ -169,15 +172,16 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ GovType public govType; bool public terminated; uint256 public terminationTime; - address public ZkCappedMinterAddress; /// @notice BaseAllocation constructor /// @param _grantee: address of the grantee, cannot be a zero address /// @param _controller: address of the MetaVesTController contract - constructor(address _grantee, address _controller) { + constructor(address _grantee, address _recipient, address _controller) { // Controller can be 0 for an immuatable version, but grantee cannot if (_grantee == address(0)) revert MetaVesT_ZeroAddress(); + if (_recipient == address(0)) revert MetaVesT_ZeroAddress(); grantee = _grantee; + recipient = _recipient; controller = _controller; govType = GovType.vested; } @@ -227,10 +231,6 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ emit MetaVesT_UnlockRateUpdated(grantee, _newUnlockRate); } - function setZkCappedMinterAddress(address _ZkCappedMinterAddress) external onlyController { - ZkCappedMinterAddress = _ZkCappedMinterAddress; - } - /// @notice Sets the governing power type for the MetaVesT /// @param _govType: the type of governing power to be used function setGovVariables(GovType _govType) external onlyController { @@ -271,8 +271,7 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ if(terminated) revert MetaVesT_AlreadyTerminated(); if (_milestoneIndex >= milestones.length) revert MetaVesT_MilestoneIndexOutOfRange(); uint256 _milestoneAward = milestones[_milestoneIndex].milestoneAward; - //transfer the milestone award back to the authority, we check in the controller to ensure only uncompleted milestones can be removed - safeTransfer(allocation.tokenContract, getAuthority(), _milestoneAward); + // No need to transfer the milestone award back to the authority since the tokens are minted on-demand delete milestones[_milestoneIndex]; milestones[_milestoneIndex] = milestones[milestones.length - 1]; milestones.pop(); @@ -307,15 +306,24 @@ abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ pendingGrantee = address(0); } + /// @notice update the recipient to a new address + /// @dev onlyGrantee -- must be called by the grantee + /// @param _newRecipient - the address of the new recipient + function updateRecipient(address _newRecipient) external onlyGrantee { + if(_newRecipient == address(0)) revert MetaVesT_ZeroAddress(); + emit MetaVesT_UpdatedRecipient(grantee, _newRecipient); + recipient = _newRecipient; + } + /// @notice withdraws tokens from the VestingAllocation /// @dev onlyGrantee -- must be called by the grantee /// @param _amount - the amount of tokens to withdraw function withdraw(uint256 _amount) external nonReentrant onlyGrantee { if (_amount == 0) revert MetaVesT_ZeroAmount(); - if (_amount > getAmountWithdrawable() || _amount > IERC20M(allocation.tokenContract).balanceOf(address(this))) revert MetaVesT_MoreThanAvailable(); + if (_amount > getAmountWithdrawable()) revert MetaVesT_MoreThanAvailable(); tokensWithdrawn += _amount; - IZkCappedMinter(ZkCappedMinterAddress).mint(msg.sender, _amount); - emit MetaVesT_Withdrawn(msg.sender, allocation.tokenContract, _amount); + IController(controller).mint(recipient, _amount); + emit MetaVesT_Withdrawn(msg.sender, recipient, allocation.tokenContract, _amount); } /// @notice gets the details of the vest diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index 257ffe0..42c7b11 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -6,15 +6,17 @@ ************************************* */ -pragma solidity 0.8.24; +pragma solidity ^0.8.24; -//import "./MetaVesT.sol"; -import "./interfaces/IAllocationFactory.sol"; +import {ICyberAgreementRegistry} from "cybercorps-contracts/src/interfaces/ICyberAgreementRegistry.sol"; +import {UUPSUpgradeable} from "zk-governance/l2-contracts/lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol"; import "./BaseAllocation.sol"; -import "./RestrictedTokenAllocation.sol"; +//import "./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"; -import "../lib/zk-governance/l2-contracts/src/ZkCappedMinterFactory.sol"; //interface deleted @@ -25,27 +27,29 @@ import "../lib/zk-governance/l2-contracts/src/ZkCappedMinterFactory.sol"; * other permissioned functions, with some powers checked by the applicable 'dao' or subject to consent * by an applicable affected grantee or a majority-in-governing power of similar token grantees **/ -contract metavestController is SafeTransferLib { +contract metavestController is UUPSUpgradeable, SafeTransferLib { using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.Bytes32Set; /// @dev opinionated time limit for a MetaVesT amendment, one calendar week in seconds uint256 internal constant AMENDMENT_TIME_LIMIT = 604800; uint256 internal constant ARRAY_LENGTH_LIMIT = 20; - mapping(bytes32 => EnumerableSet.AddressSet) private sets; EnumerableSet.Bytes32Set private setNames; address public authority; address public dao; + address public registry; address public vestingFactory; - address public tokenOptionFactory; - address public restrictedTokenFactory; - address public zkCappedMinterFactory; +// address public tokenOptionFactory; +// address public restrictedTokenFactory; + address public zkCappedMinter; address public ZkTokenAddress; address internal _pendingAuthority; address internal _pendingDao; - uint256 public metavestCounter; + + // Simple indexer for UX + bytes32[] public dealIds; struct AmendmentProposal { bool isPending; @@ -64,6 +68,15 @@ contract metavestController is SafeTransferLib { mapping(address => uint256) voterPower; } + struct DealData { + bytes32 agreementId; + metavestType _metavestType; + address grantee; + BaseAllocation.Allocation allocation; + BaseAllocation.Milestone[] milestones; + address metavest; + } + enum metavestType { Vesting, TokenOption, RestrictedTokenAward } /// @notice maps a function's signature to a Condition contract address @@ -80,6 +93,15 @@ contract metavestController is SafeTransferLib { mapping(bytes32 => bool) public setMajorityVoteActive; + /// @notice granteeId => granteeData + mapping(bytes32 => DealData) public deals; + + /// @notice Maps agreement IDs to arrays of counter party values for closed deals. + mapping(bytes32 => string[]) public counterPartyValues; + + /// @notice Map MetaVesT contract address to its corresponding agreement ID + mapping(address => bytes32) public metavestAgreementIds; + /// /// EVENTS /// @@ -95,8 +117,22 @@ contract metavestController is SafeTransferLib { event MetaVesTController_SetRemoved(string indexed set); event MetaVesTController_AddressAddedToSet(string set, address indexed grantee); event MetaVesTController_AddressRemovedFromSet(string set, address indexed grantee); - event MetaVesTController_MetaVestCreated(address indexed metavest); - event MetaVesTController_ZKCapMinterCreated(address indexed zkCapMinter); + event MetaVesTController_ZkCappedMinterUpdated(address zkCappedMinter); + event MetaVesTController_DealProposed( + bytes32 indexed agreementId, + address indexed grantee, + metavestType metavestType, + BaseAllocation.Allocation allocation, + BaseAllocation.Milestone[] milestones, + bool hasSecret, + address registry + ); + event MetaVesTController_DealFinalizedAndMetaVestCreated( + bytes32 indexed agreementId, + address indexed recipient, + address metavest + ); + event MetaVesTController_Minted(address indexed metavest, address indexed recipient, address zkCappedMinter, uint256 amount); /// /// ERRORS @@ -131,6 +167,13 @@ contract metavestController is SafeTransferLib { error MetaVestController_MetaVestNotInSet(); error MetaVesTController_SetAlreadyExists(); error MetaVesTController_StringTooLong(); + error MetaVesTController_TypeNotSupported(metavestType _type); + error MetaVesTController_DealAlreadyFinalized(); + error MetaVesTController_DealVoided(); + error MetaVesTController_CounterPartyNotFound(); + error MetaVesTController_PartyValuesLengthMismatch(); + error MetaVesTController_CounterPartyValueMismatch(); + error MetaVesTController_UnauthorizedToMint(); /// /// FUNCTIONS @@ -180,14 +223,22 @@ contract metavestController is SafeTransferLib { /// @param _authority address of the authority who can call the functions in this contract and update each MetaVesT in '_metavest', such as a BORG /// @param _dao DAO governance contract address which exercises control over ability of 'authority' to call certain functions via imposing /// conditions through 'updateFunctionCondition'. - constructor(address _authority, address _dao, address _vestingFactory, address _tokenOptionFactory, address _restrictedTokenFactory, address _zkCappedMinterFactory, address _ZkTokenAddress) { + function initialize( + address _authority, + address _dao, + address _registry, + address _vestingFactory +// address _tokenOptionFactory, +// address _restrictedTokenFactory + ) public initializer { + __UUPSUpgradeable_init(); + if (_authority == address(0)) revert MetaVesTController_ZeroAddress(); authority = _authority; + registry = _registry; vestingFactory = _vestingFactory; - tokenOptionFactory = _tokenOptionFactory; - restrictedTokenFactory = _restrictedTokenFactory; - zkCappedMinterFactory = _zkCappedMinterFactory; - ZkTokenAddress = _ZkTokenAddress; +// tokenOptionFactory = _tokenOptionFactory; +// restrictedTokenFactory = _restrictedTokenFactory; dao = _dao; } @@ -231,47 +282,133 @@ contract metavestController is SafeTransferLib { emit MetaVesTController_ConditionUpdated(_condition, _functionSig); } - function createMetavest(metavestType _type, address _grantee, BaseAllocation.Allocation calldata _allocation, BaseAllocation.Milestone[] calldata _milestones, uint256 _exercisePrice, address _paymentToken, uint256 _shortStopDuration, uint256 _longStopDate) external conditionCheck returns (address) - { - - address newMetavest; - if(_type == metavestType.Vesting) + // It can be called by anyone but must have DAO's or delegate's signature + function proposeAndSignDeal( + bytes32 templateId, + uint256 salt, + metavestType _metavestType, + address grantee, + BaseAllocation.Allocation calldata allocation, + BaseAllocation.Milestone[] calldata milestones, + string[] memory globalValues, + address[] memory parties, + string[][] memory partyValues, + bytes calldata signature, + bytes32 secretHash, + uint256 expiry + ) external returns (bytes32) { + + bytes32 agreementId = ICyberAgreementRegistry(registry).createContract( + templateId, + salt, + globalValues, + parties, + partyValues, + secretHash, + address(this), + expiry + ); + + if (partyValues.length < 2) revert MetaVesTController_CounterPartyNotFound(); + if (partyValues[1].length != partyValues[0].length) revert MetaVesTController_PartyValuesLengthMismatch(); + counterPartyValues[agreementId] = partyValues[1]; + + ICyberAgreementRegistry(registry).signContractFor( + authority, // First party (grantor) should always be the authority + agreementId, + partyValues[0], + signature, + false, // Not meant for anyone else other than the signer + "" // Signer == proposer, no secret needed + ); + + deals[agreementId] = DealData({ + agreementId: agreementId, + _metavestType: _metavestType, + grantee: grantee, + allocation: allocation, + milestones: milestones, + metavest: address(0) // Not deployed yet + }); + dealIds.push(agreementId); + + emit MetaVesTController_DealProposed( + agreementId, grantee, _metavestType, allocation, milestones, + secretHash > 0, + registry + ); + return agreementId; + } + + function signDealAndCreateMetavest( + address grantee, + address recipient, + bytes32 agreementId, + string[] memory partyValues, + bytes memory signature, + string memory secret + ) external conditionCheck returns (address) { + // Finalize agreement + + if(ICyberAgreementRegistry(registry).isVoided(agreementId)) revert MetaVesTController_DealVoided(); + if(ICyberAgreementRegistry(registry).isFinalized(agreementId)) revert MetaVesTController_DealAlreadyFinalized(); + + string[] storage counterPartyCheck = counterPartyValues[agreementId]; + if (keccak256(abi.encode(counterPartyCheck)) != keccak256(abi.encode(partyValues))) revert MetaVesTController_CounterPartyValueMismatch(); + + ICyberAgreementRegistry(registry).signContractFor(grantee, agreementId, partyValues, signature, false, secret); + + ICyberAgreementRegistry(registry).finalizeContract(agreementId); + + // Create and provision MetaVesT + + address newMetavest = _createMetavest(agreementId, recipient); + + emit MetaVesTController_DealFinalizedAndMetaVestCreated(agreementId, recipient, newMetavest); + + return newMetavest; + } + + function _createMetavest(bytes32 agreementId, address recipient) internal returns (address) { + DealData storage deal = deals[agreementId]; + + if(deal._metavestType == metavestType.Vesting) { - newMetavest = createVestingAllocation(_grantee, _allocation, _milestones); + deal.metavest = createVestingAllocation(deal.grantee, recipient, deal.allocation, deal.milestones); } - else if(_type == metavestType.TokenOption) + else if(deal._metavestType == metavestType.TokenOption) { - newMetavest = createTokenOptionAllocation(_grantee, _exercisePrice, _paymentToken, _shortStopDuration, _allocation, _milestones); + // TODO will be supported in the next stage + revert MetaVesTController_TypeNotSupported(deal._metavestType); } - else if(_type == metavestType.RestrictedTokenAward) + else if(deal._metavestType == metavestType.RestrictedTokenAward) { - newMetavest = createRestrictedTokenAward(_grantee, _exercisePrice, _paymentToken, _shortStopDuration, _allocation, _milestones); + // TODO will be supported in the next stage + revert MetaVesTController_TypeNotSupported(deal._metavestType); } else { revert MetaVesTController_IncorrectMetaVesTType(); } - uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); - uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; - address zkCappedMinterDeployAddress = ZkCappedMinterFactory(zkCappedMinterFactory).createCappedMinter(IMintableAndDelegatable(ZkTokenAddress), newMetavest, _total, metavestCounter++); - BaseAllocation(newMetavest).setZkCappedMinterAddress(zkCappedMinterDeployAddress); - emit MetaVesTController_MetaVestCreated(newMetavest); - emit MetaVesTController_ZKCapMinterCreated(zkCappedMinterDeployAddress); - return newMetavest; + // Grant MetaVesT minter privilege + metavestAgreementIds[deal.metavest] = agreementId; + + return deal.metavest; } function validateInputParameters( address _grantee, + address _recipient, address _paymentToken, uint256 _exercisePrice, - VestingAllocation.Allocation calldata _allocation + VestingAllocation.Allocation memory _allocation ) internal pure { - if (_grantee == address(0) || _allocation.tokenContract == address(0) || _paymentToken == address(0) || _exercisePrice == 0) + if (_grantee == address(0) || _recipient == address(0) || _allocation.tokenContract == address(0) || _paymentToken == address(0) || _exercisePrice == 0) revert MetaVesTController_ZeroAddress(); } - function validateAllocation(VestingAllocation.Allocation calldata _allocation) internal pure { + function validateAllocation(VestingAllocation.Allocation memory _allocation) internal pure { if ( _allocation.vestingCliffCredit > _allocation.tokenStreamTotal || _allocation.unlockingCliffCredit > _allocation.tokenStreamTotal @@ -279,7 +416,7 @@ contract metavestController is SafeTransferLib { } function validateAndCalculateMilestones( - VestingAllocation.Milestone[] calldata _milestones + VestingAllocation.Milestone[] memory _milestones ) internal pure returns (uint256 _milestoneTotal) { if (_milestones.length != 0) { if (_milestones.length > ARRAY_LENGTH_LIMIT) revert MetaVesTController_LengthMismatch(); @@ -292,67 +429,60 @@ contract metavestController is SafeTransferLib { } } - function validateTokenApprovalAndBalance(address tokenContract, uint256 total) internal view { - if ( - IERC20M(tokenContract).allowance(authority, address(this)) < total || - IERC20M(tokenContract).balanceOf(authority) < total - ) revert MetaVesTController_AmountNotApprovedForTransferFrom(); - } - - function createAndInitializeTokenOptionAllocation( - address _grantee, - address _paymentToken, - uint256 _exercisePrice, - uint256 _shortStopDuration, - VestingAllocation.Allocation calldata _allocation, - VestingAllocation.Milestone[] calldata _milestones - ) internal returns (address) { - return IAllocationFactory(tokenOptionFactory).createAllocation( - IAllocationFactory.AllocationType.TokenOption, - _grantee, - address(this), - _allocation, - _milestones, - _paymentToken, - _exercisePrice, - _shortStopDuration - ); - } - - function createAndInitializeRestrictedTokenAward( - address _grantee, - address _paymentToken, - uint256 _repurchasePrice, - uint256 _shortStopDuration, - VestingAllocation.Allocation calldata _allocation, - VestingAllocation.Milestone[] calldata _milestones - ) internal returns (address) { - return IAllocationFactory(restrictedTokenFactory).createAllocation( - IAllocationFactory.AllocationType.RestrictedToken, - _grantee, - address(this), - _allocation, - _milestones, - _paymentToken, - _repurchasePrice, - _shortStopDuration - ); - } - - - function createVestingAllocation(address _grantee, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal returns (address){ +// function createAndInitializeTokenOptionAllocation( +// address _grantee, +// address _paymentToken, +// uint256 _exercisePrice, +// uint256 _shortStopDuration, +// VestingAllocation.Allocation calldata _allocation, +// VestingAllocation.Milestone[] calldata _milestones +// ) internal returns (address) { +// return IAllocationFactory(tokenOptionFactory).createAllocation( +// IAllocationFactory.AllocationType.TokenOption, +// _grantee, +// address(this), +// _allocation, +// _milestones, +// _paymentToken, +// _exercisePrice, +// _shortStopDuration +// ); +// } +// +// function createAndInitializeRestrictedTokenAward( +// address _grantee, +// address _paymentToken, +// uint256 _repurchasePrice, +// uint256 _shortStopDuration, +// VestingAllocation.Allocation calldata _allocation, +// VestingAllocation.Milestone[] calldata _milestones +// ) internal returns (address) { +// return IAllocationFactory(restrictedTokenFactory).createAllocation( +// IAllocationFactory.AllocationType.RestrictedToken, +// _grantee, +// address(this), +// _allocation, +// _milestones, +// _paymentToken, +// _repurchasePrice, +// _shortStopDuration +// ); +// } + + + function createVestingAllocation(address _grantee, address _recipient, VestingAllocation.Allocation memory _allocation, VestingAllocation.Milestone[] memory _milestones) internal returns (address){ //hard code values not to trigger the failure for the 2 parameters that don't matter for this type of allocation - validateInputParameters(_grantee, address(this), 1, _allocation); + validateInputParameters(_grantee, _recipient, address(this), 1, _allocation); validateAllocation(_allocation); uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; if (_total == 0) revert MetaVesTController_ZeroAmount(); - //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); address vestingAllocation = IAllocationFactory(vestingFactory).createAllocation( IAllocationFactory.AllocationType.Vesting, _grantee, + _recipient, address(this), _allocation, _milestones, @@ -360,55 +490,64 @@ contract metavestController is SafeTransferLib { 0, 0 ); - //safeTransferFrom(_allocation.tokenContract, authority, vestingAllocation, _total); return vestingAllocation; } - function createTokenOptionAllocation(address _grantee, uint256 _exercisePrice, address _paymentToken, uint256 _shortStopDuration, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal conditionCheck returns (address) { - - validateInputParameters(_grantee, _paymentToken, _exercisePrice, _allocation); - validateAllocation(_allocation); - uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); - - uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; - if (_total == 0) revert MetaVesTController_ZeroAmount(); - //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); - - address tokenOptionAllocation = createAndInitializeTokenOptionAllocation( - _grantee, - _paymentToken, - _exercisePrice, - _shortStopDuration, - _allocation, - _milestones - ); - - //safeTransferFrom(_allocation.tokenContract, authority, tokenOptionAllocation, _total); - return tokenOptionAllocation; +// function createTokenOptionAllocation(address _grantee, uint256 _exercisePrice, address _paymentToken, uint256 _shortStopDuration, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal conditionCheck returns (address) { +// +// validateInputParameters(_grantee, _paymentToken, _exercisePrice, _allocation); +// validateAllocation(_allocation); +// uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); +// +// uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; +// if (_total == 0) revert MetaVesTController_ZeroAmount(); +// //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); +// +// address tokenOptionAllocation = createAndInitializeTokenOptionAllocation( +// _grantee, +// _paymentToken, +// _exercisePrice, +// _shortStopDuration, +// _allocation, +// _milestones +// ); +// +// //safeTransferFrom(_allocation.tokenContract, authority, tokenOptionAllocation, _total); +// return tokenOptionAllocation; +// } +// +// function createRestrictedTokenAward(address _grantee, uint256 _repurchasePrice, address _paymentToken, uint256 _shortStopDuration, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal conditionCheck returns (address){ +// validateInputParameters(_grantee, _paymentToken, _repurchasePrice, _allocation); +// validateAllocation(_allocation); +// uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); +// +// uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; +// if (_total == 0) revert MetaVesTController_ZeroAmount(); +// //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); +// +// address restrictedTokenAward = createAndInitializeRestrictedTokenAward( +// _grantee, +// _paymentToken, +// _repurchasePrice, +// _shortStopDuration, +// _allocation, +// _milestones +// ); +// +// //safeTransferFrom(_allocation.tokenContract, authority, restrictedTokenAward, _total); +// return restrictedTokenAward; +// } + + function mint(address recipient, uint256 amount) external { + bytes32 agreementId = metavestAgreementIds[msg.sender]; + if (agreementId == bytes32(0)) { + revert MetaVesTController_UnauthorizedToMint(); } - function createRestrictedTokenAward(address _grantee, uint256 _repurchasePrice, address _paymentToken, uint256 _shortStopDuration, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal conditionCheck returns (address){ - validateInputParameters(_grantee, _paymentToken, _repurchasePrice, _allocation); - validateAllocation(_allocation); - uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); - - uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; - if (_total == 0) revert MetaVesTController_ZeroAmount(); - //validateTokenApprovalAndBalance(_allocation.tokenContract, _total); - - address restrictedTokenAward = createAndInitializeRestrictedTokenAward( - _grantee, - _paymentToken, - _repurchasePrice, - _shortStopDuration, - _allocation, - _milestones - ); - - //safeTransferFrom(_allocation.tokenContract, authority, restrictedTokenAward, _total); - return restrictedTokenAward; - } + 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(); @@ -470,13 +609,9 @@ contract metavestController is SafeTransferLib { if (_milestone.milestoneAward == 0) revert MetaVesTController_ZeroAmount(); if (_milestone.conditionContracts.length > ARRAY_LENGTH_LIMIT) revert MetaVesTController_LengthMismatch(); if (_milestone.complete == true) revert MetaVesTController_MilestoneIndexCompletedOrDoesNotExist(); - if ( - IERC20M(_tokenContract).allowance(msg.sender, address(this)) < _milestone.milestoneAward || - IERC20M(_tokenContract).balanceOf(msg.sender) < _milestone.milestoneAward - ) revert MetaVesT_AmountNotApprovedForTransferFrom(); - // send the new milestoneAward to 'metavest' - safeTransferFrom(_tokenContract, msg.sender, _grant, _milestone.milestoneAward); + // No need to allocate token right now since they are minted on-demand + BaseAllocation(_grant).addMilestone(_milestone); } @@ -763,4 +898,41 @@ contract metavestController is 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]; + } + + // Simple indexer for UX + + function getNumberOfDeals() public view returns(uint256) { + return dealIds.length; + } + + function getDealId(uint256 index) public view returns(bytes32) { + return dealIds[index]; + } + + function _authorizeUpgrade( + address newImplementation + ) internal virtual override onlyAuthority {} + + // Avoid "Address: low-level delegate call failed" due to `UUPSUpgradeable.upgradeToAndCall()` runs with `forceCall=true` + fallback() external {} } diff --git a/src/MetaVesTFactory.sol b/src/MetaVesTFactory.sol deleted file mode 100644 index 364439b..0000000 --- a/src/MetaVesTFactory.sol +++ /dev/null @@ -1,50 +0,0 @@ -//SPDX-License-Identifier: AGPL-3.0-only - -pragma solidity 0.8.24; - -/* -************************************ - MetaVesTFactory - ************************************ - */ - -import "./MetaVesTController.sol"; -interface IMetaVesTController { - function metavest() external view returns (address); -} - -/** - * @title MetaVesT Factory - * - * @notice Deploy a new instance of MetaVesTController, which in turn deploys a new MetaVesT it controls - * - **/ -contract MetaVesTFactory { - event MetaVesT_Deployment( - address newMetaVesT, - address authority, - address controller, - address dao, - address vestingAllocationFactory, - address tokenOptionFactory, - address restrictedTokenFactory - ); - - error MetaVesTFactory_ZeroAddress(); - - constructor() { } - - /// @notice constructs a MetaVesT framework specifying authority address, DAO staking/voting contract address - /// each individual grantee's MetaVesT will be initiated in the newly deployed MetaVesT contract, and deployed MetaVesTs are amendable by 'authority' via the controller contract - /// @dev conditionals are contained in the deployed MetaVesT, which is deployed in the MetaVesTController's constructor(); the MetaVesT within the MetaVesTController is immutable, but the 'authority' which has access control within the controller may replace itself - /// @param _authority: address which initiates and may update each MetaVesT, such as a BORG or DAO - /// @param _dao: contract address which token may be staked and used for voting, typically a DAO pool, governor, staking address. Submit address(0) for no such functionality. - function deployMetavestAndController(address _authority, address _dao, address _vestingAllocationFactory, address _tokenOptionFactory, address _restrictedTokenFactory, address _ZkCappedMinterFactory, address _zkTokenAddress ) external returns(address) { - if(_vestingAllocationFactory == address(0) || _tokenOptionFactory == address(0) || _restrictedTokenFactory == address(0)) - revert MetaVesTFactory_ZeroAddress(); - metavestController _controller = new metavestController(_authority, _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory, _ZkCappedMinterFactory, _zkTokenAddress); - emit MetaVesT_Deployment(address(0), _authority, address(_controller), _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory); - return address(_controller); - } - -} diff --git a/src/RestrictedTokenAllocation.sol b/src/RestrictedTokenAllocation.sol deleted file mode 100644 index 64fa8bd..0000000 --- a/src/RestrictedTokenAllocation.sol +++ /dev/null @@ -1,231 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -import "./BaseAllocation.sol"; - -pragma solidity 0.8.24; - -contract RestrictedTokenAward is BaseAllocation { - - /// @notice address of payment token used for token option exercises or restricted token repurchases - address public immutable paymentToken; - uint256 public shortStopDuration; - uint256 public shortStopDate; - uint256 public repurchasePrice; - uint256 public tokensRepurchased; - uint256 public tokensRepurchasedWithdrawn; - - /// @notice Constructor to deploy a new RestrictedTokenAward - /// @param _grantee - address of the grantee - /// @param _controller - address of the controller - /// @param _paymentToken - address of the payment token - /// @param _repurchasePrice - price at which the restricted tokens can be repurchased in vesting token decimals but only up to payment decimal precision - /// @param _shortStopDuration - duration after termination during which restricted tokens can be repurchased - /// @param _allocation - allocation details as an Allocation struct - /// @param _milestones - milestones with their conditions and awards - constructor ( - address _grantee, - address _controller, - address _paymentToken, - uint256 _repurchasePrice, - uint256 _shortStopDuration, - Allocation memory _allocation, - Milestone[] memory _milestones - ) BaseAllocation( - _grantee, - _controller - ) { - //perform input validation - if (_allocation.tokenContract == address(0)) revert MetaVesT_ZeroAddress(); - if (_allocation.tokenStreamTotal == 0) revert MetaVesT_ZeroAmount(); - if (_allocation.vestingRate > 1000*1e18 || _allocation.unlockRate > 1000*1e18) revert MetaVesT_RateTooHigh(); - - //set vesting allocation variables - allocation.tokenContract = _allocation.tokenContract; - allocation.tokenStreamTotal = _allocation.tokenStreamTotal; - allocation.vestingCliffCredit = _allocation.vestingCliffCredit; - allocation.unlockingCliffCredit = _allocation.unlockingCliffCredit; - allocation.vestingRate = _allocation.vestingRate; - allocation.vestingStartTime = _allocation.vestingStartTime; - allocation.unlockRate = _allocation.unlockRate; - allocation.unlockStartTime = _allocation.unlockStartTime; - - // set token option variables - repurchasePrice = _repurchasePrice; - shortStopDuration = _shortStopDuration; - - paymentToken = _paymentToken; - - // manually copy milestones - for (uint256 i; i < _milestones.length; ++i) { - milestones.push(_milestones[i]); - } - } - - /// @notice returns the vesting type for RestrictedTokenAward - /// @return uint256 type 3 - function getVestingType() external pure override returns (uint256) { - return 3; - } - - /// @notice returns the governing power for RestrictedTokenAward based on the govType - /// @return uint256 governingPower for this RestrictedTokenAward contract - function getGoverningPower() external view override returns (uint256) { - uint256 governingPower; - if(govType==GovType.all) - { - uint256 totalMilestoneAward = 0; - for(uint256 i; i < milestones.length; ++i) - { - totalMilestoneAward += milestones[i].milestoneAward; - } - governingPower = (allocation.tokenStreamTotal + totalMilestoneAward) - tokensWithdrawn; - } - else if(govType==GovType.vested) - governingPower = getVestedTokenAmount() - tokensWithdrawn; - else - governingPower = _min(getVestedTokenAmount(), getUnlockedTokenAmount()) - tokensWithdrawn; - - return governingPower; - } - - /// @notice updates the short stop time of the vesting contract - /// @dev onlyController -- must be called from the metavest controller - /// @param _shortStopTime - new short stop time to be set in seconds - function updateStopTimes(uint48 _shortStopTime) external override onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - shortStopDuration = _shortStopTime; - emit MetaVesT_StopTimesUpdated(grantee, _shortStopTime); - } - - /// @notice updates the exercise price - /// @dev onlyController -- must be called from the metavest controller - /// @param _newPrice - the new exercise price in vesting token decimals but only up to payment decimal precision - function updatePrice(uint256 _newPrice) external onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - repurchasePrice = _newPrice; - emit MetaVesT_PriceUpdated(grantee, _newPrice); - } - - /// @notice gets the payment amount for a given amount of tokens - /// @param _amount - the amount of tokens to calculate the payment amount in the vesting token decimals - /// @return paymentAmount - the payment amount for the given token amount in the payment token decimals - function getPaymentAmount(uint256 _amount) public view returns (uint256) { - uint8 paymentDecimals = IERC20M(paymentToken).decimals(); - uint8 repurchaseTokenDecimals = IERC20M(allocation.tokenContract).decimals(); - - // Calculate paymentAmount - uint256 paymentAmount; - paymentAmount = _amount * repurchasePrice / (10**repurchaseTokenDecimals); - - //scale paymentAmount to payment token decimals - if(paymentDecimals getAmountRepurchasable()) revert MetaVesT_MoreThanAvailable(); - if(block.timestampIERC20M(allocation.tokenContract).balanceOf(address(this))) - repurchaseAmount = IERC20M(allocation.tokenContract).balanceOf(address(this)); - return repurchaseAmount; - } - - /// @notice returns the amount of tokens that are vested - /// @return uint256 amount of tokens that are vested - function getVestedTokenAmount() public view returns (uint256) { - if(block.timestampallocation.tokenStreamTotal) - _tokensVested = allocation.tokenStreamTotal; - return _tokensVested += milestoneAwardTotal; - } - - /// @notice returns the amount of tokens that are unlocked - /// @return uint256 amount of tokens that are unlocked - function getUnlockedTokenAmount() public view returns (uint256) { - if(block.timestampallocation.tokenStreamTotal + milestoneAwardTotal) - _tokensUnlocked = allocation.tokenStreamTotal + milestoneAwardTotal; - - return _tokensUnlocked += milestoneUnlockedTotal; - } - - /// @notice returns the amount of tokens that can be withdrawn - /// @return uint256 amount of tokens that can be withdrawn - function getAmountWithdrawable() public view override returns (uint256) { - uint256 _tokensVested = getVestedTokenAmount(); - uint256 _tokensUnlocked = getUnlockedTokenAmount(); - uint256 withdrawableAmount = _min(_tokensVested, _tokensUnlocked); - if(withdrawableAmount>tokensWithdrawn) - return withdrawableAmount - tokensWithdrawn; - else - return 0; - } - -} diff --git a/src/RestrictedTokenFactory.sol b/src/RestrictedTokenFactory.sol deleted file mode 100644 index 8db8f87..0000000 --- a/src/RestrictedTokenFactory.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.24; - -import "./RestrictedTokenAllocation.sol"; -import "./interfaces/IAllocationFactory.sol"; - -contract RestrictedTokenFactory is IAllocationFactory { - - function createAllocation( - AllocationType _allocationType, - address _grantee, - address _controller, - RestrictedTokenAward.Allocation memory _allocation, - RestrictedTokenAward.Milestone[] memory _milestones, - address _paymentToken, - uint256 _exercisePrice, - uint256 _shortStopDuration - ) external returns (address) { - if (_allocationType == AllocationType.RestrictedToken) { - return address(new RestrictedTokenAward(_grantee, _controller, _paymentToken, _exercisePrice, _shortStopDuration, _allocation, _milestones)); - } else { - revert("AllocationFactory: invalid allocation type"); - } - } -} diff --git a/src/TokenOptionAllocation.sol b/src/TokenOptionAllocation.sol deleted file mode 100644 index 46c37a3..0000000 --- a/src/TokenOptionAllocation.sol +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -import "./BaseAllocation.sol"; - -pragma solidity 0.8.24; - -contract TokenOptionAllocation is BaseAllocation { - - /// @notice address of payment token used for token option exercises or restricted token repurchases - address public immutable paymentToken; - uint256 public tokensExercised; - uint256 public exercisePrice; - uint256 public shortStopDuration; - uint256 public shortStopTime; - - event MetaVesT_TokenOptionExercised(address indexed _grantee, uint256 _tokensToExercise, uint256 _paymentAmount); - - /// @notice Constructor to create a TokenOptionAllocation - /// @param _grantee - address of the grantee - /// @param _controller - address of the controller - /// @param _paymentToken - address of the payment token - /// @param _exercisePrice - price of the token option exercise in vesting token decimals but only up to payment decimal precision - /// @param _shortStopDuration - duration of the short stop - /// @param _allocation - allocation details as an Allocation struct - /// @param _milestones - milestones with conditions and awards - constructor ( - address _grantee, - address _controller, - address _paymentToken, - uint256 _exercisePrice, - uint256 _shortStopDuration, - Allocation memory _allocation, - Milestone[] memory _milestones - ) BaseAllocation( - _grantee, - _controller - ) { - //perform input validation - if (_allocation.tokenContract == address(0)) revert MetaVesT_ZeroAddress(); - if (_allocation.tokenStreamTotal == 0) revert MetaVesT_ZeroAmount(); - if (_allocation.vestingRate > 1000*1e18 || _allocation.unlockRate > 1000*1e18) revert MetaVesT_RateTooHigh(); - - //set vesting allocation variables - allocation.tokenContract = _allocation.tokenContract; - allocation.tokenStreamTotal = _allocation.tokenStreamTotal; - allocation.vestingCliffCredit = _allocation.vestingCliffCredit; - allocation.unlockingCliffCredit = _allocation.unlockingCliffCredit; - allocation.vestingRate = _allocation.vestingRate; - allocation.vestingStartTime = _allocation.vestingStartTime; - allocation.unlockRate = _allocation.unlockRate; - allocation.unlockStartTime = _allocation.unlockStartTime; - - // set token option variables - exercisePrice = _exercisePrice; - shortStopDuration = _shortStopDuration; - - paymentToken = _paymentToken; - - // manually copy milestones - for (uint256 i; i < _milestones.length; ++i) { - milestones.push(_milestones[i]); - } - } - - /// @notice returns the contract vesting type 2 for TokenOptionAllocation - /// @return 2 - function getVestingType() external pure override returns (uint256) { - return 2; - } - - /// @notice returns the governing power of the TokenOptionAllocation - /// @return governingPower - the governing power of the TokenOptionAllocation based on the governance setting - function getGoverningPower() external view override returns (uint256) { - uint256 governingPower; - if(govType==GovType.all) - { - uint256 totalMilestoneAward = 0; - for(uint256 i; i < milestones.length; ++i) - { - totalMilestoneAward += milestones[i].milestoneAward; - } - governingPower = (allocation.tokenStreamTotal + totalMilestoneAward) - tokensWithdrawn; - } - else if(govType==GovType.vested) - governingPower = tokensExercised - tokensWithdrawn; - else - governingPower = _min(tokensExercised, getUnlockedTokenAmount()) - tokensWithdrawn; - - return governingPower; - } - - /// @notice updates the short stop time of the TokenOptionAllocation - /// @dev onlyController -- must be called from the metavest controller - /// @param _shortStopTime - the new short stop time - function updateStopTimes(uint48 _shortStopTime) external override onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - shortStopDuration = _shortStopTime; - emit MetaVesT_StopTimesUpdated(grantee, _shortStopTime); - } - - /// @notice updates the exercise price - /// @dev onlyController -- must be called from the metavest controller - /// @param _newPrice - the new exercise price in vesting token decimals but only up to payment decimal precision - function updatePrice(uint256 _newPrice) external onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - exercisePrice = _newPrice; - emit MetaVesT_PriceUpdated(grantee, _newPrice); - } - - /// @notice gets the payment amount for a given amount of tokens - /// @param _amount - the amount of tokens to calculate the payment amount in the vesting token decimals - /// @return paymentAmount - the payment amount for the given token amount in the payment token decimals - function getPaymentAmount(uint256 _amount) public view returns (uint256) { - uint8 paymentDecimals = IERC20M(paymentToken).decimals(); - uint8 exerciseTokenDecimals = IERC20M(allocation.tokenContract).decimals(); - - // Calculate paymentAmount - uint256 paymentAmount; - paymentAmount = _amount * exercisePrice / (10**exerciseTokenDecimals); - - //scale paymentAmount to payment token decimals - if(paymentDecimalsshortStopTime && terminated) revert MetaVest_ShortStopDatePassed(); - if (_tokensToExercise == 0) revert MetaVesT_ZeroAmount(); - if (_tokensToExercise > getAmountExercisable()) revert MetaVesT_MoreThanAvailable(); - - // Calculate paymentAmount - uint256 paymentAmount = getPaymentAmount(_tokensToExercise); - if(paymentAmount == 0) revert MetaVesT_TooSmallAmount(); - - safeTransferFrom(paymentToken, msg.sender, getAuthority(), paymentAmount); - tokensExercised += _tokensToExercise; - emit MetaVesT_TokenOptionExercised(msg.sender, _tokensToExercise, paymentAmount); - } - - /// @notice Allows the controller to terminate the TokenOptionAllocation - /// @dev onlyController -- must be called from the metavest controller - function terminate() external override onlyController nonReentrant { - if(terminated) revert MetaVesT_AlreadyTerminated(); - - uint256 milestonesAllocation = 0; - for (uint256 i; i < milestones.length; ++i) { - milestonesAllocation += milestones[i].milestoneAward; - } - uint256 tokensToRecover = allocation.tokenStreamTotal + milestonesAllocation - getAmountExercisable() - tokensExercised; - terminationTime = block.timestamp; - shortStopTime = block.timestamp + shortStopDuration; - safeTransfer(allocation.tokenContract, getAuthority(), tokensToRecover); - terminated = true; - emit MetaVesT_Terminated(grantee, tokensToRecover); - } - - /// @notice recovers any forfeited tokens after the short stop time - /// @dev onlyAuthority -- must be called from the authority - function recoverForfeitTokens() external onlyAuthority nonReentrant { - if(block.timestamp tokensExercised - tokensWithdrawn) - tokensToRecover = IERC20M(allocation.tokenContract).balanceOf(address(this)) + tokensWithdrawn - tokensExercised; - safeTransfer(allocation.tokenContract, getAuthority(), tokensToRecover); - } - - /// @notice gets the amount of tokens available for a grantee to exercise - /// @return uint256 amount of tokens available for the grantee to exercise - function getAmountExercisable() public view returns (uint256) { - if(block.timestampshortStopTime && shortStopTime>0)) - return 0; - - uint256 _timeElapsedSinceVest = block.timestamp - allocation.vestingStartTime; - if(terminated) - _timeElapsedSinceVest = terminationTime - allocation.vestingStartTime; - - uint256 _tokensVested = (_timeElapsedSinceVest * allocation.vestingRate) + allocation.vestingCliffCredit; - - if(_tokensVested>allocation.tokenStreamTotal) - _tokensVested = allocation.tokenStreamTotal; - uint256 _tokensExercisable = _tokensVested + milestoneAwardTotal; - if(_tokensExercisable>tokensExercised) - return _tokensExercisable - tokensExercised; - else - return 0; - } - - /// @notice gets the amount of tokens unlocked for a grantee - /// @return uint256 amount of tokens unlocked for the grantee - function getUnlockedTokenAmount() public view returns (uint256) { - if(block.timestampallocation.tokenStreamTotal + milestoneAwardTotal) - _tokensUnlocked = allocation.tokenStreamTotal + milestoneAwardTotal; - - return _tokensUnlocked + milestoneUnlockedTotal; - } - - /// @notice gets the amount of tokens available for a grantee to withdraw - /// @return uint256 amount of tokens available for the grantee to withdraw - function getAmountWithdrawable() public view override returns (uint256) { - uint256 _tokensUnlocked = getUnlockedTokenAmount(); - - uint256 withdrawableAmount = _min(tokensExercised, _tokensUnlocked); - if(withdrawableAmount>tokensWithdrawn) - return withdrawableAmount - tokensWithdrawn; - else - return 0; - } - -} diff --git a/src/TokenOptionFactory.sol b/src/TokenOptionFactory.sol deleted file mode 100644 index a23041d..0000000 --- a/src/TokenOptionFactory.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.24; - -import "./TokenOptionAllocation.sol"; -import "./interfaces/IAllocationFactory.sol"; - -contract TokenOptionFactory is IAllocationFactory { - - function createAllocation( - AllocationType _allocationType, - address _grantee, - address _controller, - TokenOptionAllocation.Allocation memory _allocation, - TokenOptionAllocation.Milestone[] memory _milestones, - address _paymentToken, - uint256 _exercisePrice, - uint256 _shortStopDuration - ) external returns (address) { - if (_allocationType == AllocationType.TokenOption) { - return address(new TokenOptionAllocation(_grantee, _controller, _paymentToken, _exercisePrice, _shortStopDuration, _allocation, _milestones)); - } else { - revert("AllocationFactory: invalid allocation type"); - } - } -} diff --git a/src/VestingAllocation.sol b/src/VestingAllocation.sol index ffffe2a..680eb0c 100644 --- a/src/VestingAllocation.sol +++ b/src/VestingAllocation.sol @@ -1,28 +1,32 @@ // SPDX-License-Identifier: AGPL-3.0-only import "./BaseAllocation.sol"; -pragma solidity 0.8.24; +pragma solidity ^0.8.24; contract VestingAllocation is BaseAllocation { /// @notice constructor for VestingAllocation /// @param _grantee address of the grantee + /// @param _recipient address of the fund recipient /// @param _controller address of the controller /// @param _allocation Allocation struct containing token contract /// @param _milestones array of Milestone structs with conditions and awards constructor ( address _grantee, + address _recipient, address _controller, Allocation memory _allocation, Milestone[] memory _milestones ) BaseAllocation( - _grantee, - _controller + _grantee, + _recipient, + _controller ) { //perform input validation if (_allocation.tokenContract == address(0)) revert MetaVesT_ZeroAddress(); if (_allocation.tokenStreamTotal == 0) revert MetaVesT_ZeroAmount(); if (_grantee == address(0)) revert MetaVesT_ZeroAddress(); + if (_recipient == address(0)) revert MetaVesT_ZeroAddress(); if (_allocation.vestingRate > 1000*1e18 || _allocation.unlockRate > 1000*1e18) revert MetaVesT_RateTooHigh(); //set vesting allocation variables @@ -49,19 +53,23 @@ contract VestingAllocation is BaseAllocation { /// @notice returns the governing power of the VestingAllocation /// @return governingPower - the governing power of the VestingAllocation based on the governance setting function getGoverningPower() external view override returns (uint256 governingPower) { - if(govType==GovType.all) - { + if(govType==GovType.all) { uint256 totalMilestoneAward = 0; - for(uint256 i; i < milestones.length; ++i) - { + for(uint256 i; i < milestones.length; ++i) { totalMilestoneAward += milestones[i].milestoneAward; } governingPower = (allocation.tokenStreamTotal + totalMilestoneAward) - tokensWithdrawn; + } else if(govType==GovType.vested) { + uint256 amount = getVestedTokenAmount(); + governingPower = (amount > tokensWithdrawn) + ? amount - tokensWithdrawn + : 0; + } else { + uint256 amount = _min(getVestedTokenAmount(), getUnlockedTokenAmount()); + governingPower = (amount > tokensWithdrawn) + ? amount - tokensWithdrawn + : 0; } - else if(govType==GovType.vested) - governingPower = getVestedTokenAmount() - tokensWithdrawn; - else - governingPower = _min(getVestedTokenAmount(), getUnlockedTokenAmount()) - tokensWithdrawn; return governingPower; } diff --git a/src/VestingAllocationFactory.sol b/src/VestingAllocationFactory.sol index e0b70de..f01baaa 100644 --- a/src/VestingAllocationFactory.sol +++ b/src/VestingAllocationFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "./VestingAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; @@ -9,6 +9,7 @@ contract VestingAllocationFactory is IAllocationFactory { function createAllocation( AllocationType _allocationType, address _grantee, + address _recipient, address _controller, VestingAllocation.Allocation memory _allocation, VestingAllocation.Milestone[] memory _milestones, @@ -17,7 +18,7 @@ contract VestingAllocationFactory is IAllocationFactory { uint256 _shortStopDuration ) external returns (address) { if (_allocationType == AllocationType.Vesting) { - return address(new VestingAllocation(_grantee, _controller, _allocation, _milestones)); + return address(new VestingAllocation(_grantee, _recipient, _controller, _allocation, _milestones)); } else { revert("AllocationFactory: invalid allocation type"); } diff --git a/src/interfaces/IAllocationFactory.sol b/src/interfaces/IAllocationFactory.sol index 175c1ed..7bf0370 100644 --- a/src/interfaces/IAllocationFactory.sol +++ b/src/interfaces/IAllocationFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "../VestingAllocation.sol"; @@ -14,6 +14,7 @@ interface IAllocationFactory { function createAllocation( AllocationType _allocationType, address _grantee, + address _recipient, address _controller, VestingAllocation.Allocation memory _allocation, VestingAllocation.Milestone[] memory _milestones, diff --git a/src/interfaces/IBaseAllocation.sol b/src/interfaces/IBaseAllocation.sol index f8789d2..c12680f 100644 --- a/src/interfaces/IBaseAllocation.sol +++ b/src/interfaces/IBaseAllocation.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; interface IBaseAllocation { function getVestingType() external view returns (uint256); diff --git a/src/interfaces/IPriceAllocation.sol b/src/interfaces/IPriceAllocation.sol index 264c7bf..029ee08 100644 --- a/src/interfaces/IPriceAllocation.sol +++ b/src/interfaces/IPriceAllocation.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; interface IPriceAllocation { function getVestingType() external view returns (uint256); diff --git a/src/interfaces/IRestrictedTokenAward.sol b/src/interfaces/IRestrictedTokenAward.sol index 66e3bc5..6a775bf 100644 --- a/src/interfaces/IRestrictedTokenAward.sol +++ b/src/interfaces/IRestrictedTokenAward.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "./IBaseAllocation.sol"; diff --git a/src/interfaces/IZkCappedMinter.sol b/src/interfaces/IZkCappedMinter.sol deleted file mode 100644 index b99da73..0000000 --- a/src/interfaces/IZkCappedMinter.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -interface IZkCappedMinter { - function mint(address _to, uint256 _amount) external; -} \ No newline at end of file diff --git a/src/interfaces/zk-governance/IMintable.sol b/src/interfaces/zk-governance/IMintable.sol new file mode 100644 index 0000000..a80461f --- /dev/null +++ b/src/interfaces/zk-governance/IMintable.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +interface IMintable { + function mint(address _to, uint256 _amount) external; +} diff --git a/src/interfaces/IMintableAndDelegatable.sol b/src/interfaces/zk-governance/IMintableAndDelegatable.sol similarity index 80% rename from src/interfaces/IMintableAndDelegatable.sol rename to src/interfaces/zk-governance/IMintableAndDelegatable.sol index fe2634a..3e72b8c 100644 --- a/src/interfaces/IMintableAndDelegatable.sol +++ b/src/interfaces/zk-governance/IMintableAndDelegatable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.24; +pragma solidity ^0.8.24; -import {IMintable} from "src/interfaces/IMintable.sol"; +import {IMintable} from "./IMintable.sol"; interface IMintableAndDelegatable is IMintable { function DOMAIN_SEPARATOR() external view returns (bytes32); diff --git a/src/interfaces/zk-governance/IZkCappedMinterV2.sol b/src/interfaces/zk-governance/IZkCappedMinterV2.sol new file mode 100644 index 0000000..522fc8d --- /dev/null +++ b/src/interfaces/zk-governance/IZkCappedMinterV2.sol @@ -0,0 +1,26 @@ +// 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 new file mode 100644 index 0000000..901e958 --- /dev/null +++ b/src/interfaces/zk-governance/IZkCappedMinterV2Factory.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +interface IZkCappedMinterV2Factory { + function createCappedMinter( + address _mintable, + address _admin, + uint256 _cap, + uint48 _startTime, + uint48 _expirationTime, + uint256 _saltNonce + ) external returns (address minterAddress); +} diff --git a/src/interfaces/zk-governance/IZkTokenV1.sol b/src/interfaces/zk-governance/IZkTokenV1.sol new file mode 100644 index 0000000..83c03ac --- /dev/null +++ b/src/interfaces/zk-governance/IZkTokenV1.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +interface IZkTokenV1 is IERC20 { + function MINTER_ROLE() external returns (bytes32); + + function grantRole(bytes32 role, address account) external; +} diff --git a/test/AuditBaseA.t (1).sol b/test/AuditBaseA.t (1).sol deleted file mode 100644 index 71bfe59..0000000 --- a/test/AuditBaseA.t (1).sol +++ /dev/null @@ -1,55 +0,0 @@ -pragma solidity ^0.8.20; - -import "forge-std/Test.sol"; - -import "../test/amendement.t.sol"; - -contract EvilGrant { - - function grantee () public view returns (address) { - return address(0x31337); - } - function getGoverningPower() public view returns (uint256) { - return 99999999999999999999999999999; - } -} - -contract Audit is MetaVestControllerTest { - function testFailAuditArbitraryVote() public { - // template from testVoteOnMetavestAmendment - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - address attacker = address(0x31337); - address evil_grant = address(new EvilGrant()); - - vm.prank(attacker); - controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); - - (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); - console.log("attacker made vote and power is" , currentVotingPower); - } - - function testFailAuditRemoveConfirmedMilestone() public { - // template from testRemoveMilestone - address vestingAllocation = createDummyVestingAllocation(); - VestingAllocation(vestingAllocation).confirmMilestone(0); - - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); - controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); - controller.removeMetavestMilestone(vestingAllocation, 0); - } - -} \ No newline at end of file diff --git a/test/AuditBaseA.t.sol b/test/AuditBaseA.t.sol index 71bfe59..1846b52 100644 --- a/test/AuditBaseA.t.sol +++ b/test/AuditBaseA.t.sol @@ -15,40 +15,44 @@ contract EvilGrant { } contract Audit is MetaVestControllerTest { - function testFailAuditArbitraryVote() public { + function test_RevertIf_AuditArbitraryVote() public { // template from testVoteOnMetavestAmendment bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); vm.prank(authority); controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); address attacker = address(0x31337); address evil_grant = address(new EvilGrant()); - + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); console.log("attacker made vote and power is" , currentVotingPower); } - function testFailAuditRemoveConfirmedMilestone() public { + function test_RevertIf_AuditRemoveConfirmedMilestone() public { // template from testRemoveMilestone address vestingAllocation = createDummyVestingAllocation(); VestingAllocation(vestingAllocation).confirmMilestone(0); - + address[] memory addresses = new address[](1); addresses[0] = vestingAllocation; bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); vm.prank(grantee); controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); + vm.prank(authority); controller.removeMetavestMilestone(vestingAllocation, 0); } diff --git a/test/AuditBaseA2.t.sol b/test/AuditBaseA2.t.sol index 2829668..6a4288e 100644 --- a/test/AuditBaseA2.t.sol +++ b/test/AuditBaseA2.t.sol @@ -14,41 +14,46 @@ contract EvilGrant { } } +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract Audit is MetaVestControllerTest { - function testFailAuditArbitraryVote() public { + function test_RevertIf_AuditArbitraryVote() public { // template from testVoteOnMetavestAmendment bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); vm.prank(authority); controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); address attacker = address(0x31337); address evil_grant = address(new EvilGrant()); - + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true); (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); console.log("attacker made vote and power is" , currentVotingPower); } - function testFailAuditRemoveConfirmedMilestone() public { + function test_RevertIf_AuditRemoveConfirmedMilestone() public { // template from testRemoveMilestone address vestingAllocation = createDummyVestingAllocation(); VestingAllocation(vestingAllocation).confirmMilestone(0); - + address[] memory addresses = new address[](1); addresses[0] = vestingAllocation; bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); vm.prank(grantee); controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_MilestoneIndexCompletedOrDoesNotExist.selector)); + vm.prank(authority); controller.removeMetavestMilestone(vestingAllocation, 0); } @@ -61,6 +66,7 @@ contract Audit is MetaVestControllerTest { vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation3); vm.warp(block.timestamp + 1 days); vm.prank(authority); @@ -76,61 +82,62 @@ contract Audit is MetaVestControllerTest { controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); } - function testAuditModifiedCalldataProposal() public { - // template from testCreateSetWithThreeTokenOptionsAndChangeExercisePrice - address allocation1 = createDummyTokenOptionAllocation(); - address allocation2 = createDummyTokenOptionAllocation(); - address allocation3 = createDummyTokenOptionAllocation(); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", allocation1); - controller.addMetaVestToSet("testSet", allocation2); - controller.addMetaVestToSet("testSet", allocation3); - assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); - vm.warp(block.timestamp + 25 seconds); - - - vm.startPrank(grantee); - ERC20(paymentToken).approve(address(allocation1), 2000e18); - ERC20(paymentToken).approve(address(allocation2), 2000e18); - - TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); - - TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); - vm.stopPrank(); - bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); - - vm.prank(authority); - vm.expectRevert(); - controller.updateExerciseOrRepurchasePrice(allocation1, 999999999999999999999e18); - - // Bypass MetaVesTController_AmendmentNeitherMutualNorMajorityConsented - vm.prank(authority); - bytes memory p = abi.encodeWithSelector(msgSig, allocation1, 999999999999999999999e18, 2e18); - address(controller).call(p); - - console.log('Modified excercise price: ', TokenOptionAllocation(allocation1).exercisePrice()); - } +// function testAuditModifiedCalldataProposal() public { +// // template from testCreateSetWithThreeTokenOptionsAndChangeExercisePrice +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// vm.expectRevert(); +// controller.updateExerciseOrRepurchasePrice(allocation1, 999999999999999999999e18); +// +// // Bypass MetaVesTController_AmendmentNeitherMutualNorMajorityConsented +// vm.prank(authority); +// bytes memory p = abi.encodeWithSelector(msgSig, allocation1, 999999999999999999999e18, 2e18); +// address(controller).call(p); +// +// console.log('Modified excercise price: ', TokenOptionAllocation(allocation1).exercisePrice()); +// } function testAuditConsentToMetavestAmendmentInFlavor() public { // template from testRemoveMilestone address vestingAllocation = createDummyVestingAllocation(); VestingAllocation(vestingAllocation).confirmMilestone(0); - + address[] memory addresses = new address[](1); addresses[0] = vestingAllocation; bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); vm.prank(grantee); @@ -143,7 +150,7 @@ contract Audit is MetaVestControllerTest { } - function testFailAuditProposeMajorityMetavestAmendmentNewGranteeDuringProposal() public { + function test_RevertIf_AuditProposeMajorityMetavestAmendmentNewGranteeDuringProposal() public { // template from testProposeMajorityMetavestAmendment address mockAllocation2 = createDummyVestingAllocation(); address mockAllocation3 = createDummyVestingAllocation(); @@ -161,29 +168,39 @@ contract Audit is MetaVestControllerTest { controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); vm.startPrank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); controller.addMetaVestToSet("testSet", mockAllocation3); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); controller.addMetaVestToSet("testSet", mockAllocation4); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); controller.addMetaVestToSet("testSet", mockAllocation5); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); controller.addMetaVestToSet("testSet", mockAllocation6); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); controller.addMetaVestToSet("testSet", mockAllocation7); vm.stopPrank(); - + vm.startPrank(grantee); controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(mockAllocation5, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(mockAllocation6, "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(mockAllocation7, "testSet", msgSig, true); vm.stopPrank(); - + (uint256 totalVotingPower, uint256 currentVotingPower,,,) = controller.functionToSetMajorityProposal(msgSig, "testSet"); console.log("totalVotingPower: ", totalVotingPower); console.log("currentVotingPower: ", currentVotingPower); } function testCreateSetAddVestingThenRemoveSet() public { - + // template from testCreateSetAddVestingThenRemoveSet address allocation1 = createDummyVestingAllocation(); address allocation2 = createDummyVestingAllocation(); diff --git a/test/AuditBaseC.t.sol b/test/AuditBaseC.t.sol index 885a738..b466e23 100644 --- a/test/AuditBaseC.t.sol +++ b/test/AuditBaseC.t.sol @@ -4,75 +4,58 @@ import "forge-std/Test.sol"; import "../test/controller.t.sol"; +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract Audit is MetaVestControllerTest { - function testFailAuditTerminateFailAfterWithdraw() public { - // template from testTerminateVestAndRecoverSlowUnlock - address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = token.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 25 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 25 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - - // InsufficientBalance - vm.expectRevert(); - controller.terminateMetavestVesting(vestingAllocation); - } - function testAuditTerminateFailAfterWithdrawFixCheck() public { // template from testTerminateVestAndRecoverSlowUnlock address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = token.balanceOf(authority); VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 25 seconds); vm.startPrank(grantee); + skip(25 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 1000e18 + 100e18 + 25 * 5e18, "Unexpected amount after cliff and milestone"); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 25 seconds); + skip(25 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), 25 * 5e18, "Unexpected amount after the second vesting period"); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - + vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); - vm.warp(block.timestamp + 365 days); + skip(1200 seconds); + assertEq(VestingAllocation(vestingAllocation).getAmountWithdrawable(), (10e18 - 5e18) * 50, "Unexpected amount after termination"); vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - //check balance of the vesting contract - assertEq(token.balanceOf(vestingAllocation), 0); - } - - function testAuditTerminateFailAfterWithdrawFixCheckOptions() public { - // template from testTerminateVestAndRecoverSlowUnlock - address vestingAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(grantee); - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); - TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 5 seconds); - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); - TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - vm.warp(block.timestamp + 5 seconds); - controller.terminateMetavestVesting(vestingAllocation); - - vm.startPrank(grantee); - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); - TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - vm.warp(block.timestamp + 365 days); - - vm.prank(authority); - TokenOptionAllocation(vestingAllocation).recoverForfeitTokens(); - //check balance of the vesting contract - assertEq(token.balanceOf(vestingAllocation), 0); } +// function testAuditTerminateFailAfterWithdrawFixCheckOptions() public { +// // template from testTerminateVestAndRecoverSlowUnlock +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// VestingAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 5 seconds); +// vm.startPrank(grantee); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.warp(block.timestamp + 5 seconds); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// vm.warp(block.timestamp + 5 seconds); +// controller.terminateMetavestVesting(vestingAllocation); +// +// vm.startPrank(grantee); +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// vm.warp(block.timestamp + 365 days); +// +// vm.prank(authority); +// TokenOptionAllocation(vestingAllocation).recoverForfeitTokens(); +// //check balance of the vesting contract +// assertEq(token.balanceOf(vestingAllocation), 0); +// } } \ No newline at end of file diff --git a/test/AuditBaseC3.t.sol b/test/AuditBaseC3.t.sol index ae52b4a..31f356e 100644 --- a/test/AuditBaseC3.t.sol +++ b/test/AuditBaseC3.t.sol @@ -4,29 +4,12 @@ import "forge-std/Test.sol"; import "../test/controller.t.sol"; +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter contract Audit is MetaVestControllerTest { - function testFailAuditTerminateFailAfterWithdraw() public { - // template from testTerminateVestAndRecoverSlowUnlock - address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = token.balanceOf(authority); - VestingAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 25 seconds); - vm.startPrank(grantee); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.warp(block.timestamp + 25 seconds); - VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - - // InsufficientBalance - vm.expectRevert(); - controller.terminateMetavestVesting(vestingAllocation); - } - function testAuditTerminateVestAndRecovers() public { // template from testTerminateVestAndRecovers address vestingAllocation = createDummyVestingAllocation(); - uint256 snapshot = token.balanceOf(authority); BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); milestones[0] = BaseAllocation.Milestone({ @@ -34,73 +17,72 @@ contract Audit is MetaVestControllerTest { unlockOnCompletion: false, complete: false, conditionContracts: new address[](0) - }); - token.approve(address(controller), 2100e18); - - + }); + vm.prank(authority); controller.addMetavestMilestone(vestingAllocation, milestones[0]); VestingAllocation(vestingAllocation).confirmMilestone(1); - vm.warp(block.timestamp + 50 seconds); + skip(50 seconds); + vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); vm.startPrank(grantee); - VestingAllocation(vestingAllocation).getVestedTokenAmount(); - VestingAllocation(vestingAllocation).getUnlockedTokenAmount(); + assertEq(VestingAllocation(vestingAllocation).getVestedTokenAmount(), 100e18 + 1000e18 + 10e18 * 50, "Unexpected vested amount after termination"); + assertEq(VestingAllocation(vestingAllocation).getUnlockedTokenAmount(), 100e18 + 5e18 * 50 + 5e18 * 50, "Unexpected unlocked amount after termination"); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); // assertEq(token.balanceOf(vestingAllocation), 0); } - function testFailAuditRounding() public { - // template from testConfirmingMilestoneTokenOption - address vestingAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - TokenOptionAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - //exercise max available - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - - console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); - console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e6)); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e11)); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(9.99e11)); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e12)); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1.1e12)); - console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e13)); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1.1e12); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); - - console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); - console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); - // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - - function testAuditExcercisePrice() public { - // template from testConfirmingMilestoneTokenOption - address vestingAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - TokenOptionAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - //exercise max available - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - - console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); - console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); - - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e18); - - console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); - console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); - // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } -} \ No newline at end of file +// function test_RevertIf_AuditRounding() public { +// // template from testConfirmingMilestoneTokenOption +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// +// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e6)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e11)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(9.99e11)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e12)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1.1e12)); +// console.log('small amount payment: ', TokenOptionAllocation(vestingAllocation).getPaymentAmount(1e13)); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1.1e12); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e6); +// +// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } +// +// function testAuditExcercisePrice() public { +// // template from testConfirmingMilestoneTokenOption +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// +// console.log('before amount of payment token:', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('before tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(1e18); +// +// console.log('after amount of payment token: ', ERC20Stable(paymentToken).balanceOf(grantee)); +// console.log('after tokensExercised: ', TokenOptionAllocation(vestingAllocation).tokensExercised()); +// // TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } +} diff --git a/test/MetaVesTFactory.t.sol b/test/MetaVesTFactory.t.sol deleted file mode 100644 index e111113..0000000 --- a/test/MetaVesTFactory.t.sol +++ /dev/null @@ -1,115 +0,0 @@ -//SPDX-License-Identifier: AGPL-3.0-only - -pragma solidity ^0.8.18; - -import "forge-std/Test.sol"; -import "../src/MetaVesTFactory.sol"; -import "../src/MetaVesTController.sol"; -import "../src/VestingAllocationFactory.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkCappedMinterFactory.sol"; - -interface IERC20 { - function transfer(address recipient, uint256 amount) external returns (bool); - function balanceOf(address account) external view returns (uint256); - function approve(address spender, uint256 amount) external returns (bool); - -} - -/// @dev foundry framework testing of MetaVesTFactory.sol -/// forge t --via-ir - -/// @notice test contract for MetaVesTFactory using Foundry -contract MetaVesTFactoryTest is Test { - MetaVesTFactory internal factory; - address factoryAddr; - address dai_addr = 0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6; - VestingAllocationFactory _factory;// = new VestingAllocationFactory(); - RestrictedTokenFactory _factory2;// = new RestrictedTokenFactory(); - TokenOptionFactory _factory3;// = new TokenOptionFactory(); - - event MetaVesT_Deployment( - address newMetaVesT, - address authority, - address controller, - address dao, - address paymentToken - ); - - function setUp() public { - _factory = new VestingAllocationFactory(); - _factory2 = new RestrictedTokenFactory(); - _factory3 = new TokenOptionFactory(); - factory = new MetaVesTFactory(); - factoryAddr = address(factory); - address _authority = address(0xa); - - address _dao = address(0xB); - address _paymentToken = address(0xC); - - - ZkTokenV2 zkToken = ZkTokenV2(0x3D65a7e2960ac3820262b847b4C4dCB50F225f1a); - ZkCappedMinterFactory zkMinterFactory = ZkCappedMinterFactory(0x25BDFa33Fb8873701DDbeeD3f09edD173Ac71A1b); - - address _controller = factory.deployMetavestAndController(_authority, _dao, address(_factory), address(_factory2), address(_factory3), address(zkMinterFactory), address(zkToken)); - } - - function testDeployMetavestAndController() public { - address _authority = address(0xa); - address _dao = address(0xB); - address _paymentToken = address(0xC); - address grantee = address(0xD); - RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); - - ZkTokenV2 zkToken = ZkTokenV2(0x3D65a7e2960ac3820262b847b4C4dCB50F225f1a); - ZkCappedMinterFactory zkMinterFactory = ZkCappedMinterFactory(0x25BDFa33Fb8873701DDbeeD3f09edD173Ac71A1b); - - address _controller = factory.deployMetavestAndController(_authority, _dao, address(_factory), address(_factory2), address(_factory3), address(zkMinterFactory), address(zkToken)); - metavestController controller = metavestController(_controller); - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - //token.approve(address(controller), 1100e18); - - address vestingAllocation = controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - - assertEq(token.balanceOf(vestingAllocation), 1100e18);,9u - } - - function testFailControllerZeroAddress() public { - address _authority = address(0); - address _dao = address(0); - address _paymentToken = address(0); - factory.deployMetavestAndController(_authority, _dao, address(0), address(0), address(0), address(0), address(0)); - } - - -} diff --git a/test/VestingAllocation.t.sol b/test/VestingAllocation.t.sol new file mode 100644 index 0000000..76c1231 --- /dev/null +++ b/test/VestingAllocation.t.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: AGPL-3.0-only +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 {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 + ) { + authority = _authority; + zkCappedMinter = _zkCappedMinter; + } + + function mint(address recipient, uint256 amount) external { + ZkCappedMinterV2(zkCappedMinter).mint(recipient, amount); + } + + function updateMetavestVestingRate( + address _grant, + uint160 _vestingRate + ) external { + BaseAllocation(_grant).updateVestingRate(_vestingRate); + } +} + +contract VestingAllocationTest is Test { + + address grantee = address(0xa); + address recipient = address(0xb); + address newRecipient = address(0xc); + + ZkTokenV1 zkToken; + + 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)); + + // Create mock controller + mockController = new MockMetaVesTController(address(this), address(zkCappedMinter)); + + // Grant controller minter privilege + zkCappedMinter.grantRole( + zkCappedMinter.MINTER_ROLE(), + address(mockController) + ); + + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); + milestones[0] = BaseAllocation.Milestone({ + milestoneAward: 2000 ether, + unlockOnCompletion: false, + complete: false, + conditionContracts: new address[](0) + }); + + vestingAllocation = new VestingAllocation( + grantee, + recipient, + address(mockController), + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), + milestones + ); + } + + function test_Metadata() public { + assertEq(vestingAllocation.grantee(), grantee, "Unexpected grantee"); + assertEq(vestingAllocation.recipient(), recipient, "Unexpected recipient"); + } + + function test_Withdraw() public { + // Should withdraw to recipient by default + uint256 balanceBefore = zkToken.balanceOf(recipient); + + vm.expectEmit(true, true, true, true); + emit BaseAllocation.MetaVesT_Withdrawn(grantee, recipient, address(zkToken), 100 ether); + vm.prank(grantee); + VestingAllocation(vestingAllocation).withdraw(100 ether); + + assertEq(zkToken.balanceOf(recipient), balanceBefore + 100 ether); + } + + function test_RevertIf_WithdrawTooMuch() public { + vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_MoreThanAvailable.selector)); + VestingAllocation(vestingAllocation).withdraw(101 ether); + } + + function test_UpdateRecipient() public { + // Grantee should be able to update recipient + vm.prank(grantee); + vm.expectEmit(true, true, true, true); + emit BaseAllocation.MetaVesT_UpdatedRecipient(grantee, newRecipient); + VestingAllocation(vestingAllocation).updateRecipient(newRecipient); + + // Should withdraw to new recipient now + uint256 balanceBefore = zkToken.balanceOf(newRecipient); + vm.prank(grantee); + VestingAllocation(vestingAllocation).withdraw(100 ether); + assertEq(zkToken.balanceOf(newRecipient), balanceBefore + 100 ether); + } + + function test_RevertIf_UpdateRecipientNonGrantee() public { + vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_OnlyGrantee.selector)); + VestingAllocation(vestingAllocation).updateRecipient(newRecipient); + } + + function test_Terminate() public { + // Controller should be able to terminate it + assertFalse(vestingAllocation.terminated(), "vesting contract should not be terminated yet"); + vm.prank(address(mockController)); + vm.expectEmit(true, true, true, true); + emit BaseAllocation.MetaVesT_Terminated(grantee, 0); // No token recovered because it is mint-on-demand + vestingAllocation.terminate(); + assertTrue(vestingAllocation.terminated(), "vesting contract should be terminated"); + } + + function test_RevertIf_TerminateNonController() public { + vm.expectRevert(abi.encodeWithSelector(BaseAllocation.MetaVesT_OnlyController.selector)); + vestingAllocation.terminate(); + } + + function test_GetGoverningPowerAfterVestingRateReduction() public { + // Withdraw cliff amount first + vm.prank(grantee); + VestingAllocation(vestingAllocation).withdraw(100 ether); + + skip(2 seconds); + assertEq(vestingAllocation.getAmountWithdrawable(), 10 ether * 2); + assertEq(vestingAllocation.getGoverningPower(), 10 ether * 2); + + vm.prank(grantee); + VestingAllocation(vestingAllocation).withdraw(10 ether); + + assertEq(vestingAllocation.getAmountWithdrawable(), 10 ether * 2 - 10 ether); + assertEq(vestingAllocation.getGoverningPower(), 10 ether * 2 - 10 ether); + + mockController.updateMetavestVestingRate(address(vestingAllocation), 4 ether); + + // 4 ether/sec * 2 sec - 10 ether = -2 ether < 0 + assertEq(vestingAllocation.getAmountWithdrawable(), 0 ether); + assertEq(vestingAllocation.getGoverningPower(), 0 ether); + } +} diff --git a/test/ZkSyncGuardianCompensation.t.sol b/test/ZkSyncGuardianCompensation.t.sol new file mode 100644 index 0000000..c60728c --- /dev/null +++ b/test/ZkSyncGuardianCompensation.t.sol @@ -0,0 +1,427 @@ +// 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 new file mode 100644 index 0000000..f006f53 --- /dev/null +++ b/test/ZkSyncGuardianCompensationAcceptance.t.sol @@ -0,0 +1,133 @@ +// 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 4533639..9b300ad 100644 --- a/test/amendement.t.sol +++ b/test/amendement.t.sol @@ -2,615 +2,91 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; -import "../src/metavestController.sol"; import "../src/BaseAllocation.sol"; -import "../src/RestrictedTokenAllocation.sol"; +//import "../src/RestrictedTokenAllocation.sol"; import "../src/interfaces/IAllocationFactory.sol"; import "../src/VestingAllocationFactory.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/RestrictedTokenFactory.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkCappedMinterFactory.sol"; +//import "../src/TokenOptionFactory.sol"; +//import "../src/RestrictedTokenFactory.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "./lib/MetaVesTControllerTestBase.sol"; -abstract contract ERC20 { +// TODO WIP: non-VestingAllocation tests are disabled until adopted ZkCappedMinter +contract MetaVestControllerTest is MetaVesTControllerTestBase { + address public authority = guardianSafe; + address public dao = guardianSafe; + address public grantee = alice; - /// @dev The total supply has overflowed. - error TotalSupplyOverflow(); + uint256 cap = 2000 ether; + uint48 cappedMinterStartTime = uint48(block.timestamp); // Minter start now + uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 1600); // Minter expired 1600 seconds after start - /// @dev The allowance has overflowed. - error AllowanceOverflow(); + address public vestingAllocation; - /// @dev The allowance has underflowed. - error AllowanceUnderflow(); + uint256 agreementSaltCounter = 0; - /// @dev Insufficient balance. - error InsufficientBalance(); - - /// @dev Insufficient allowance. - error InsufficientAllowance(); - - /// @dev The permit is invalid. - error InvalidPermit(); - - /// @dev The permit has expired. - error PermitExpired(); - - /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 amount); - - /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. - uint256 private constant _TRANSFER_EVENT_SIGNATURE = - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; - - /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. - uint256 private constant _APPROVAL_EVENT_SIGNATURE = - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; - - /// @dev The storage slot for the total supply. - uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; - - /// @dev The balance slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _BALANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let balanceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; - - /// @dev The allowance slot of (`owner`, `spender`) is given by: - /// ``` - /// mstore(0x20, spender) - /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let allowanceSlot := keccak256(0x0c, 0x34) - /// ``` - uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; - - /// @dev The nonce slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _NONCES_SLOT_SEED) - /// mstore(0x00, owner) - /// let nonceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _NONCES_SLOT_SEED = 0x38377508; - - - /// @dev Returns the name of the token. - function name() public view virtual returns (string memory); - - /// @dev Returns the symbol of the token. - function symbol() public view virtual returns (string memory); - - /// @dev Returns the decimals places of the token. - function decimals() public view virtual returns (uint8) { - return 18; - } - - - /// @dev Returns the amount of tokens in existence. - function totalSupply() public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - result := sload(_TOTAL_SUPPLY_SLOT) - } - } - - /// @dev Returns the amount of tokens owned by `owner`. - function balanceOf(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. - function allowance(address owner, address spender) - public - view - virtual - returns (uint256 result) - { - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x34)) - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - /// - /// Emits a {Approval} event. - function approve(address spender, uint256 amount) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically increases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Add to the allowance. - let allowanceAfter := add(allowanceBefore, difference) - // Revert upon overflow. - if lt(allowanceAfter, allowanceBefore) { - mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated allowance. - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically decreases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Revert if will underflow. - if lt(allowanceBefore, difference) { - mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - let allowanceAfter := sub(allowanceBefore, difference) - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Transfer `amount` tokens from the caller to `to`. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(msg.sender, to, amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, caller()) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) - } - _afterTokenTransfer(msg.sender, to, amount); - return true; - } - - /// @dev Transfers `amount` tokens from `from` to `to`. - /// - /// Note: Does not update the allowance if it is the maximum uint256 value. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the allowance slot and load its value. - mstore(0x20, caller()) - mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - return true; - } - - /// @dev Returns the current nonce for `owner`. - /// This value is used to compute the signature for EIP-2612 permit. - function nonces(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, - /// authorized by a signed approval by `owner`. - /// - /// Emits a {Approval} event. - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - bytes32 domainSeparator = DOMAIN_SEPARATOR(); - /// @solidity memory-safe-assembly - assembly { - // Grab the free memory pointer. - let m := mload(0x40) - // Revert if the block timestamp greater than `deadline`. - if gt(timestamp(), deadline) { - mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. - revert(0x1c, 0x04) - } - // Clean the upper 96 bits. - owner := shr(96, shl(96, owner)) - spender := shr(96, shl(96, spender)) - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - let nonceSlot := keccak256(0x0c, 0x20) - let nonceValue := sload(nonceSlot) - // Increment and store the updated nonce. - sstore(nonceSlot, add(nonceValue, 1)) - // Prepare the inner hash. - // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. - // forgefmt: disable-next-item - mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) - mstore(add(m, 0x20), owner) - mstore(add(m, 0x40), spender) - mstore(add(m, 0x60), value) - mstore(add(m, 0x80), nonceValue) - mstore(add(m, 0xa0), deadline) - // Prepare the outer hash. - mstore(0, 0x1901) - mstore(0x20, domainSeparator) - mstore(0x40, keccak256(m, 0xc0)) - // Prepare the ecrecover calldata. - mstore(0, keccak256(0x1e, 0x42)) - mstore(0x20, and(0xff, v)) - mstore(0x40, r) - mstore(0x60, s) - pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) - // If the ecrecover fails, the returndatasize will be 0x00, - // `owner` will be be checked if it equals the hash at 0x00, - // which evaluates to false (i.e. 0), and we will revert. - // If the ecrecover succeeds, the returndatasize will be 0x20, - // `owner` will be compared against the returned address at 0x20. - if iszero(eq(mload(returndatasize()), owner)) { - mstore(0x00, 0xddafbaef) // `InvalidPermit()`. - revert(0x1c, 0x04) - } - // Compute the allowance slot and store the value. - // The `owner` is already at slot 0x20. - mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) - sstore(keccak256(0x2c, 0x34), value) - // Emit the {Approval} event. - log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) - mstore(0x40, m) // Restore the free memory pointer. - mstore(0x60, 0) // Restore the zero pointer. - } - } - - /// @dev Returns the EIP-2612 domains separator. - function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) // Grab the free memory pointer. - } - // We simply calculate it on-the-fly to allow for cases where the `name` may change. - bytes32 nameHash = keccak256(bytes(name())); - /// @solidity memory-safe-assembly - assembly { - let m := result - // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - // forgefmt: disable-next-item - mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) - mstore(add(m, 0x20), nameHash) - // `keccak256("1")`. - // forgefmt: disable-next-item - mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) - mstore(add(m, 0x60), chainid()) - mstore(add(m, 0x80), address()) - result := keccak256(m, 0xa0) - } - } - - /// @dev Mints `amount` tokens to `to`, increasing the total supply. - /// - /// Emits a {Transfer} event. - function _mint(address to, uint256 amount) internal virtual { - _beforeTokenTransfer(address(0), to, amount); - /// @solidity memory-safe-assembly - assembly { - let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) - let totalSupplyAfter := add(totalSupplyBefore, amount) - // Revert if the total supply overflows. - if lt(totalSupplyAfter, totalSupplyBefore) { - mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) - } - _afterTokenTransfer(address(0), to, amount); - } - - /// @dev Burns `amount` tokens from `from`, reducing the total supply. - /// - /// Emits a {Transfer} event. - function _burn(address from, uint256 amount) internal virtual { - _beforeTokenTransfer(from, address(0), amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, from) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Subtract and store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) - // Emit the {Transfer} event. - mstore(0x00, amount) - log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) - } - _afterTokenTransfer(from, address(0), amount); - } - - /// @dev Moves `amount` of tokens from `from` to `to`. - function _transfer(address from, address to, uint256 amount) internal virtual { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - } - - /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. - /// - /// Emits a {Approval} event. - function _approve(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - let owner_ := shl(96, owner) - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) - } - } - - - /// @dev Hook that is called before any transfer of tokens. - /// This includes minting and burning. - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - /// @dev Hook that is called after any transfer of tokens. - /// This includes minting and burning. - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} + function setUp() public override { + MetaVesTControllerTestBase.setUp(); + + vm.startPrank(deployer); + + // Deploy MetaVesT controller + + vestingAllocationFactory = new VestingAllocationFactory(); + + controller = metavestController(address(new ERC1967Proxy{salt: salt}( + address(new metavestController{salt: salt}()), + abi.encodeWithSelector( + metavestController.initialize.selector, + guardianSafe, + guardianSafe, + address(registry), + address(vestingAllocationFactory) + ) + ))); + + // Deploy ZK Capped Minter v2 + + 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) + )); -contract MockERC20 is ERC20 { - constructor(string memory name, string memory symbol) ERC20() {} - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - function name() public view override returns (string memory) { - return "Test Token"; - } - function symbol() public view override returns (string memory) { - return "TT"; - } -} + vm.stopPrank(); -contract MetaVestControllerTest is Test { - metavestController public controller; - MockERC20 public token; - MockERC20 public paymentToken; - address public authority; - address public dao; - address public vestingFactory; - address public tokenOptionFactory; - address public restrictedTokenFactory; - address public grantee; - address public mockAllocation; - - function setUp() public { - authority = address(this); - dao = address(2); - VestingAllocationFactory factory = new VestingAllocationFactory(); - TokenOptionFactory tokenFactory = new TokenOptionFactory(); - RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); - grantee = address(6); - - token = new MockERC20("Test Token", "TT"); - paymentToken = new MockERC20("Payment Token", "PT"); - - ZkTokenV2 zkToken = new ZkTokenV2(); - ZkCappedMinterFactory zkMinterFactory = new ZkCappedMinterFactory(0x0); - - controller = new metavestController( - authority, - dao, - address(factory), - address(tokenFactory), - address(restrictedTokenFactory), - address(zkMinterFactory), - address(zkToken) - ); + vm.startPrank(guardianSafe); + controller.setZkCappedMinter(address(zkCappedMinter)); + controller.createSet("testSet"); - token.mint(authority, 1000000e58); - paymentToken.mint(authority, 1000000e58); + // Guardian SAFE to delegate signing to an EOA + registry.setDelegation(delegate, block.timestamp + 365 days * 3); // This is a hack. One should not delegate signing for this long + assertTrue(registry.isValidDelegate(guardianSafe, delegate), "delegate should be Guardian SAFE's delegate"); - paymentToken.transfer(address(grantee), 1000e25); - mockAllocation = createDummyVestingAllocation(); + vm.stopPrank(); - vm.prank(authority); - controller.createSet("testSet"); + vestingAllocation = createDummyVestingAllocation(); } - function testProposeMetavestAmendment() public { bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); vm.prank(authority); - controller.proposeMetavestAmendment(address(mockAllocation), msgSig, callData); + controller.proposeMetavestAmendment(address(vestingAllocation), msgSig, callData); + + (bool isPending, bytes32 dataHash, bool inFavor) = controller.functionToGranteeToAmendmentPending(msgSig, address(vestingAllocation)); - (bool isPending, bytes32 dataHash, bool inFavor) = controller.functionToGranteeToAmendmentPending(msgSig, address(mockAllocation)); - assertTrue(isPending); assertEq(dataHash, keccak256(callData)); assertFalse(inFavor); } - function testFailProposeMajorityMetavestAmendment() public { + function test_RevertIf_ProposeMajorityMetavestAmendment() public { address mockAllocation2 = createDummyVestingAllocation(); address mockAllocation3 = createDummyVestingAllocation(); address mockAllocation4 = createDummyVestingAllocation(); @@ -619,19 +95,21 @@ contract MetaVestControllerTest is Test { vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation3); + vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation4); vm.warp(block.timestamp + 1 days); vm.prank(authority); controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - //log the current withdrawable - console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); + //log the current withdrawable + console.log(VestingAllocation(mockAllocation2).getAmountWithdrawable()); + vm.prank(grantee); controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); controller.updateMetavestTransferability(mockAllocation2, true); } @@ -644,82 +122,84 @@ contract MetaVestControllerTest is Test { vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation3); + vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation4); vm.warp(block.timestamp + 15 seconds); vm.prank(authority); controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - + vm.startPrank(grantee); //log the current withdrawable - console.log(TokenOptionAllocation(mockAllocation2).getAmountWithdrawable()); + console.log(VestingAllocation(mockAllocation2).getAmountWithdrawable()); controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); - - controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - vm.stopPrank(); - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - } - - - function testMajorityPowerMetavestAmendment() public { - address mockAllocation2 = createDummyTokenOptionAllocation(); - address mockAllocation3 = createDummyTokenOptionAllocation(); - address mockAllocation4 = createDummyTokenOptionAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.warp(block.timestamp + 1 days); - vm.startPrank(grantee); - ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); - TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); - vm.stopPrank(); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - vm.prank(grantee); controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); - + vm.stopPrank(); vm.prank(authority); controller.updateMetavestTransferability(mockAllocation2, true); } - function testFailMajorityPowerMetavestAmendment() public { - address mockAllocation2 = createDummyTokenOptionAllocation(); - address mockAllocation3 = createDummyTokenOptionAllocation(); - address mockAllocation4 = createDummyTokenOptionAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); - controller.addMetaVestToSet("testSet", mockAllocation3); - controller.addMetaVestToSet("testSet", mockAllocation4); - vm.warp(block.timestamp + 1 days); - vm.startPrank(grantee); - ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); - TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); - vm.stopPrank(); - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); - vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); - } +// function testMajorityPowerMetavestAmendment() public { +// address mockAllocation2 = createDummyTokenOptionAllocation(); +// address mockAllocation3 = createDummyTokenOptionAllocation(); +// address mockAllocation4 = createDummyTokenOptionAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// controller.addMetaVestToSet("testSet", mockAllocation4); +// vm.warp(block.timestamp + 1 days); +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +// vm.stopPrank(); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } + +// function test_RevertIf_MajorityPowerMetavestAmendment() public { +// address mockAllocation2 = createDummyTokenOptionAllocation(); +// address mockAllocation3 = createDummyTokenOptionAllocation(); +// address mockAllocation4 = createDummyTokenOptionAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", mockAllocation2); +// controller.addMetaVestToSet("testSet", mockAllocation3); +// controller.addMetaVestToSet("testSet", mockAllocation4); +// vm.warp(block.timestamp + 1 days); +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(mockAllocation2), 2000e18); +// TokenOptionAllocation(mockAllocation2).exerciseTokenOption(TokenOptionAllocation(mockAllocation2).getAmountExercisable()); +// vm.stopPrank(); +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation3, "testSet", msgSig, true); +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(mockAllocation4, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// vm.prank(authority); +// controller.updateMetavestTransferability(mockAllocation2, true); +// } function testProposeMajorityMetavestAmendment() public { address mockAllocation2 = createDummyVestingAllocation(); @@ -729,6 +209,7 @@ contract MetaVestControllerTest is Test { vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation3); vm.warp(block.timestamp + 1 days); vm.prank(authority); @@ -752,6 +233,7 @@ contract MetaVestControllerTest is Test { vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation3); vm.warp(block.timestamp + 1 days); vm.prank(authority); @@ -784,7 +266,7 @@ contract MetaVestControllerTest is Test { controller.updateMetavestTransferability(mockAllocation3, true); } - function testFailNoPassProposeMajorityMetavestAmendment() public { + function test_RevertIf_NoPassProposeMajorityMetavestAmendment() public { address mockAllocation2 = createDummyVestingAllocation(); address mockAllocation3 = createDummyVestingAllocation(); bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); @@ -792,63 +274,67 @@ contract MetaVestControllerTest is Test { vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation2); + vm.prank(authority); controller.addMetaVestToSet("testSet", mockAllocation3); vm.warp(block.timestamp + 1 days); vm.prank(authority); controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); controller.updateMetavestTransferability(mockAllocation2, true); - + vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); controller.updateMetavestTransferability(mockAllocation3, true); } function testVoteOnMetavestAmendment() public { bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); vm.prank(authority); controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); vm.prank(grantee); - controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); + controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); (uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet"); } - function testFailVoteOnMetavestAmendmentTwice() public { + function test_RevertIf_VoteOnMetavestAmendmentTwice() public { bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true); + bytes memory callData = abi.encodeWithSelector(msgSig, address(vestingAllocation), true); vm.prank(authority); - controller.addMetaVestToSet("testSet", address(mockAllocation)); + controller.addMetaVestToSet("testSet", address(vestingAllocation)); vm.prank(authority); controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); vm.startPrank(grantee); - controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); - controller.voteOnMetavestAmendment(address(mockAllocation), "testSet", msgSig, true); + controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AlreadyVoted.selector)); + controller.voteOnMetavestAmendment(address(vestingAllocation), "testSet", msgSig, true); vm.stopPrank(); } function testSetManagement() public { vm.startPrank(authority); - + // Test creating a new set controller.createSet("newSet"); // Test adding a MetaVest to a set - controller.addMetaVestToSet("newSet", address(mockAllocation)); + controller.addMetaVestToSet("newSet", address(vestingAllocation)); // Test removing a MetaVest from a set - controller.removeMetaVestFromSet("newSet", address(mockAllocation)); + controller.removeMetaVestFromSet("newSet", address(vestingAllocation)); // Test removing a set @@ -858,123 +344,140 @@ contract MetaVestControllerTest is Test { vm.stopPrank(); } - function testFailCreateDuplicateSet() public { + function test_RevertIf_CreateDuplicateSet() public { vm.startPrank(authority); controller.createSet("duplicateSet"); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetAlreadyExists.selector)); controller.createSet("duplicateSet"); vm.stopPrank(); } - function testFailNonAuthorityCreateSet() public { + function test_RevertIf_NonAuthorityCreateSet() public { vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); controller.createSet("unauthorizedSet"); } - // Helper functions to create dummy allocations for testing + // Helper functions to create dummy allocations for testing function createDummyVestingAllocation() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); + return createDummyVestingAllocation(""); // Expect no reverts } - function createDummyTokenOptionAllocation() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation(bytes memory expectRevertData) internal returns (address) { BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, + milestoneAward: 100 ether, unlockOnCompletion: true, complete: false, conditionContracts: new address[](0) }); - token.approve(address(controller), 1100e18); - - return controller.createMetavest( - metavestController.metavestType.TokenOption, - grantee, - allocation, + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + agreementSaltCounter++, + delegatePrivateKey, + alice, // = grantee + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), milestones, - 1e18, - address(paymentToken), - 365 days, - 0 + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - } - - function createDummyRestrictedTokenAward() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - return controller.createMetavest( - metavestController.metavestType.RestrictedTokenAward, - grantee, - allocation, - milestones, - 1e18, - address(paymentToken), - 365 days, - 0 - + // 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 + alice, // recipient + alicePrivateKey, + "Alice", + expectRevertData ); } +// function createDummyTokenOptionAllocation() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 1100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// ); +// } +// +// function createDummyRestrictedTokenAward() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 1100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// +// ); +// } + //write a test for every consentcheck function in metavest controller function testConsentCheck() public { address allocation = createDummyVestingAllocation(); @@ -991,7 +494,7 @@ contract MetaVestControllerTest is Test { controller.updateMetavestTransferability(allocation, true); } - function testFailConsentCheck() public { + function test_RevertIf_ConsentCheck() public { address allocation = createDummyVestingAllocation(); bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); @@ -1000,21 +503,24 @@ contract MetaVestControllerTest is Test { controller.proposeMetavestAmendment(allocation, msgSig, callData); vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, false); vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); controller.updateMetavestTransferability(allocation, true); } - function testFailConsentCheckNoProposal() public { + function test_RevertIf_ConsentCheckNoProposal() public { address allocation = createDummyVestingAllocation(); bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); } - function testFailConsentCheckNoVote() public { + function test_RevertIf_ConsentCheckNoVote() public { address allocation = createDummyVestingAllocation(); bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); @@ -1023,10 +529,11 @@ contract MetaVestControllerTest is Test { controller.proposeMetavestAmendment(allocation, msgSig, callData); vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); controller.updateMetavestTransferability(allocation, true); } - function testFailConsentCheckNoUpdate() public { + function test_RevertIf_ConsentCheckNoUpdate() public { address allocation = createDummyVestingAllocation(); bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); @@ -1035,10 +542,11 @@ contract MetaVestControllerTest is Test { controller.proposeMetavestAmendment(allocation, msgSig, callData); vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); } - function testFailConsentCheckNoVoteUpdate() public { + function test_RevertIf_ConsentCheckNoVoteUpdate() public { address allocation = createDummyVestingAllocation(); bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); @@ -1047,259 +555,261 @@ contract MetaVestControllerTest is Test { controller.proposeMetavestAmendment(allocation, msgSig, callData); vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_SetDoesNotExist.selector)); controller.voteOnMetavestAmendment(allocation, "testSet", msgSig, true); } - function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { - address allocation1 = createDummyTokenOptionAllocation(); - address allocation2 = createDummyTokenOptionAllocation(); - address allocation3 = createDummyTokenOptionAllocation(); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", allocation1); - controller.addMetaVestToSet("testSet", allocation2); - controller.addMetaVestToSet("testSet", allocation3); - assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); - vm.warp(block.timestamp + 25 seconds); - - - vm.startPrank(grantee); - ERC20(paymentToken).approve(address(allocation1), 2000e18); - ERC20(paymentToken).approve(address(allocation2), 2000e18); - - TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); - - TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); - vm.stopPrank(); - bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); - - vm.prank(grantee); - controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); - - // Check that the exercise price was updated - assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); - } - - function testFailCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { - address allocation1 = createDummyTokenOptionAllocation(); - address allocation2 = createDummyTokenOptionAllocation(); - address allocation3 = createDummyTokenOptionAllocation(); - - vm.prank(authority); - controller.addMetaVestToSet("testSet", allocation1); - controller.addMetaVestToSet("testSet", allocation2); - controller.addMetaVestToSet("testSet", allocation3); - assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); - vm.warp(block.timestamp + 25 seconds); - - - vm.startPrank(grantee); - ERC20(paymentToken).approve(address(allocation1), 2000e18); - ERC20(paymentToken).approve(address(allocation2), 2000e18); - - TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); - - TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); - vm.stopPrank(); - bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); - - vm.prank(authority); - controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - - //vm.prank(grantee); - // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); - - // vm.prank(grantee); - // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); - - // Check that the exercise price was updated - assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); - } - - function testFailconsentToNoPendingAmendment() public { +// function testCreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// vm.prank(grantee); +// controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +// +// // Check that the exercise price was updated +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +// } + +// function test_RevertIf_CreateSetWithThreeTokenOptionsAndChangeExercisePrice() public { +// address allocation1 = createDummyTokenOptionAllocation(); +// address allocation2 = createDummyTokenOptionAllocation(); +// address allocation3 = createDummyTokenOptionAllocation(); +// +// vm.prank(authority); +// controller.addMetaVestToSet("testSet", allocation1); +// controller.addMetaVestToSet("testSet", allocation2); +// controller.addMetaVestToSet("testSet", allocation3); +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 1e18); +// vm.warp(block.timestamp + 25 seconds); +// +// +// vm.startPrank(grantee); +// ERC20(paymentToken).approve(address(allocation1), 2000e18); +// ERC20(paymentToken).approve(address(allocation2), 2000e18); +// +// TokenOptionAllocation(allocation1).exerciseTokenOption(TokenOptionAllocation(allocation1).getAmountExercisable()); +// +// TokenOptionAllocation(allocation2).exerciseTokenOption(TokenOptionAllocation(allocation2).getAmountExercisable()); +// vm.stopPrank(); +// bytes4 msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation1, 2e18); +// +// vm.prank(authority); +// controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); +// +// //vm.prank(grantee); +// // controller.voteOnMetavestAmendment(allocation1, "testSet", msgSig, true); +// +// // vm.prank(grantee); +// // controller.voteOnMetavestAmendment(allocation2, "testSet", msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation1, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation2, 2e18); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation3, 2e18); +// +// // Check that the exercise price was updated +// assertTrue(TokenOptionAllocation(allocation1).exercisePrice() == 2e18); +// } + + function test_RevertIf_consentToNoPendingAmendment() public { address allocation = createDummyVestingAllocation(); bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); vm.prank(grantee); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_NoPendingAmendment.selector, msgSig, allocation)); controller.consentToMetavestAmendment(allocation, msgSig, true); } - function testEveryUpdateAmendmentFunction() public { - address allocation = createDummyTokenOptionAllocation(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - - msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 2e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation, 2e18); - - msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.removeMetavestMilestone(allocation, 0); - - msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(allocation, 20e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(allocation, 20e18); - - msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); - callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); - } - - function testEveryUpdateAmendmentFunctionRestricted() public { - address allocation = createDummyRestrictedTokenAward(); - bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestTransferability(allocation, true); - - msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 2e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateExerciseOrRepurchasePrice(allocation, 2e18); - - msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); - callData = abi.encodeWithSelector(msgSig, allocation, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.removeMetavestMilestone(allocation, 0); - - msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(allocation, 20e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, allocation, 20e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(allocation, 20e18); - - msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); - callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); - - vm.prank(authority); - controller.proposeMetavestAmendment(allocation, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(allocation, msgSig, true); - - vm.prank(authority); - controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); - } +// function testEveryUpdateAmendmentFunction() public { +// address allocation = createDummyTokenOptionAllocation(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(allocation, true); +// +// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); +// +// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 0); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.removeMetavestMilestone(allocation, 0); +// +// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestUnlockRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); +// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); +// } + +// function testEveryUpdateAmendmentFunctionRestricted() public { +// address allocation = createDummyRestrictedTokenAward(); +// bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestTransferability(allocation, true); +// +// msgSig = bytes4(keccak256("updateExerciseOrRepurchasePrice(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 2e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateExerciseOrRepurchasePrice(allocation, 2e18); +// +// msgSig = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 0); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.removeMetavestMilestone(allocation, 0); +// +// msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestUnlockRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, allocation, 20e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(allocation, 20e18); +// +// msgSig = bytes4(keccak256("setMetaVestGovVariables(address,uint8)")); +// callData = abi.encodeWithSelector(msgSig, allocation, BaseAllocation.GovType.vested); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(allocation, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(allocation, msgSig, true); +// +// vm.prank(authority); +// controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); +// } function testEveryUpdateAmendmentFunctionVesting() public { address allocation = createDummyVestingAllocation(); @@ -1364,7 +874,7 @@ contract MetaVestControllerTest is Test { controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); } - function testFailEveryUpdateAmendmentFunctionVesting() public { + function test_RevertIf_EveryUpdateAmendmentFunctionVesting() public { address allocation = createDummyVestingAllocation(); bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); bytes memory callData = abi.encodeWithSelector(msgSig, allocation, true); @@ -1421,6 +931,7 @@ contract MetaVestControllerTest is Test { controller.proposeMetavestAmendment(allocation, msgSig, callData); vm.prank(authority); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentNeitherMutualNorMajorityConsented.selector)); controller.setMetaVestGovVariables(allocation, BaseAllocation.GovType.vested); } -} \ No newline at end of file +} diff --git a/test/controller.t.sol b/test/controller.t.sol index 0dacf78..46de3a6 100644 --- a/test/controller.t.sol +++ b/test/controller.t.sol @@ -1,1258 +1,191 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.20; -import "forge-std/Test.sol"; -import "../src/metavestController.sol"; +//import "../src/RestrictedTokenAllocation.sol"; +//import "../src/RestrictedTokenFactory.sol"; +//import "../src/TokenOptionAllocation.sol"; +//import "../src/TokenOptionFactory.sol"; import "../src/VestingAllocation.sol"; -import "../src/TokenOptionAllocation.sol"; -import "../src/RestrictedTokenAllocation.sol"; -import "../src/interfaces/IAllocationFactory.sol"; import "../src/VestingAllocationFactory.sol"; -import "../src/TokenOptionFactory.sol"; -import "../src/RestrictedTokenFactory.sol"; +import "../src/interfaces/IAllocationFactory.sol"; +import "../src/interfaces/zk-governance/IZkTokenV1.sol"; +import "./lib/MetaVesTControllerTestBase.sol"; import "./mocks/MockCondition.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkTokenV2.sol"; -import "../lib/zk-governance/l2-contracts/src/ZkCappedMinterFactory.sol"; -import {console} from "forge-std/console.sol"; - -abstract contract ERC20 { - - - /// @dev The total supply has overflowed. - error TotalSupplyOverflow(); - - /// @dev The allowance has overflowed. - error AllowanceOverflow(); - - /// @dev The allowance has underflowed. - error AllowanceUnderflow(); - - /// @dev Insufficient balance. - error InsufficientBalance(); - - /// @dev Insufficient allowance. - error InsufficientAllowance(); - - /// @dev The permit is invalid. - error InvalidPermit(); - - /// @dev The permit has expired. - error PermitExpired(); - - - /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 amount); - - /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. - uint256 private constant _TRANSFER_EVENT_SIGNATURE = - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; - - /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. - uint256 private constant _APPROVAL_EVENT_SIGNATURE = - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; - - - /// @dev The storage slot for the total supply. - uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; - - /// @dev The balance slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _BALANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let balanceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; - - /// @dev The allowance slot of (`owner`, `spender`) is given by: - /// ``` - /// mstore(0x20, spender) - /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let allowanceSlot := keccak256(0x0c, 0x34) - /// ``` - uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; - - /// @dev The nonce slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _NONCES_SLOT_SEED) - /// mstore(0x00, owner) - /// let nonceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _NONCES_SLOT_SEED = 0x38377508; - - - /// @dev Returns the name of the token. - function name() public view virtual returns (string memory); - - /// @dev Returns the symbol of the token. - function symbol() public view virtual returns (string memory); - - /// @dev Returns the decimals places of the token. - function decimals() public view virtual returns (uint8) { - return 18; - } - - - /// @dev Returns the amount of tokens in existence. - function totalSupply() public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - result := sload(_TOTAL_SUPPLY_SLOT) - } - } - - /// @dev Returns the amount of tokens owned by `owner`. - function balanceOf(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. - function allowance(address owner, address spender) - public - view - virtual - returns (uint256 result) - { - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x34)) - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - /// - /// Emits a {Approval} event. - function approve(address spender, uint256 amount) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically increases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Add to the allowance. - let allowanceAfter := add(allowanceBefore, difference) - // Revert upon overflow. - if lt(allowanceAfter, allowanceBefore) { - mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated allowance. - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically decreases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Revert if will underflow. - if lt(allowanceBefore, difference) { - mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - let allowanceAfter := sub(allowanceBefore, difference) - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Transfer `amount` tokens from the caller to `to`. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(msg.sender, to, amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, caller()) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) - } - _afterTokenTransfer(msg.sender, to, amount); - return true; - } - - /// @dev Transfers `amount` tokens from `from` to `to`. - /// - /// Note: Does not update the allowance if it is the maximum uint256 value. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the allowance slot and load its value. - mstore(0x20, caller()) - mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - return true; - } - - - /// @dev Returns the current nonce for `owner`. - /// This value is used to compute the signature for EIP-2612 permit. - function nonces(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, - /// authorized by a signed approval by `owner`. - /// - /// Emits a {Approval} event. - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - bytes32 domainSeparator = DOMAIN_SEPARATOR(); - /// @solidity memory-safe-assembly - assembly { - // Grab the free memory pointer. - let m := mload(0x40) - // Revert if the block timestamp greater than `deadline`. - if gt(timestamp(), deadline) { - mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. - revert(0x1c, 0x04) - } - // Clean the upper 96 bits. - owner := shr(96, shl(96, owner)) - spender := shr(96, shl(96, spender)) - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - let nonceSlot := keccak256(0x0c, 0x20) - let nonceValue := sload(nonceSlot) - // Increment and store the updated nonce. - sstore(nonceSlot, add(nonceValue, 1)) - // Prepare the inner hash. - // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. - // forgefmt: disable-next-item - mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) - mstore(add(m, 0x20), owner) - mstore(add(m, 0x40), spender) - mstore(add(m, 0x60), value) - mstore(add(m, 0x80), nonceValue) - mstore(add(m, 0xa0), deadline) - // Prepare the outer hash. - mstore(0, 0x1901) - mstore(0x20, domainSeparator) - mstore(0x40, keccak256(m, 0xc0)) - // Prepare the ecrecover calldata. - mstore(0, keccak256(0x1e, 0x42)) - mstore(0x20, and(0xff, v)) - mstore(0x40, r) - mstore(0x60, s) - pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) - // If the ecrecover fails, the returndatasize will be 0x00, - // `owner` will be be checked if it equals the hash at 0x00, - // which evaluates to false (i.e. 0), and we will revert. - // If the ecrecover succeeds, the returndatasize will be 0x20, - // `owner` will be compared against the returned address at 0x20. - if iszero(eq(mload(returndatasize()), owner)) { - mstore(0x00, 0xddafbaef) // `InvalidPermit()`. - revert(0x1c, 0x04) - } - // Compute the allowance slot and store the value. - // The `owner` is already at slot 0x20. - mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) - sstore(keccak256(0x2c, 0x34), value) - // Emit the {Approval} event. - log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) - mstore(0x40, m) // Restore the free memory pointer. - mstore(0x60, 0) // Restore the zero pointer. - } - } - - /// @dev Returns the EIP-2612 domains separator. - function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) // Grab the free memory pointer. - } - // We simply calculate it on-the-fly to allow for cases where the `name` may change. - bytes32 nameHash = keccak256(bytes(name())); - /// @solidity memory-safe-assembly - assembly { - let m := result - // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - // forgefmt: disable-next-item - mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) - mstore(add(m, 0x20), nameHash) - // `keccak256("1")`. - // forgefmt: disable-next-item - mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) - mstore(add(m, 0x60), chainid()) - mstore(add(m, 0x80), address()) - result := keccak256(m, 0xa0) - } - } - - - /// @dev Mints `amount` tokens to `to`, increasing the total supply. - /// - /// Emits a {Transfer} event. - function _mint(address to, uint256 amount) internal virtual { - _beforeTokenTransfer(address(0), to, amount); - /// @solidity memory-safe-assembly - assembly { - let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) - let totalSupplyAfter := add(totalSupplyBefore, amount) - // Revert if the total supply overflows. - if lt(totalSupplyAfter, totalSupplyBefore) { - mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) - } - _afterTokenTransfer(address(0), to, amount); - } - - /// @dev Burns `amount` tokens from `from`, reducing the total supply. - /// - /// Emits a {Transfer} event. - function _burn(address from, uint256 amount) internal virtual { - _beforeTokenTransfer(from, address(0), amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, from) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Subtract and store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) - // Emit the {Transfer} event. - mstore(0x00, amount) - log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) - } - _afterTokenTransfer(from, address(0), amount); - } - - /// @dev Moves `amount` of tokens from `from` to `to`. - function _transfer(address from, address to, uint256 amount) internal virtual { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - } - - - /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. - /// - /// Emits a {Approval} event. - function _approve(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - let owner_ := shl(96, owner) - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) - } - } - - - /// @dev Hook that is called before any transfer of tokens. - /// This includes minting and burning. - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} +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"; - /// @dev Hook that is called after any transfer of tokens. - /// This includes minting and burning. - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} - -abstract contract ERC20Stable { - - /// @dev The total supply has overflowed. - error TotalSupplyOverflow(); - - /// @dev The allowance has overflowed. - error AllowanceOverflow(); - - /// @dev The allowance has underflowed. - error AllowanceUnderflow(); - - /// @dev Insufficient balance. - error InsufficientBalance(); - - /// @dev Insufficient allowance. - error InsufficientAllowance(); - - /// @dev The permit is invalid. - error InvalidPermit(); - - /// @dev The permit has expired. - error PermitExpired(); - - - /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. - event Transfer(address indexed from, address indexed to, uint256 amount); - - /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. - uint256 private constant _TRANSFER_EVENT_SIGNATURE = - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; - - /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. - uint256 private constant _APPROVAL_EVENT_SIGNATURE = - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; - - /// @dev The storage slot for the total supply. - uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; - - /// @dev The balance slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _BALANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let balanceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; - - /// @dev The allowance slot of (`owner`, `spender`) is given by: - /// ``` - /// mstore(0x20, spender) - /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) - /// mstore(0x00, owner) - /// let allowanceSlot := keccak256(0x0c, 0x34) - /// ``` - uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; - - /// @dev The nonce slot of `owner` is given by: - /// ``` - /// mstore(0x0c, _NONCES_SLOT_SEED) - /// mstore(0x00, owner) - /// let nonceSlot := keccak256(0x0c, 0x20) - /// ``` - uint256 private constant _NONCES_SLOT_SEED = 0x38377508; - - /// @dev Returns the name of the token. - function name() public view virtual returns (string memory); - - /// @dev Returns the symbol of the token. - function symbol() public view virtual returns (string memory); - - /// @dev Returns the decimals places of the token. - function decimals() public view virtual returns (uint8) { - return 6; - } - - /// @dev Returns the amount of tokens in existence. - function totalSupply() public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - result := sload(_TOTAL_SUPPLY_SLOT) - } - } - - /// @dev Returns the amount of tokens owned by `owner`. - function balanceOf(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. - function allowance(address owner, address spender) - public - view - virtual - returns (uint256 result) - { - /// @solidity memory-safe-assembly - assembly { - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x34)) - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - /// - /// Emits a {Approval} event. - function approve(address spender, uint256 amount) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically increases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Add to the allowance. - let allowanceAfter := add(allowanceBefore, difference) - // Revert upon overflow. - if lt(allowanceAfter, allowanceBefore) { - mstore(0x00, 0xf9067066) // `AllowanceOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated allowance. - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Atomically decreases the allowance granted to `spender` by the caller. - /// - /// Emits a {Approval} event. - function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, caller()) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowanceBefore := sload(allowanceSlot) - // Revert if will underflow. - if lt(allowanceBefore, difference) { - mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - let allowanceAfter := sub(allowanceBefore, difference) - sstore(allowanceSlot, allowanceAfter) - // Emit the {Approval} event. - mstore(0x00, allowanceAfter) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) - } - return true; - } - - /// @dev Transfer `amount` tokens from the caller to `to`. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - /// Emits a {Transfer} event. - function transfer(address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(msg.sender, to, amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, caller()) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) - } - _afterTokenTransfer(msg.sender, to, amount); - return true; - } - - /// @dev Transfers `amount` tokens from `from` to `to`. - /// - /// Note: Does not update the allowance if it is the maximum uint256 value. - /// - /// Requirements: - /// - `from` must at least have `amount`. - /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. - /// - /// Emits a {Transfer} event. - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the allowance slot and load its value. - mstore(0x20, caller()) - mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - return true; - } - - - /// @dev Returns the current nonce for `owner`. - /// This value is used to compute the signature for EIP-2612 permit. - function nonces(address owner) public view virtual returns (uint256 result) { - /// @solidity memory-safe-assembly - assembly { - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - result := sload(keccak256(0x0c, 0x20)) - } - } - - /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, - /// authorized by a signed approval by `owner`. - /// - /// Emits a {Approval} event. - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - bytes32 domainSeparator = DOMAIN_SEPARATOR(); - /// @solidity memory-safe-assembly - assembly { - // Grab the free memory pointer. - let m := mload(0x40) - // Revert if the block timestamp greater than `deadline`. - if gt(timestamp(), deadline) { - mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. - revert(0x1c, 0x04) - } - // Clean the upper 96 bits. - owner := shr(96, shl(96, owner)) - spender := shr(96, shl(96, spender)) - // Compute the nonce slot and load its value. - mstore(0x0c, _NONCES_SLOT_SEED) - mstore(0x00, owner) - let nonceSlot := keccak256(0x0c, 0x20) - let nonceValue := sload(nonceSlot) - // Increment and store the updated nonce. - sstore(nonceSlot, add(nonceValue, 1)) - // Prepare the inner hash. - // `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. - // forgefmt: disable-next-item - mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9) - mstore(add(m, 0x20), owner) - mstore(add(m, 0x40), spender) - mstore(add(m, 0x60), value) - mstore(add(m, 0x80), nonceValue) - mstore(add(m, 0xa0), deadline) - // Prepare the outer hash. - mstore(0, 0x1901) - mstore(0x20, domainSeparator) - mstore(0x40, keccak256(m, 0xc0)) - // Prepare the ecrecover calldata. - mstore(0, keccak256(0x1e, 0x42)) - mstore(0x20, and(0xff, v)) - mstore(0x40, r) - mstore(0x60, s) - pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)) - // If the ecrecover fails, the returndatasize will be 0x00, - // `owner` will be be checked if it equals the hash at 0x00, - // which evaluates to false (i.e. 0), and we will revert. - // If the ecrecover succeeds, the returndatasize will be 0x20, - // `owner` will be compared against the returned address at 0x20. - if iszero(eq(mload(returndatasize()), owner)) { - mstore(0x00, 0xddafbaef) // `InvalidPermit()`. - revert(0x1c, 0x04) - } - // Compute the allowance slot and store the value. - // The `owner` is already at slot 0x20. - mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) - sstore(keccak256(0x2c, 0x34), value) - // Emit the {Approval} event. - log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) - mstore(0x40, m) // Restore the free memory pointer. - mstore(0x60, 0) // Restore the zero pointer. - } - } - - /// @dev Returns the EIP-2612 domains separator. - function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { - result := mload(0x40) // Grab the free memory pointer. - } - // We simply calculate it on-the-fly to allow for cases where the `name` may change. - bytes32 nameHash = keccak256(bytes(name())); - /// @solidity memory-safe-assembly - assembly { - let m := result - // `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - // forgefmt: disable-next-item - mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f) - mstore(add(m, 0x20), nameHash) - // `keccak256("1")`. - // forgefmt: disable-next-item - mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6) - mstore(add(m, 0x60), chainid()) - mstore(add(m, 0x80), address()) - result := keccak256(m, 0xa0) - } - } - - /// @dev Mints `amount` tokens to `to`, increasing the total supply. - /// - /// Emits a {Transfer} event. - function _mint(address to, uint256 amount) internal virtual { - _beforeTokenTransfer(address(0), to, amount); - /// @solidity memory-safe-assembly - assembly { - let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) - let totalSupplyAfter := add(totalSupplyBefore, amount) - // Revert if the total supply overflows. - if lt(totalSupplyAfter, totalSupplyBefore) { - mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) - } - _afterTokenTransfer(address(0), to, amount); - } - - /// @dev Burns `amount` tokens from `from`, reducing the total supply. - /// - /// Emits a {Transfer} event. - function _burn(address from, uint256 amount) internal virtual { - _beforeTokenTransfer(from, address(0), amount); - /// @solidity memory-safe-assembly - assembly { - // Compute the balance slot and load its value. - mstore(0x0c, _BALANCE_SLOT_SEED) - mstore(0x00, from) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Subtract and store the updated total supply. - sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) - // Emit the {Transfer} event. - mstore(0x00, amount) - log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) - } - _afterTokenTransfer(from, address(0), amount); - } - - - /// @dev Moves `amount` of tokens from `from` to `to`. - function _transfer(address from, address to, uint256 amount) internal virtual { - _beforeTokenTransfer(from, to, amount); - /// @solidity memory-safe-assembly - assembly { - let from_ := shl(96, from) - // Compute the balance slot and load its value. - mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) - let fromBalanceSlot := keccak256(0x0c, 0x20) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x00, to) - let toBalanceSlot := keccak256(0x0c, 0x20) - // Add and store the updated balance of `to`. - // Will not overflow because the sum of all user balances - // cannot exceed the maximum uint256 value. - sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) - // Emit the {Transfer} event. - mstore(0x20, amount) - log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) - } - _afterTokenTransfer(from, to, amount); - } - - - /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - // Compute the allowance slot and load its value. - mstore(0x20, spender) - mstore(0x0c, _ALLOWANCE_SLOT_SEED) - mstore(0x00, owner) - let allowanceSlot := keccak256(0x0c, 0x34) - let allowance_ := sload(allowanceSlot) - // If the allowance is not the maximum uint256 value. - if iszero(eq(allowance_, not(0))) { - // Revert if the amount to be transferred exceeds the allowance. - if gt(amount, allowance_) { - mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated allowance. - sstore(allowanceSlot, sub(allowance_, amount)) - } - } - } - - /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. - /// - /// Emits a {Approval} event. - function _approve(address owner, address spender, uint256 amount) internal virtual { - /// @solidity memory-safe-assembly - assembly { - let owner_ := shl(96, owner) - // Compute the allowance slot and store the amount. - mstore(0x20, spender) - mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) - sstore(keccak256(0x0c, 0x34), amount) - // Emit the {Approval} event. - mstore(0x00, amount) - log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) - } - } - - /// @dev Hook that is called before any transfer of tokens. - /// This includes minting and burning. - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} - - /// @dev Hook that is called after any transfer of tokens. - /// This includes minting and burning. - function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} - -contract MockERC20 is ERC20 { - constructor(string memory name, string memory symbol) ERC20() {} - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - function name() public view override returns (string memory) { - return "Test Token"; - } - function symbol() public view override returns (string memory) { - return "TT"; - } -} - -contract MockERC20Stable is ERC20Stable { - constructor(string memory name, string memory symbol) ERC20Stable() {} - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - function name() public view override returns (string memory) { - return "Test Token"; - } - function symbol() public view override returns (string memory) { - return "TT"; - } -} - -contract MetaVestControllerTest is Test { - metavestController public controller; - MockERC20 public token; - MockERC20Stable public paymentToken; - - address public authority; - address public dao; - address public grantee; - address public transferee; - - function setUp() public { - authority = address(this); - dao = address(0x2); - grantee = address(0x3); - transferee = address(0x4); - - token = new MockERC20("Test Token", "TT"); - paymentToken = new MockERC20Stable("Payment Token", "PT"); - - VestingAllocationFactory factory = new VestingAllocationFactory(); - TokenOptionFactory tokenFactory = new TokenOptionFactory(); - RestrictedTokenFactory restrictedTokenFactory = new RestrictedTokenFactory(); - - ZkTokenV2 zkToken = new ZkTokenV2(); - ZkCappedMinterFactory zkMinterFactory = new ZkCappedMinterFactory(0x073749a0f8ed0d49b1acfd4e0efdc59328c83d0c2eed9ee099a3979f0c332ff8); - - - controller = new metavestController( - authority, - dao, - address(factory), - address(tokenFactory), - address(restrictedTokenFactory), - address(zkMinterFactory), - address(zkToken) - ); +contract MetaVestControllerTest is MetaVesTControllerTestBase { + using ERC1967ProxyLib for address; + + address authority = guardianSafe; + address dao = guardianSafe; + address grantee = alice; + address transferee = address(0x101); + + // Parameters + uint256 cap = 2000 ether; + uint48 cappedMinterStartTime = uint48(block.timestamp); // Minter start now + uint48 cappedMinterExpirationTime = uint48(cappedMinterStartTime + 1600); // Minter expired 1600 seconds after start + + function setUp() public override { + MetaVesTControllerTestBase.setUp(); + + vm.startPrank(deployer); + + // Deploy MetaVesT controller + + vestingAllocationFactory = new VestingAllocationFactory(); + + controller = metavestController(address(new ERC1967Proxy{salt: salt}( + address(new metavestController{salt: salt}()), + abi.encodeWithSelector( + metavestController.initialize.selector, + guardianSafe, + guardianSafe, + address(registry), + address(vestingAllocationFactory) + ) + ))); + + 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)); - token.mint(authority, 1000000e58); - paymentToken.mint(authority, 1000000e58); - paymentToken.transfer(grantee, 1e25); + vm.stopPrank(); - vm.prank(authority); + vm.startPrank(guardianSafe); + controller.setZkCappedMinter(address(zkCappedMinter)); // Guardian SAFE to set capped minter on MetaVesTController controller.createSet("testSet"); - } - - function testCreateVestingAllocation() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - - address vestingAllocation = controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - - ); - - assertEq(token.balanceOf(vestingAllocation), 1100e18); - // assertEq(controller.vestingAllocations(grantee, 0), vestingAllocation); - } - - function testCreateTokenOptionAllocation() public { - bytes32 bytecodeHash = keccak256(type(ZkCappedMinter).creationCode); - console.logBytes32(bytecodeHash); - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - - address tokenOptionAllocation = controller.createMetavest( - metavestController.metavestType.TokenOption, - grantee, - allocation, - milestones, - 1e18, - address(paymentToken), - 365 days, - 0 - ); + vm.stopPrank(); - assertEq(token.balanceOf(tokenOptionAllocation), 1100e18); - //assertEq(controller.tokenOptionAllocations(grantee, 0), tokenOptionAllocation); + // Guardian SAFE to delegate signing to an EOA + vm.prank(guardianSafe); + registry.setDelegation(delegate, block.timestamp + 365 days * 3); // This is a hack. One should not delegate signing for this long + assertTrue(registry.isValidDelegate(guardianSafe, delegate), "delegate should be Guardian SAFE's delegate"); } - function testCreateRestrictedTokenAward() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 100e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 1100e18); - - address restrictedTokenAward = controller.createMetavest( - metavestController.metavestType.RestrictedTokenAward, - grantee, - allocation, - milestones, - 1e18, - address(paymentToken), - 365 days, - 0 - + function testCreateVestingAllocation() public { + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 60 ether, + vestingCliffCredit: 30 ether, + unlockingCliffCredit: 30 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - assertEq(token.balanceOf(restrictedTokenAward), 1100e18); - //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); - } + VestingAllocation vestingAllocationAlice = VestingAllocation(_granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" + )); + + assertEq(controller.getDeal(contractIdAlice).metavest, address(vestingAllocationAlice), "deal data should be updated with MetaVesT address"); + + // Grantees should be able to withdraw all remaining tokens after sufficient time passed + skip(61); + _granteeWithdrawAndAsserts(vestingAllocationAlice, 60 ether, "Alice full"); + } + +// function testCreateTokenOptionAllocation() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// address tokenOptionAllocation = controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// ); +// +// assertEq(zkToken.balanceOf(address(tokenOptionAllocation)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// //assertEq(controller.tokenOptionAllocations(grantee, 0), tokenOptionAllocation); +// } + +// function testCreateRestrictedTokenAward() public { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(zkToken), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 100e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// address restrictedTokenAward = controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 365 days, +// 0 +// +// ); +// +// assertEq(zkToken.balanceOf(address(restrictedTokenAward)), 0, "Vesting contract should not have any token (it mints on-demand)"); +// //assertEq(controller.restrictedTokenAllocations(grantee, 0), restrictedTokenAward); +// } function testUpdateTransferability() public { uint256 startTimestamp = block.timestamp; @@ -1262,27 +195,28 @@ contract MetaVestControllerTest is Test { //compute msg.data for updateMetavestTransferability(vestingAllocation, true) bytes4 selector = controller.updateMetavestTransferability.selector; bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, true); + vm.prank(authority); controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, msgData); vm.prank(grantee); controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestTransferability.selector, true); - + vm.prank(authority); controller.updateMetavestTransferability(vestingAllocation, true); vm.prank(grantee); - RestrictedTokenAward(vestingAllocation).transferRights(transferee); + VestingAllocation(vestingAllocation).transferRights(transferee); vm.prank(transferee); - RestrictedTokenAward(vestingAllocation).confirmTransfer(); + VestingAllocation(vestingAllocation).confirmTransfer(); uint256 newTimestamp = startTimestamp + 100; // 101 vm.warp(newTimestamp); skip(10); vm.prank(transferee); - uint256 balance = RestrictedTokenAward(vestingAllocation).getAmountWithdrawable(); - + uint256 balance = VestingAllocation(vestingAllocation).getAmountWithdrawable(); + //warp ahead 100 blocks - + vm.prank(transferee); - RestrictedTokenAward(vestingAllocation).withdraw(balance); - + VestingAllocation(vestingAllocation).withdraw(balance); + // assertTrue(BaseAllocation(vestingAllocation).transferable()); } @@ -1292,25 +226,25 @@ contract MetaVestControllerTest is Test { } function testProposeMajorityMetavestAmendment() public { - address mockAllocation2 = createDummyVestingAllocation(); + address vestingAllocation = createDummyVestingAllocation(); bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); - bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, true); vm.prank(authority); - controller.addMetaVestToSet("testSet", mockAllocation2); + controller.addMetaVestToSet("testSet", vestingAllocation); vm.warp(block.timestamp + 1 days); vm.prank(authority); controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); vm.prank(grantee); - controller.voteOnMetavestAmendment(mockAllocation2, "testSet", msgSig, true); + controller.voteOnMetavestAmendment(vestingAllocation, "testSet", msgSig, true); vm.prank(authority); - controller.updateMetavestTransferability(mockAllocation2, true); + controller.updateMetavestTransferability(vestingAllocation, true); } - - function testFailReProposeMajorityMetavestAmendment() public { + + function test_RevertIf_ReProposeMajorityMetavestAmendment() public { address mockAllocation2 = createDummyVestingAllocation(); bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)")); bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation2, true); @@ -1327,8 +261,9 @@ contract MetaVestControllerTest is Test { vm.prank(authority); controller.updateMetavestTransferability(mockAllocation2, true);*/ + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_AmendmentAlreadyPending.selector)); + vm.prank(authority); controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - } function testReProposeMajorityMetavestAmendment() public { @@ -1348,33 +283,34 @@ contract MetaVestControllerTest is Test { vm.prank(authority); controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData); - + } - function testFailRemoveNonExistantMetaVestFromSet() public { + function test_RevertIf_RemoveNonExistantMetaVestFromSet() public { address mockAllocation2 = createDummyVestingAllocation(); vm.startPrank(authority); // controller.createSet("testSet"); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_MetaVestNotInSet.selector)); controller.removeMetaVestFromSet("testSet", mockAllocation2); } - function testUpdateExercisePrice() public { - address tokenOptionAllocation = createDummyTokenOptionAllocation(); - - //compute msg.data for updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18) - bytes4 selector = controller.updateExerciseOrRepurchasePrice.selector; - bytes memory msgData = abi.encodeWithSelector(selector, tokenOptionAllocation, 2e18); - - controller.proposeMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, msgData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, true); - - controller.updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18); - - assertEq(TokenOptionAllocation(tokenOptionAllocation).exercisePrice(), 2e18); - } +// function testUpdateExercisePrice() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// +// //compute msg.data for updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18) +// bytes4 selector = controller.updateExerciseOrRepurchasePrice.selector; +// bytes memory msgData = abi.encodeWithSelector(selector, tokenOptionAllocation, 2e18); +// +// controller.proposeMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, msgData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(tokenOptionAllocation, controller.updateExerciseOrRepurchasePrice.selector, true); +// +// controller.updateExerciseOrRepurchasePrice(tokenOptionAllocation, 2e18); +// +// assertEq(TokenOptionAllocation(tokenOptionAllocation).exercisePrice(), 2e18); +// } function testRemoveMilestone() public { address vestingAllocation = createDummyVestingAllocation(); @@ -1383,29 +319,31 @@ contract MetaVestControllerTest is Test { addresses[0] = vestingAllocation; bytes4 selector = bytes4(keccak256("removeMetavestMilestone(address,uint256)")); bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); controller.proposeMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, msgData); vm.prank(grantee); //consent to amendment for the removemetavestmilestone method sig function consentToMetavestAmendment(address _metavest, bytes4 _msgSig, bool _inFavor) external { controller.consentToMetavestAmendment(vestingAllocation, controller.removeMetavestMilestone.selector, true); + vm.prank(authority); controller.removeMetavestMilestone(vestingAllocation, 0); - + //BaseAllocation.Milestone memory milestone = BaseAllocation(vestingAllocation).milestones(0); //assertEq(milestone.milestoneAward, 0); } function testAddMilestone() public { address vestingAllocation = createDummyVestingAllocation(); - + BaseAllocation.Milestone memory newMilestone = BaseAllocation.Milestone({ milestoneAward: 50e18, unlockOnCompletion: true, complete: false, conditionContracts: new address[](0) }); - - token.approve(address(controller), 50e18); + + vm.prank(authority); controller.addMetavestMilestone(vestingAllocation, newMilestone); - + // BaseAllocation.Milestone memory addedMilestone = BaseAllocation(vestingAllocation).milestones[0]; // assertEq(addedMilestone.milestoneAward, 50e18); } @@ -1416,12 +354,13 @@ contract MetaVestControllerTest is Test { addresses[0] = vestingAllocation; bytes4 selector = controller.updateMetavestUnlockRate.selector; bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); + vm.prank(authority); controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); vm.prank(grantee); controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - + vm.prank(authority); controller.updateMetavestUnlockRate(vestingAllocation, 20e18); - + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); assertEq(updatedAllocation.unlockRate, 20e18); } @@ -1432,59 +371,66 @@ contract MetaVestControllerTest is Test { addresses[0] = vestingAllocation; bytes4 selector = controller.updateMetavestUnlockRate.selector; bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); vm.prank(grantee); controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - + vm.prank(authority); controller.updateMetavestUnlockRate(vestingAllocation, 0); - + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); assertEq(updatedAllocation.unlockRate, 0); - + vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); - + vm.prank(authority); controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); assertEq(updatedAllocation.unlockRate, 1e20); } - function testFailUpdateUnlockRateZeroEmergency() public { + function test_RevertIf_UpdateUnlockRateZeroEmergency() public { address vestingAllocation = createDummyVestingAllocation(); address[] memory addresses = new address[](1); addresses[0] = vestingAllocation; bytes4 selector = controller.updateMetavestUnlockRate.selector; bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); vm.prank(grantee); controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - + vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); + vm.prank(authority); controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 1e20); + assertEq(updatedAllocation.unlockRate, 10e18); } - function testFailUpdateUnlockRateZeroEmergencyTerminated() public { + function test_RevertIf_UpdateUnlockRateZeroEmergencyTerminated() public { address vestingAllocation = createDummyVestingAllocation(); address[] memory addresses = new address[](1); addresses[0] = vestingAllocation; bytes4 selector = controller.updateMetavestUnlockRate.selector; bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 0); + vm.prank(authority); controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, msgData); vm.prank(grantee); controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); vm.prank(grantee); controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestUnlockRate.selector, true); - + vm.prank(authority); controller.updateMetavestUnlockRate(vestingAllocation, 0); - + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); assertEq(updatedAllocation.unlockRate, 0); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_EmergencyUnlockNotSatisfied.selector)); + vm.prank(authority); controller.emergencyUpdateMetavestUnlockRate(vestingAllocation, 1e20); - updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); - assertEq(updatedAllocation.unlockRate, 1e20); + updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); + assertEq(updatedAllocation.unlockRate, 0); } function testUpdateVestingRate() public { @@ -1493,867 +439,866 @@ contract MetaVestControllerTest is Test { addresses[0] = vestingAllocation; bytes4 selector = controller.updateMetavestVestingRate.selector; bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, 20e18); + vm.prank(authority); controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, msgData); vm.prank(grantee); controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestVestingRate.selector, true); - + vm.prank(authority); controller.updateMetavestVestingRate(vestingAllocation, 20e18); - + BaseAllocation.Allocation memory updatedAllocation = BaseAllocation(vestingAllocation).getMetavestDetails(); assertEq(updatedAllocation.vestingRate, 20e18); } - function testUpdateStopTimes() public { - - address vestingAllocation = createDummyRestrictedTokenAward(); - address[] memory addresses = new address[](1); - addresses[0] = vestingAllocation; - bytes4 selector = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); - bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, uint48(block.timestamp + 500 days)); - controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, msgData); - vm.prank(grantee); - controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, true); - uint48 newShortStopTime = uint48(block.timestamp + 500 days); - - controller.updateMetavestStopTimes(vestingAllocation, newShortStopTime); - } +// function testUpdateStopTimes() public { +// +// address vestingAllocation = createDummyRestrictedTokenAward(); +// address[] memory addresses = new address[](1); +// addresses[0] = vestingAllocation; +// bytes4 selector = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); +// bytes memory msgData = abi.encodeWithSelector(selector, vestingAllocation, uint48(block.timestamp + 500 days)); +// controller.proposeMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, msgData); +// vm.prank(grantee); +// controller.consentToMetavestAmendment(vestingAllocation, controller.updateMetavestStopTimes.selector, true); +// uint48 newShortStopTime = uint48(block.timestamp + 500 days); +// +// controller.updateMetavestStopTimes(vestingAllocation, newShortStopTime); +// } function testTerminateVesting() public { address vestingAllocation = createDummyVestingAllocation(); - - controller.terminateMetavestVesting(vestingAllocation); - - assertTrue(BaseAllocation(vestingAllocation).terminated()); - } - - function testRepurchaseTokens() public { - uint256 startingBalance = paymentToken.balanceOf(grantee); - address restrictedTokenAward = createDummyRestrictedTokenAward(); - uint256 repurchaseAmount = 5e18; - uint256 snapshot = token.balanceOf(authority); - uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); - controller.terminateMetavestVesting(restrictedTokenAward); - paymentToken.approve(address(restrictedTokenAward), payment); - vm.warp(block.timestamp + 20 days); vm.prank(authority); - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); - - assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); + controller.terminateMetavestVesting(vestingAllocation); - vm.prank(grantee); - RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); - assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); + assertTrue(BaseAllocation(vestingAllocation).terminated()); } - function testRepurchaseTokensFuture() public { - uint256 startingBalance = paymentToken.balanceOf(grantee); - address restrictedTokenAward = createDummyRestrictedTokenAwardFuture(); - - uint256 snapshot = token.balanceOf(authority); - - controller.terminateMetavestVesting(restrictedTokenAward); - uint256 repurchaseAmount = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); - uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); - paymentToken.approve(address(restrictedTokenAward), payment); - vm.warp(block.timestamp + 20 days); - vm.prank(authority); - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); - - assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); - - vm.prank(grantee); - RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); - console.log(token.balanceOf(restrictedTokenAward)); - assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); - - } +// function testRepurchaseTokens() public { +// uint256 startingBalance = paymentToken.balanceOf(grantee); +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 repurchaseAmount = 5e18; +// uint256 snapshot = token.balanceOf(authority); +// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); +// controller.terminateMetavestVesting(restrictedTokenAward); +// paymentToken.approve(address(restrictedTokenAward), payment); +// vm.warp(block.timestamp + 20 days); +// vm.prank(authority); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +// +// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +// +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +// } + +// function testRepurchaseTokensFuture() public { +// uint256 startingBalance = paymentToken.balanceOf(grantee); +// address restrictedTokenAward = createDummyRestrictedTokenAwardFuture(); +// +// uint256 snapshot = token.balanceOf(authority); +// +// controller.terminateMetavestVesting(restrictedTokenAward); +// uint256 repurchaseAmount = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payment = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(repurchaseAmount); +// paymentToken.approve(address(restrictedTokenAward), payment); +// vm.warp(block.timestamp + 20 days); +// vm.prank(authority); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(repurchaseAmount); +// +// assertEq(token.balanceOf(authority), snapshot+repurchaseAmount); +// +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); +// assertEq(paymentToken.balanceOf(grantee), startingBalance + payment); +// +// } function testTerminateTokensFuture() public { - uint256 startingBalance = paymentToken.balanceOf(grantee); - address restrictedTokenAward = createDummyVestingAllocationLargeFuture(); - - controller.terminateMetavestVesting(restrictedTokenAward); - - console.log(token.balanceOf(restrictedTokenAward)); + address vestingAllocation = createDummyVestingAllocationLargeFuture(); + vm.prank(authority); + controller.terminateMetavestVesting(vestingAllocation); } function testUpdateAuthority() public { address newAuthority = address(0x4); - + vm.prank(authority); controller.initiateAuthorityUpdate(newAuthority); - + vm.prank(newAuthority); controller.acceptAuthorityRole(); - + assertEq(controller.authority(), newAuthority); } function testUpdateDao() public { address newDao = address(0x5); - + vm.prank(dao); controller.initiateDaoUpdate(newDao); - + vm.prank(newDao); controller.acceptDaoRole(); - + assertEq(controller.dao(), newDao); } // Helper functions to create dummy allocations for testing function createDummyVestingAllocation() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); + return createDummyVestingAllocation(""); // Expect no reverts + } + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocation(bytes memory expectRevertData) internal returns (address) { BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, + milestoneAward: 1000 ether, unlockOnCompletion: true, complete: false, conditionContracts: new address[](0) }); - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, // = grantee + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), milestones, - 0, - address(0), - 0, - 0 - + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + ); + + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice", + expectRevertData ); } - // Helper functions to create dummy allocations for testing + // Helper functions to create dummy allocations for testing function createDummyVestingAllocationNoUnlock() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, + milestoneAward: 1000 ether, unlockOnCompletion: false, complete: false, conditionContracts: new address[](0) }); - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, // = grantee + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), milestones, - 0, - address(0), - 0, - 0 - + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + ); + + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" ); } - // Helper functions to create dummy allocations for testing + // Helper functions to create dummy allocations for testing function createDummyVestingAllocationSlowUnlock() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 5e18, - unlockStartTime: uint48(block.timestamp) - }); - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, + milestoneAward: 1000 ether, unlockOnCompletion: true, complete: false, conditionContracts: new address[](0) }); - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, // = grantee + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 5 ether, + unlockStartTime: uint48(block.timestamp) + }), milestones, - 0, - address(0), - 0, - 0 - + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - } - - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationLarge() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 0, - unlockingCliffCredit: 0, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" ); } - // Helper functions to create dummy allocations for testing - function createDummyVestingAllocationLargeFuture() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 0, - unlockingCliffCredit: 0, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp+2000), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp+2000) - }); - + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationLarge() internal returns (address) { BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, // = grantee + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 0 ether, + unlockingCliffCredit: 0 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), milestones, - 0, - address(0), - 0, - 0 - + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - } - - function createDummyTokenOptionAllocation() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - token.approve(address(controller), 2000e18); - - return controller.createMetavest( - metavestController.metavestType.TokenOption, - grantee, - allocation, - milestones, - 5e17, - address(paymentToken), - 1 days, - 0 + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" ); } + // Helper functions to create dummy allocations for testing + function createDummyVestingAllocationLargeFuture() internal returns (address) { + BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - function createDummyRestrictedTokenAward() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.RestrictedTokenAward, - grantee, - allocation, + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, // = grantee + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 0 ether, + unlockingCliffCredit: 0 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp + 2000), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp + 2000) + }), milestones, - 1e18, - address(paymentToken), - 1 days, - 0 - + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - } - - function createDummyRestrictedTokenAwardFuture() internal returns (address) { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp+1000), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp+1000) - }); - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); - milestones[0] = BaseAllocation.Milestone({ - milestoneAward: 1000e18, - unlockOnCompletion: true, - complete: false, - conditionContracts: new address[](0) - }); - - token.approve(address(controller), 2100e18); - - return controller.createMetavest( - metavestController.metavestType.RestrictedTokenAward, - grantee, - allocation, - milestones, - 1e18, - address(paymentToken), - 1 days, - 0 - + return _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" ); } +// function createDummyTokenOptionAllocation() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 1000e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 2000e18); +// +// return controller.createMetavest( +// metavestController.metavestType.TokenOption, +// grantee, +// allocation, +// milestones, +// 5e17, +// address(paymentToken), +// 1 days, +// 0 +// ); +// } + + +// function createDummyRestrictedTokenAward() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 1000e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 2100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 1 days, +// 0 +// +// ); +// } +// +// function createDummyRestrictedTokenAwardFuture() internal returns (address) { +// BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ +// tokenContract: address(token), +// tokenStreamTotal: 1000e18, +// vestingCliffCredit: 100e18, +// unlockingCliffCredit: 100e18, +// vestingRate: 10e18, +// vestingStartTime: uint48(block.timestamp+1000), +// unlockRate: 10e18, +// unlockStartTime: uint48(block.timestamp+1000) +// }); +// +// BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](1); +// milestones[0] = BaseAllocation.Milestone({ +// milestoneAward: 1000e18, +// unlockOnCompletion: true, +// complete: false, +// conditionContracts: new address[](0) +// }); +// +// token.approve(address(controller), 2100e18); +// +// return controller.createMetavest( +// metavestController.metavestType.RestrictedTokenAward, +// grantee, +// allocation, +// milestones, +// 1e18, +// address(paymentToken), +// 1 days, +// 0 +// +// ); +// } + function testGetMetaVestType() public { address vestingAllocation = createDummyVestingAllocation(); - address tokenOptionAllocation = createDummyTokenOptionAllocation(); - address restrictedTokenAward = createDummyRestrictedTokenAward(); +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// address restrictedTokenAward = createDummyRestrictedTokenAward(); assertEq(controller.getMetaVestType(vestingAllocation), 1); - assertEq(controller.getMetaVestType(tokenOptionAllocation), 2); - assertEq(controller.getMetaVestType(restrictedTokenAward), 3); - } - - function testWithdrawFromController() public { - uint256 amount = 100e18; - token.transfer(address(controller), amount); - - uint256 initialBalance = token.balanceOf(authority); - controller.withdrawFromController(address(token)); - uint256 finalBalance = token.balanceOf(authority); - - assertEq(finalBalance - initialBalance, amount); - assertEq(token.balanceOf(address(controller)), 0); - } - - function testFailWithdrawFromControllerNonAuthority() public { - vm.prank(address(0x1234)); - controller.withdrawFromController(address(token)); - } - - function testFailCreateMetavestWithZeroAddress() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(0), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - +// assertEq(controller.getMetaVestType(tokenOptionAllocation), 2); +// assertEq(controller.getMetaVestType(restrictedTokenAward), 3); + } + +// function testWithdrawFromController() public { +// uint256 amount = 100e18; +// token.transfer(address(controller), amount); +// +// uint256 initialBalance = token.balanceOf(authority); +// controller.withdrawFromController(address(token)); +// uint256 finalBalance = token.balanceOf(authority); +// +// assertEq(finalBalance - initialBalance, amount); +// assertEq(token.balanceOf(address(controller)), 0); +// } + + function test_RevertIf_CreateMetavestWithZeroAddress() public { BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - controller.createMetavest( - metavestController.metavestType.Vesting, - address(0), - allocation, + // Guardians to sign agreements and register on MetaVesTController + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, // = grantee + BaseAllocation.Allocation({ + tokenContract: address(0), // zero address + tokenStreamTotal: 1000 ether, + vestingCliffCredit: 100 ether, + unlockingCliffCredit: 100 ether, + vestingRate: 10 ether, + vestingStartTime: uint48(block.timestamp), + unlockRate: 10 ether, + unlockStartTime: uint48(block.timestamp) + }), milestones, - 0, - address(0), - 0, - 0 - + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible ); - } - - function testFailCreateMetavestWithInsufficientApproval() public { - BaseAllocation.Allocation memory allocation = BaseAllocation.Allocation({ - tokenContract: address(token), - tokenStreamTotal: 1000e18, - vestingCliffCredit: 100e18, - unlockingCliffCredit: 100e18, - vestingRate: 10e18, - vestingStartTime: uint48(block.timestamp), - unlockRate: 10e18, - unlockStartTime: uint48(block.timestamp) - }); - BaseAllocation.Milestone[] memory milestones = new BaseAllocation.Milestone[](0); - - // Not approving any tokens - controller.createMetavest( - metavestController.metavestType.Vesting, - grantee, - allocation, - milestones, - 0, - address(0), - 0, - 0 - + _granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice", + abi.encodeWithSelector(metavestController.MetaVesTController_ZeroAddress.selector) ); } function testTerminateVestAndRecovers() public { address vestingAllocation = createDummyVestingAllocation(); - uint256 snapshot = token.balanceOf(authority); + uint256 snapshot = zkToken.balanceOf(authority); VestingAllocation(vestingAllocation).confirmMilestone(0); vm.warp(block.timestamp + 50 seconds); + vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(token.balanceOf(vestingAllocation), 0); + assertEq(zkToken.balanceOf(authority), 0); } function testTerminateVestAndRecoverSlowUnlock() public { address vestingAllocation = createDummyVestingAllocationSlowUnlock(); - uint256 snapshot = token.balanceOf(authority); + uint256 snapshot = zkToken.balanceOf(authority); VestingAllocation(vestingAllocation).confirmMilestone(0); vm.warp(block.timestamp + 25 seconds); + vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.warp(block.timestamp + 25 seconds); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(token.balanceOf(vestingAllocation), 0); + assertEq(zkToken.balanceOf(vestingAllocation), 0); } function testTerminateRecoverAll() public { address vestingAllocation = createDummyVestingAllocationLarge(); - uint256 snapshot = token.balanceOf(authority); + uint256 snapshot = zkToken.balanceOf(authority); vm.warp(block.timestamp + 25 seconds); + vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(token.balanceOf(vestingAllocation), 0); + assertEq(zkToken.balanceOf(authority), 0); } function testTerminateRecoverChunksBefore() public { address vestingAllocation = createDummyVestingAllocationLarge(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); + uint256 snapshot = zkToken.balanceOf(authority); + vm.warp(block.timestamp + 25 seconds); vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - vm.warp(block.timestamp + 25 seconds); - + vm.warp(block.timestamp + 25 seconds); + vm.prank(authority); controller.terminateMetavestVesting(vestingAllocation); vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(token.balanceOf(vestingAllocation), 0); - } - - function testConfirmingMilestoneRestrictedTokenAllocation() public { - address vestingAllocation = createDummyRestrictedTokenAward(); - uint256 snapshot = token.balanceOf(authority); - RestrictedTokenAward(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } - - function testConfirmingMilestoneTokenOption() public { - address vestingAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - TokenOptionAllocation(vestingAllocation).confirmMilestone(0); - vm.warp(block.timestamp + 50 seconds); - vm.startPrank(grantee); - //exercise max available - ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); - TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); - TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - vm.stopPrank(); - } + assertEq(zkToken.balanceOf(authority), 0); + } + +// function testConfirmingMilestoneRestrictedTokenAllocation() public { +// address vestingAllocation = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// VestingAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } +// +// function testConfirmingMilestoneTokenOption() public { +// address vestingAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// TokenOptionAllocation(vestingAllocation).confirmMilestone(0); +// vm.warp(block.timestamp + 50 seconds); +// vm.startPrank(grantee); +// //exercise max available +// ERC20Stable(paymentToken).approve(vestingAllocation, TokenOptionAllocation(vestingAllocation).getPaymentAmount(TokenOptionAllocation(vestingAllocation).getAmountExercisable())); +// TokenOptionAllocation(vestingAllocation).exerciseTokenOption(TokenOptionAllocation(vestingAllocation).getAmountExercisable()); +// TokenOptionAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// } function testUnlockMilestoneNotUnlocked() public { address vestingAllocation = createDummyVestingAllocationNoUnlock(); - uint256 snapshot = token.balanceOf(authority); + uint256 snapshot = zkToken.balanceOf(authority); VestingAllocation(vestingAllocation).confirmMilestone(0); vm.warp(block.timestamp + 50 seconds); vm.startPrank(grantee); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - console.log(token.balanceOf(vestingAllocation)); vm.warp(block.timestamp + 1050 seconds); VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); - console.log(token.balanceOf(vestingAllocation)); - vm.stopPrank(); - } - - function testTerminateTokenOptionAndRecover() public { - address tokenOptionAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - vm.prank(grantee); - ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); - vm.prank(grantee); - TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18); - controller.terminateMetavestVesting(tokenOptionAllocation); - vm.startPrank(grantee); - vm.warp(block.timestamp + 1 days + 25 seconds); - assertEq(TokenOptionAllocation(tokenOptionAllocation).getAmountExercisable(), 0); - TokenOptionAllocation(tokenOptionAllocation).withdraw(TokenOptionAllocation(tokenOptionAllocation).getAmountWithdrawable()); vm.stopPrank(); - assertEq(token.balanceOf(tokenOptionAllocation), 0); - vm.warp(block.timestamp + 365 days); - vm.prank(authority); - TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); - } - - function testTerminateEarlyTokenOptionAndRecover() public { - address tokenOptionAllocation = createDummyTokenOptionAllocation(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 5 seconds); - // vm.prank(grantee); - /* ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); - vm.prank(grantee); - TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18);*/ - controller.terminateMetavestVesting(tokenOptionAllocation); - vm.warp(block.timestamp + 365 days); - vm.prank(authority); - TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); } - - function testTerminateRestrictedTokenAwardAndRecover() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); - uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); - vm.warp(block.timestamp + 20 days); - paymentToken.approve(address(restrictedTokenAward), payamt); - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); - - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); - assertEq(token.balanceOf(restrictedTokenAward), 0); - assertEq(paymentToken.balanceOf(restrictedTokenAward), 0); - } - - function testChangeVestingAndUnlockingRate() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - uint256 snapshot = token.balanceOf(authority); - vm.warp(block.timestamp + 25 seconds); - - bytes4 msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestUnlockRate(restrictedTokenAward, 50e18); - - msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 50e18); - - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - - } - - function testZeroReclaim() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - vm.warp(block.timestamp + 15 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 0); - - vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.warp(block.timestamp + 155 days); - uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); - uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); - paymentToken.approve(address(restrictedTokenAward), payamt); - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); - vm.stopPrank(); - vm.prank(grantee); - RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); - console.log(token.balanceOf(restrictedTokenAward)); - } +// function testTerminateTokenOptionAndRecover() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// vm.prank(grantee); +// ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); +// vm.prank(grantee); +// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18); +// controller.terminateMetavestVesting(tokenOptionAllocation); +// vm.startPrank(grantee); +// vm.warp(block.timestamp + 1 days + 25 seconds); +// assertEq(TokenOptionAllocation(tokenOptionAllocation).getAmountExercisable(), 0); +// TokenOptionAllocation(tokenOptionAllocation).withdraw(TokenOptionAllocation(tokenOptionAllocation).getAmountWithdrawable()); +// vm.stopPrank(); +// assertEq(token.balanceOf(tokenOptionAllocation), 0); +// vm.warp(block.timestamp + 365 days); +// vm.prank(authority); +// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +// } + +// function testTerminateEarlyTokenOptionAndRecover() public { +// address tokenOptionAllocation = createDummyTokenOptionAllocation(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 5 seconds); +// // vm.prank(grantee); +// /* ERC20Stable(paymentToken).approve(tokenOptionAllocation, 350e18); +// vm.prank(grantee); +// TokenOptionAllocation(tokenOptionAllocation).exerciseTokenOption(350e18);*/ +// controller.terminateMetavestVesting(tokenOptionAllocation); +// vm.warp(block.timestamp + 365 days); +// vm.prank(authority); +// TokenOptionAllocation(tokenOptionAllocation).recoverForfeitTokens(); +// } + + +// function testTerminateRestrictedTokenAwardAndRecover() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// vm.warp(block.timestamp + 20 days); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// assertEq(token.balanceOf(restrictedTokenAward), 0); +// assertEq(paymentToken.balanceOf(restrictedTokenAward), 0); +// } + +// function testChangeVestingAndUnlockingRate() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// uint256 snapshot = token.balanceOf(authority); +// vm.warp(block.timestamp + 25 seconds); +// +// bytes4 msgSig = bytes4(keccak256("updateMetavestUnlockRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestUnlockRate(restrictedTokenAward, 50e18); +// +// msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 50e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(restrictedTokenAward, 50e18); +// +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// +// } + +// function testZeroReclaim() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// vm.warp(block.timestamp + 15 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// //create call data to propose setting vesting to 0 +// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(restrictedTokenAward, 0); +// +// vm.startPrank(authority); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.warp(block.timestamp + 155 days); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// vm.stopPrank(); +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); +// } function testZeroReclaimVesting() public { - address restrictedTokenAward = createDummyVestingAllocation(); + address vestingAllocation = createDummyVestingAllocation(); vm.warp(block.timestamp + 15 seconds); vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); //create call data to propose setting vesting to 0 bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 0); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 0); vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 0); + controller.updateMetavestVestingRate(vestingAllocation, 0); vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); + controller.terminateMetavestVesting(vestingAllocation); vm.stopPrank(); - console.log(token.balanceOf(restrictedTokenAward)); } function testSlightReduc() public { - address restrictedTokenAward = createDummyVestingAllocation(); + address vestingAllocation = createDummyVestingAllocation(); vm.warp(block.timestamp + 5 seconds); vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); //create call data to propose setting vesting to 0 bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 80e18); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 80e18); vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 80e18); + controller.updateMetavestVestingRate(vestingAllocation, 80e18); vm.warp(block.timestamp + 5 seconds); vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); + controller.terminateMetavestVesting(vestingAllocation); vm.stopPrank(); vm.warp(block.timestamp + 155 seconds); vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - console.log(token.balanceOf(restrictedTokenAward)); } function testLargeReduc() public { - address restrictedTokenAward = createDummyVestingAllocation(); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - //create call data to propose setting vesting to 0 - bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); - - vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); - - vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); - - vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); - vm.warp(block.timestamp + 5 seconds); - vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.stopPrank(); - vm.warp(block.timestamp + 155 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - console.log(token.balanceOf(restrictedTokenAward)); - } - - function testLargeReducOption() public { - address restrictedTokenAward = createDummyTokenOptionAllocation(); + address vestingAllocation = createDummyVestingAllocation(); vm.warp(block.timestamp + 5 seconds); vm.startPrank(grantee); - //approve amount to exercise by getting amount to exercise and price - ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); - TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); //create call data to propose setting vesting to 0 bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); - bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); + bytes memory callData = abi.encodeWithSelector(msgSig, vestingAllocation, 10e18); vm.prank(authority); - controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); + controller.proposeMetavestAmendment(vestingAllocation, msgSig, callData); vm.prank(grantee); - controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); + controller.consentToMetavestAmendment(vestingAllocation, msgSig, true); vm.prank(authority); - controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); + controller.updateMetavestVestingRate(vestingAllocation, 10e18); vm.warp(block.timestamp + 5 seconds); vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); + controller.terminateMetavestVesting(vestingAllocation); vm.stopPrank(); vm.warp(block.timestamp + 155 seconds); vm.startPrank(grantee); - ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); - TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); - vm.stopPrank(); - console.log(token.balanceOf(restrictedTokenAward)); - } - - - - function testReclaim() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - vm.warp(block.timestamp + 15 seconds); - vm.startPrank(grantee); - RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); + VestingAllocation(vestingAllocation).withdraw(VestingAllocation(vestingAllocation).getAmountWithdrawable()); vm.stopPrank(); - - vm.startPrank(authority); - controller.terminateMetavestVesting(restrictedTokenAward); - vm.warp(block.timestamp + 155 days); - uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); - uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); - paymentToken.approve(address(restrictedTokenAward), payamt); - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); - vm.stopPrank(); - vm.prank(grantee); - RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); - console.log(token.balanceOf(restrictedTokenAward)); - } - - - - function testFailUpdateExercisePriceForVesting() public { - address vestingAllocation = createDummyVestingAllocation(); - controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); - } - - function testFailRepurchaseTokensAfterExpiry() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - - // Fast forward time to after the short stop date - vm.warp(block.timestamp + 366 days); - - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); - } - - function testFailRepurchaseTokensInsufficientAllowance() public { - address restrictedTokenAward = createDummyRestrictedTokenAward(); - - // Not approving any tokens - RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); } - function testFailInitiateAuthorityUpdateNonAuthority() public { +// function testLargeReducOption() public { +// address restrictedTokenAward = createDummyTokenOptionAllocation(); +// vm.warp(block.timestamp + 5 seconds); +// vm.startPrank(grantee); +// //approve amount to exercise by getting amount to exercise and price +// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); +// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// //create call data to propose setting vesting to 0 +// bytes4 msgSig = bytes4(keccak256("updateMetavestVestingRate(address,uint160)")); +// bytes memory callData = abi.encodeWithSelector(msgSig, restrictedTokenAward, 10e18); +// +// vm.prank(authority); +// controller.proposeMetavestAmendment(restrictedTokenAward, msgSig, callData); +// +// vm.prank(grantee); +// controller.consentToMetavestAmendment(restrictedTokenAward, msgSig, true); +// +// vm.prank(authority); +// controller.updateMetavestVestingRate(restrictedTokenAward, 10e18); +// vm.warp(block.timestamp + 5 seconds); +// vm.startPrank(authority); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.stopPrank(); +// vm.warp(block.timestamp + 155 seconds); +// vm.startPrank(grantee); +// ERC20Stable(paymentToken).approve(restrictedTokenAward, TokenOptionAllocation(restrictedTokenAward).getPaymentAmount(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable())); +// TokenOptionAllocation(restrictedTokenAward).exerciseTokenOption(TokenOptionAllocation(restrictedTokenAward).getAmountExercisable()); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// console.log(token.balanceOf(restrictedTokenAward)); +// } + + + +// function testReclaim() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// vm.warp(block.timestamp + 15 seconds); +// vm.startPrank(grantee); +// RestrictedTokenAward(restrictedTokenAward).withdraw(RestrictedTokenAward(restrictedTokenAward).getAmountWithdrawable()); +// vm.stopPrank(); +// +// vm.startPrank(authority); +// controller.terminateMetavestVesting(restrictedTokenAward); +// vm.warp(block.timestamp + 155 days); +// uint256 amt = RestrictedTokenAward(restrictedTokenAward).getAmountRepurchasable(); +// uint256 payamt = RestrictedTokenAward(restrictedTokenAward).getPaymentAmount(amt); +// paymentToken.approve(address(restrictedTokenAward), payamt); +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(amt); +// vm.stopPrank(); +// vm.prank(grantee); +// RestrictedTokenAward(restrictedTokenAward).claimRepurchasedTokens(); +// console.log(token.balanceOf(restrictedTokenAward)); +// } + + + +// function test_RevertIf_UpdateExercisePriceForVesting() public { +// address vestingAllocation = createDummyVestingAllocation(); +// controller.updateExerciseOrRepurchasePrice(vestingAllocation, 2e18); +// } + +// function test_RevertIf_RepurchaseTokensAfterExpiry() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// +// // Fast forward time to after the short stop date +// vm.warp(block.timestamp + 366 days); +// +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +// } + +// function test_RevertIf_RepurchaseTokensInsufficientAllowance() public { +// address restrictedTokenAward = createDummyRestrictedTokenAward(); +// +// // Not approving any tokens +// RestrictedTokenAward(restrictedTokenAward).repurchaseTokens(500e18); +// } + + function test_RevertIf_InitiateAuthorityUpdateNonAuthority() public { vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); controller.initiateAuthorityUpdate(address(0x5678)); } - function testFailAcceptAuthorityRoleNonPendingAuthority() public { + function test_RevertIf_AcceptAuthorityRoleNonPendingAuthority() public { + vm.prank(authority); controller.initiateAuthorityUpdate(address(0x5678)); - + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingAuthority.selector)); controller.acceptAuthorityRole(); } - function testFailInitiateDaoUpdateNonDao() public { + function test_RevertIf_InitiateDaoUpdateNonDao() public { vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); controller.initiateDaoUpdate(address(0x5678)); } - function testFailAcceptDaoRoleNonPendingDao() public { + function test_RevertIf_AcceptDaoRoleNonPendingDao() public { vm.prank(dao); controller.initiateDaoUpdate(address(0x5678)); - + vm.prank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyPendingDao.selector)); controller.acceptDaoRole(); } @@ -2368,17 +1313,17 @@ contract MetaVestControllerTest is Test { signers[0] = address(0x1); signers[1] = address(0x2); SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - + vm.prank(dao); controller.updateFunctionCondition(address(condition), functionSig); - + assertEq(controller.functionToConditions(functionSig, 0), address(condition)); } - function testFailUpdateFunctionConditionNonDao() public { + function test_RevertIf_UpdateFunctionConditionNonDao() public { bytes4 functionSig = bytes4(keccak256("updateMetavestStopTimes(address,uint48)")); address condition = address(0x1234); - + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyDAO.selector)); controller.updateFunctionCondition(condition, functionSig); } @@ -2394,7 +1339,7 @@ contract MetaVestControllerTest is Test { signers[0] = address(0x1); signers[1] = address(0x2); SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - + vm.prank(dao); controller.updateFunctionCondition(address(condition), functionSig); assert(controller.functionToConditions(functionSig, 0) == address(condition)); @@ -2402,8 +1347,8 @@ contract MetaVestControllerTest is Test { controller.removeFunctionCondition(address(condition), functionSig); } - function testFailCheckFunctionCondition() public { - bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); + function test_RevertIf_CheckFunctionCondition() public { + bytes4 functionSig = bytes4(keccak256("signDealAndCreateMetavest(address,address,bytes32,string[],bytes,string)")); /* constructor( address[] memory _signers, uint256 _threshold, @@ -2413,15 +1358,17 @@ contract MetaVestControllerTest is Test { signers[0] = address(0x1); signers[1] = address(0x2); SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - + vm.prank(dao); controller.updateFunctionCondition(address(condition), functionSig); assert(controller.functionToConditions(functionSig, 0) == address(condition)); - //create a dummy metavest - address vestingAllocation = createDummyVestingAllocation(); + // create a dummy metavest + createDummyVestingAllocation( + abi.encodeWithSelector(metavestController.MetaVesTController_ConditionNotSatisfied.selector, condition) // Expected revert + ); } - function testFailAddDuplicateCondition() public { + function test_RevertIf_AddDuplicateCondition() public { bytes4 functionSig = bytes4(keccak256("createMetavest(uint8,address,(uint256,uint128,uint128,uint160,uint48,uint160,uint48,address),(uint256,bool,bool,address[])[],uint256,address,uint256,uint256)")); /* constructor( address[] memory _signers, @@ -2432,11 +1379,255 @@ contract MetaVestControllerTest is Test { signers[0] = address(0x1); signers[1] = address(0x2); SignatureCondition condition = new SignatureCondition(signers, 1, SignatureCondition.Logic.AND); - + vm.prank(dao); controller.updateFunctionCondition(address(condition), functionSig); assert(controller.functionToConditions(functionSig, 0) == address(condition)); vm.prank(dao); + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVestController_DuplicateCondition.selector)); controller.updateFunctionCondition(address(condition), functionSig); } -} \ No newline at end of file + + 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); + } + + function test_RevertIf_IncorrectGrantorSignature() public { + // Should not be able to propose a deal without grantor's authorization + _proposeAndSignDeal( + templateId, + block.timestamp, // salt + alicePrivateKey, // Should fail because Alice is not delegated by the grantor + alice, // grantee + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + block.timestamp + 7 days, + abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert + ); + } + + function test_RevertIf_IncorrectGranteeSignature() public { + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + block.timestamp + 7 days + ); + + // Should not be able to sign Alice's agreement with other's signature + _granteeSignDeal( + contractIdAlice, + alice, + alice, + bobPrivateKey, // Wrong signer + "Alice", + abi.encodeWithSelector(CyberAgreementRegistry.SignatureVerificationFailed.selector) // Expected revert + ); + } + + function test_GranteeDelegateSignature() public { + // Alice to delegate to Bob + vm.prank(alice); + registry.setDelegation(bob, block.timestamp + 60); + assertTrue(registry.isValidDelegate(alice, bob), "Bob should be Alice's delegate"); + + // Bob should be able to sign for Alice now + bytes32 contractId = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + tokenStreamTotal: 100 ether, + vestingCliffCredit: 10 ether, + unlockingCliffCredit: 10 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + ); + VestingAllocation vestingAllocation = VestingAllocation(_granteeSignDeal( + contractId, + alice, + alice, + bobPrivateKey, // Use Bob to sign + "Alice" + )); + assertEq(vestingAllocation.grantee(), alice, "Alice should be the grantee"); + + // Wait until expiry + skip(61); + + // Bob should no longer be able to sign for Alice + assertFalse(registry.isValidDelegate(alice, bob), "Bob should no longer be Alice's delegate"); + } + + 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(); + } + + 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 + address newImplementation = address(new metavestController()); + + // Upgrade to new implementation without initialization data + + // Non-owner should not be able to upgrade it + vm.expectRevert(abi.encodeWithSelector(metavestController.MetaVesTController_OnlyAuthority.selector)); + controller.upgradeToAndCall(newImplementation, ""); + + // Owner should be able to upgrade it + vm.prank(guardianSafe); + controller.upgradeToAndCall(newImplementation, ""); + assertEq(address(controller).getErc1967Implementation(vm), newImplementation); + + // Verify the controller still works + + bytes32 contractIdAlice = _proposeAndSignDeal( + templateId, + block.timestamp, // salt + delegatePrivateKey, + alice, + BaseAllocation.Allocation({ + tokenContract: address(zkToken), + // 100k ZK total, the first half unlocks with a cliff and the second half unlocks over an year + tokenStreamTotal: 60 ether, + vestingCliffCredit: 30 ether, + unlockingCliffCredit: 30 ether, + vestingRate: 1 ether, + vestingStartTime: zkCappedMinter.START_TIME(), // start along with capped minter + unlockRate: 1 ether, + unlockStartTime: zkCappedMinter.START_TIME() // start along with capped minter + }), + new BaseAllocation.Milestone[](0), + "Alice", + cappedMinterExpirationTime // Same expiry as the minter so grantee can defer vesting contract creation as much as possible + ); + + VestingAllocation vestingAllocationAlice = VestingAllocation(_granteeSignDeal( + contractIdAlice, + alice, // grantee + alice, // recipient + alicePrivateKey, + "Alice" + )); + assertEq(vestingAllocationAlice.grantee(), alice); + } +} diff --git a/test/lib/ERC1967ProxyLib.sol b/test/lib/ERC1967ProxyLib.sol new file mode 100644 index 0000000..087302b --- /dev/null +++ b/test/lib/ERC1967ProxyLib.sol @@ -0,0 +1,53 @@ +/* .o. + .888. + .8"888. + .8' `888. + .88ooo8888. + .8' `888. +o88o o8888o + + + +ooo ooooo . ooooo ooooooo ooooo +`88. .888' .o8 `888' `8888 d8' + 888b d'888 .ooooo. .o888oo .oooo. 888 .ooooo. Y888..8P + 8 Y88. .P 888 d88' `88b 888 `P )88b 888 d88' `88b `8888' + 8 `888' 888 888ooo888 888 .oP"888 888 888ooo888 .8PY888. + 8 Y 888 888 .o 888 . d8( 888 888 o 888 .o d8' `888b +o8o o888o `Y8bod8P' "888" `Y888""8o o888ooooood8 `Y8bod8P' o888o o88888o + + + + .oooooo. .o8 .oooooo. + d8P' `Y8b "888 d8P' `Y8b +888 oooo ooo 888oooo. .ooooo. oooo d8b 888 .ooooo. oooo d8b oo.ooooo. +888 `88. .8' d88' `88b d88' `88b `888""8P 888 d88' `88b `888""8P 888' `88b +888 `88..8' 888 888 888ooo888 888 888 888 888 888 888 888 +`88b ooo `888' 888 888 888 .o 888 `88b ooo 888 888 888 888 888 .o. + `Y8bood8P' .8' `Y8bod8P' `Y8bod8P' d888b `Y8bood8P' `Y8bod8P' d888b 888bod8P' Y8P + .o..P' 888 + `Y8P' o888o +_______________________________________________________________________________________________________ + +All software, documentation and other files and information in this repository (collectively, the "Software") +are copyright MetaLeX Labs, Inc., a Delaware corporation. + +All rights reserved. + +The Software is proprietary and shall not, in part or in whole, be used, copied, modified, merged, published, +distributed, transmitted, sublicensed, sold, or otherwise used in any form or by any means, electronic or +mechanical, including photocopying, recording, or by any information storage and retrieval system, +except with the express prior written permission of the copyright holder.*/ + +pragma solidity 0.8.28; + +import {Vm} from "forge-std/Test.sol"; + +library ERC1967ProxyLib { + + function getErc1967Implementation(address proxy, Vm vm) internal view returns (address) { + // Workaround since there is no public function to get the implementation address: + // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/acd4ff74de833399287ed6b31b4debf6b2b35527/contracts/proxy/ERC1967/ERC1967Proxy.sol#L35 + return address(uint160(uint256(vm.load(proxy, 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc)))); + } +} diff --git a/test/lib/MetaVesTControllerTestBase.sol b/test/lib/MetaVesTControllerTestBase.sol new file mode 100644 index 0000000..a9877a7 --- /dev/null +++ b/test/lib/MetaVesTControllerTestBase.sol @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import "../../src/MetaVesTController.sol"; +import "../../src/VestingAllocationFactory.sol"; +import "../../src/interfaces/zk-governance/IZkTokenV1.sol"; +import {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"; + +contract MetaVesTControllerTestBase is Test { +// // zkSync Era Sepolia @ 5576300 +// address zkTokenAdmin = 0x0d9DD6964692a0027e1645902536E7A3b34AA1d7; +// IZkTokenV1 zkToken = IZkTokenV1(0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96); +// IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x329CE320a0Ef03F8c0E01195604b5ef7D3Fb150E); + // zkSync Era mainnet @ 63631890 + address zkTokenAdmin = 0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d; + IZkTokenV1 zkToken = IZkTokenV1(0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E); + IZkCappedMinterV2Factory zkCappedMinterFactory = IZkCappedMinterV2Factory(0x0400E6bc22B68686Fb197E91f66E199C6b0DDD6a); + + IZkCappedMinterV2 zkCappedMinter; + + address deployer = address(0x2); + address guardianSafe = address(0x3); + + uint256 alicePrivateKey = 1; + address alice = vm.addr(alicePrivateKey); + uint256 bobPrivateKey = 2; + address bob = vm.addr(bobPrivateKey); + uint256 chadPrivateKey = 3; + address chad = vm.addr(chadPrivateKey); + uint256 delegatePrivateKey = 4; + address delegate = vm.addr(delegatePrivateKey); + + bytes32 salt = keccak256("MetaVesTControllerTestBase"); + + bytes32 templateId = bytes32(uint256(123)); + string agreementUri = "ipfs.io/ipfs/[cid]"; + string[] globalFields; + string[] partyFields; + + BorgAuth auth; + CyberAgreementRegistry registry; + + VestingAllocationFactory vestingAllocationFactory; + + metavestController controller; + + function setUp() public virtual { + vm.startPrank(deployer); + + // Deploy CyberAgreementRegistry and prepare templates + + // TODO who should be the owner of auth? + auth = new BorgAuth{salt: salt}(deployer); + registry = CyberAgreementRegistry(address(new ERC1967Proxy{salt: salt}( + address(new CyberAgreementRegistry{salt: salt}()), + abi.encodeWithSelector( + CyberAgreementRegistry.initialize.selector, + address(auth) + ) + ))); + + globalFields = new string[](11); + globalFields[0] = "metavestType"; + globalFields[1] = "grantor"; + globalFields[2] = "grantee"; + globalFields[3] = "tokenContract"; + globalFields[4] = "tokenStreamTotal"; + globalFields[5] = "vestingCliffCredit"; + globalFields[6] = "unlockingCliffCredit"; + globalFields[7] = "vestingRate"; + globalFields[8] = "vestingStartTime"; + globalFields[9] = "unlockRate"; + globalFields[10] = "unlockStartTime"; + + partyFields = new string[](4); + partyFields[0] = "name"; + partyFields[1] = "evmAddress"; + partyFields[2] = "contactDetails"; + partyFields[3] = "type"; + + registry.createTemplate( + templateId, + "ZkSyncGuardianCompensation", + agreementUri, + globalFields, + partyFields + ); + + vm.stopPrank(); + } + + function _granteeWithdrawAndAsserts(VestingAllocation vestingAllocation, uint256 amount, string memory assertName) internal { + address grantee = vestingAllocation.grantee(); + uint256 balanceBefore = zkToken.balanceOf(grantee); + + vm.prank(grantee); + vm.expectEmit(true, true, true, true); + emit metavestController.MetaVesTController_Minted(address(vestingAllocation), grantee, address(zkCappedMinter), amount); + vestingAllocation.withdraw(amount); + + assertEq(zkToken.balanceOf(grantee), balanceBefore + amount, string(abi.encodePacked(assertName, ": unexpected received amount"))); + assertEq(zkToken.balanceOf(address(vestingAllocation)), 0, string(abi.encodePacked(assertName, ": vesting contract should not have any token (it mints on-demand)"))); + } + + function _proposeAndSignDeal( + bytes32 templateId, + uint256 agreementSalt, + uint256 grantorOrDelegatePrivateKey, + address grantee, + BaseAllocation.Allocation memory allocation, + BaseAllocation.Milestone[] memory milestones, + string memory partyName, + uint256 expiry + ) internal returns(bytes32) { + return _proposeAndSignDeal( + templateId, agreementSalt, grantorOrDelegatePrivateKey, grantee, allocation, milestones, partyName, expiry, + "" // Not expecting revert + ); + } + + function _proposeAndSignDeal( + bytes32 templateId, + uint256 agreementSalt, + uint256 grantorOrDelegatePrivateKey, + address grantee, + BaseAllocation.Allocation memory allocation, + BaseAllocation.Milestone[] memory milestones, + string memory partyName, + uint256 expiry, + bytes memory expectRevertData + ) internal returns(bytes32) { + string[] memory globalValues = new string[](11); + globalValues[0] = "0"; // metavestType: Vesting + globalValues[1] = vm.toString(address(guardianSafe)); // grantor + globalValues[2] = vm.toString(grantee); // grantee + globalValues[3] = vm.toString(allocation.tokenContract); // tokenContract + globalValues[4] = vm.toString(allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) + globalValues[5] = vm.toString(allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) + globalValues[6] = vm.toString(allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) + globalValues[7] = vm.toString(allocation.vestingRate * 365 days / 1 ether); // vestingRate (annually) (human-readable) + globalValues[8] = vm.toString(allocation.vestingStartTime); // vestingStartTime + globalValues[9] = vm.toString(allocation.unlockRate * 365 days / 1 ether); // unlockRate (annually) (human-readable) + globalValues[10] = vm.toString(allocation.unlockStartTime); // unlockStartTime + + // TODO what to do with milestones, which could be of dynamic lengths + + string[][] memory partyValues = new string[][](2); + partyValues[0] = new string[](4); + partyValues[0][0] = "Guardian BORG"; + partyValues[0][1] = vm.toString(address(guardianSafe)); + partyValues[0][2] = "guardian-safe@company.com"; + partyValues[0][3] = "Foundation"; + partyValues[1] = new string[](4); + partyValues[1][0] = partyName; + partyValues[1][1] = vm.toString(grantee); // evmAddress + partyValues[1][2] = "email@company.com"; + partyValues[1][3] = "individual"; + + address[] memory parties = new address[](2); + parties[0] = address(guardianSafe); + parties[1] = grantee; + bytes32 expectedContractId = keccak256( + abi.encode( + templateId, + agreementSalt, + globalValues, + parties + ) + ); + + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + expectedContractId, + agreementUri, + globalFields, + partyFields, + globalValues, + partyValues[0], + grantorOrDelegatePrivateKey + ); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + bytes32 contractId = controller.proposeAndSignDeal( + templateId, + agreementSalt, + metavestController.metavestType.Vesting, + grantee, + allocation, + milestones, + globalValues, + parties, + partyValues, + signature, + bytes32(0), // no secrets + expiry + ); + + if (expectRevertData.length == 0) { + assertEq(contractId, expectedContractId, "Unexpected contract ID"); + return contractId; + } else { + return 0; + } + } + + function _granteeSignDeal( + bytes32 contractId, + address grantee, + address recipient, + uint256 granteePrivateKey, + string memory partyName + ) internal returns(address) { + return _granteeSignDeal( + contractId, grantee, recipient, granteePrivateKey, partyName, + "" // Not expecting revert + ); + } + + function _granteeSignDeal( + bytes32 contractId, + address grantee, + address recipient, + uint256 granteePrivateKey, + string memory partyName, + bytes memory expectRevertData + ) internal returns(address) { + metavestController.DealData memory deal = controller.getDeal(contractId); + + string[] memory globalValues = new string[](11); + globalValues[0] = "0"; // metavestType: Vesting + globalValues[1] = vm.toString(address(guardianSafe)); // grantor + globalValues[2] = vm.toString(grantee); // grantee + globalValues[3] = vm.toString(deal.allocation.tokenContract); // tokenContract + globalValues[4] = vm.toString(deal.allocation.tokenStreamTotal / 1 ether); //tokenStreamTotal (human-readable) + globalValues[5] = vm.toString(deal.allocation.vestingCliffCredit / 1 ether); // vestingCliffCredit (human-readable) + globalValues[6] = vm.toString(deal.allocation.unlockingCliffCredit / 1 ether); // unlockingCliffCredit (human-readable) + globalValues[7] = vm.toString(deal.allocation.vestingRate * 365 days / 1 ether); // vestingRate (annually) (human-readable) + globalValues[8] = vm.toString(deal.allocation.vestingStartTime); // vestingStartTime + globalValues[9] = vm.toString(deal.allocation.unlockRate * 365 days / 1 ether); // unlockRate (annually) (human-readable) + globalValues[10] = vm.toString(deal.allocation.unlockStartTime); // unlockStartTime + + string[] memory partyValues = new string[](4); + partyValues[0] = partyName; + partyValues[1] = vm.toString(grantee); // evmAddress + partyValues[2] = "email@company.com"; // Make sure it matches the proposed deal + partyValues[3] = "individual"; // Make sure it matches the proposed deal + + bytes memory signature = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + agreementUri, + globalFields, + partyFields, + globalValues, + partyValues, + granteePrivateKey + ); + + if (expectRevertData.length > 0) { + vm.expectRevert(expectRevertData); + } + address metavest = controller.signDealAndCreateMetavest( + grantee, + recipient, + contractId, + partyValues, + signature, + "" // no secrets + ); + + if (expectRevertData.length == 0) { + return metavest; + } else { + return address(0); + } + } +} diff --git a/test/lib/safe.sol b/test/lib/safe.sol new file mode 100644 index 0000000..be09f19 --- /dev/null +++ b/test/lib/safe.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +interface IGnosisSafe { + function getThreshold() external view returns (uint256); + + function isOwner(address owner) external view returns (bool); + + function getOwners() external view returns (address[] memory); + + function isModuleEnabled(address module) external view returns (bool); + + function setGuard(address guard) external; + + function addOwnerWithThreshold(address owner, uint256 threshold) external; + + function execTransaction( + address to, + uint256 value, + bytes memory data, + uint8 operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address refundReceiver, + bytes memory signatures + ) external payable returns (bool success); + + function encodeTransactionData( + address to, + uint256 value, + bytes memory data, + uint8 operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address refundReceiver, + uint256 _nonce + ) external view returns (bytes memory); + + function nonce() external view returns (uint256); + + function setup( + address[] calldata _owners, + uint256 _threshold, + address to, + bytes calldata data, + address fallbackHandler, + address paymentToken, + uint256 payment, + address payable paymentReceiver + ) external; +} + +struct GnosisTransaction { + address to; + uint256 value; + bytes data; +} + +interface IMultiSendCallOnly { + function multiSend(bytes memory transactions) external payable; +} + +interface ISafeProxyFactory { + function createProxyWithNonce(address _singleton, bytes memory initializer, uint256 saltNonce) external returns (address proxy); +} diff --git a/test/mocks/MockCondition.sol b/test/mocks/MockCondition.sol index cd96bf4..c0111a5 100644 --- a/test/mocks/MockCondition.sol +++ b/test/mocks/MockCondition.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import "../../src/BaseAllocation.sol";