From 0e76c9cbb91b996352124e15630be9c9e238c891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Mon, 14 Oct 2024 14:06:04 +0200 Subject: [PATCH] Multisig only DAO Factory flow --- .env.example | 74 +- README.md | 118 +- script/Deploy.s.sol | 126 +- script/DeployRouter.s.sol | 52 - script/multisig-members.json | 2 +- src/clock/Clock.sol | 226 --- src/clock/IClock.sol | 60 - src/escrow/increasing/ExitQueue.sol | 230 --- src/escrow/increasing/Lock.sol | 127 -- .../increasing/QuadraticIncreasingEscrow.sol | 352 ---- .../increasing/VotingEscrowIncreasing.sol | 412 ----- .../increasing/interfaces/IERC721EMB.sol | 12 - .../interfaces/IEscrowCurveIncreasing.sol | 137 -- .../increasing/interfaces/IExitQueue.sol | 115 -- src/escrow/increasing/interfaces/ILock.sol | 29 - src/escrow/increasing/interfaces/IVotes.sol | 84 - .../interfaces/IVotingEscrowIncreasing.sol | 192 --- ...sDaoFactory.sol => MultisigDaoFactory.sol} | 182 +-- src/libs/CurveConstantLib.sol | 22 - src/libs/ProxyLib.sol | 42 - src/libs/SignedFixedPointMathLib.sol | 51 - src/voting/ISimpleGaugeVoter.sol | 137 -- src/voting/SimpleGaugeVoter.sol | 339 ---- src/voting/SimpleGaugeVoterSetup.sol | 346 ---- test/escrow/curve/QuadraticCurveBase.t.sol | 59 - test/escrow/curve/QuadraticCurveLogic.t.sol | 45 - test/escrow/curve/QuadraticCurveMath.t.sol | 175 -- test/escrow/escrow/EscrowAdmin.t.sol | 173 -- test/escrow/escrow/EscrowBase.sol | 230 --- test/escrow/escrow/EscrowCreateLock.t.sol | 373 ----- test/escrow/escrow/EscrowSweep.t.sol | 163 -- test/escrow/escrow/EscrowTransfers.t.sol | 60 - test/escrow/escrow/EscrowWithdraw.t.sol | 339 ---- test/escrow/escrow/Lock.t.sol | 110 -- test/escrow/queue/ExitQueue.t.sol | 301 ---- test/escrow/queue/ExitQueueBase.sol | 74 - .../queue/ExitQueueFeeWithdrawals.t.sol | 115 -- test/fork/e2eV2.t.sol | 1452 ----------------- test/helpers/OSxHelpers.sol | 137 -- test/helpers/TestHelpers.sol | 50 - test/integration/GaugesDaoFactory.sol | 1328 --------------- test/libs/SignedFixedPointMathLib.t.sol | 90 - test/mocks/MockDAO.sol | 51 - test/mocks/MockERC20.sol | 19 - test/mocks/osx/MockDAOFactory.sol | 205 --- test/mocks/osx/MockPSP.sol | 748 --------- test/mocks/osx/MockPSPMulti.sol | 744 --------- test/mocks/osx/MockPluginRepoRegistry.sol | 63 - test/mocks/osx/PlaceholderSetup.sol | 10 - .../mocks/osx/PluginSetupProcessorHelpers.sol | 90 - test/python/crosscheck.py | 57 - test/python/epoch.py | 23 - test/voting/GaugeManage.t.sol | 201 --- test/voting/GaugeTime.t.sol | 101 -- test/voting/GaugeVote.t.sol | 537 ------ test/voting/GaugeVotingBase.sol | 270 --- test/voting/Setup.t.sol | 174 -- 57 files changed, 33 insertions(+), 12001 deletions(-) delete mode 100644 script/DeployRouter.s.sol delete mode 100644 src/clock/Clock.sol delete mode 100644 src/clock/IClock.sol delete mode 100644 src/escrow/increasing/ExitQueue.sol delete mode 100644 src/escrow/increasing/Lock.sol delete mode 100644 src/escrow/increasing/QuadraticIncreasingEscrow.sol delete mode 100644 src/escrow/increasing/VotingEscrowIncreasing.sol delete mode 100644 src/escrow/increasing/interfaces/IERC721EMB.sol delete mode 100644 src/escrow/increasing/interfaces/IEscrowCurveIncreasing.sol delete mode 100644 src/escrow/increasing/interfaces/IExitQueue.sol delete mode 100644 src/escrow/increasing/interfaces/ILock.sol delete mode 100644 src/escrow/increasing/interfaces/IVotes.sol delete mode 100644 src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol rename src/factory/{GaugesDaoFactory.sol => MultisigDaoFactory.sol} (55%) delete mode 100644 src/libs/CurveConstantLib.sol delete mode 100644 src/libs/ProxyLib.sol delete mode 100644 src/libs/SignedFixedPointMathLib.sol delete mode 100644 src/voting/ISimpleGaugeVoter.sol delete mode 100644 src/voting/SimpleGaugeVoter.sol delete mode 100644 src/voting/SimpleGaugeVoterSetup.sol delete mode 100644 test/escrow/curve/QuadraticCurveBase.t.sol delete mode 100644 test/escrow/curve/QuadraticCurveLogic.t.sol delete mode 100644 test/escrow/curve/QuadraticCurveMath.t.sol delete mode 100644 test/escrow/escrow/EscrowAdmin.t.sol delete mode 100644 test/escrow/escrow/EscrowBase.sol delete mode 100644 test/escrow/escrow/EscrowCreateLock.t.sol delete mode 100644 test/escrow/escrow/EscrowSweep.t.sol delete mode 100644 test/escrow/escrow/EscrowTransfers.t.sol delete mode 100644 test/escrow/escrow/EscrowWithdraw.t.sol delete mode 100644 test/escrow/escrow/Lock.t.sol delete mode 100644 test/escrow/queue/ExitQueue.t.sol delete mode 100644 test/escrow/queue/ExitQueueBase.sol delete mode 100644 test/escrow/queue/ExitQueueFeeWithdrawals.t.sol delete mode 100644 test/fork/e2eV2.t.sol delete mode 100644 test/helpers/OSxHelpers.sol delete mode 100644 test/helpers/TestHelpers.sol delete mode 100644 test/integration/GaugesDaoFactory.sol delete mode 100644 test/libs/SignedFixedPointMathLib.t.sol delete mode 100644 test/mocks/MockDAO.sol delete mode 100644 test/mocks/MockERC20.sol delete mode 100644 test/mocks/osx/MockDAOFactory.sol delete mode 100644 test/mocks/osx/MockPSP.sol delete mode 100644 test/mocks/osx/MockPSPMulti.sol delete mode 100644 test/mocks/osx/MockPluginRepoRegistry.sol delete mode 100644 test/mocks/osx/PlaceholderSetup.sol delete mode 100644 test/mocks/osx/PluginSetupProcessorHelpers.sol delete mode 100644 test/python/crosscheck.py delete mode 100644 test/python/epoch.py delete mode 100644 test/voting/GaugeManage.t.sol delete mode 100644 test/voting/GaugeTime.t.sol delete mode 100644 test/voting/GaugeVote.t.sol delete mode 100644 test/voting/GaugeVotingBase.sol delete mode 100644 test/voting/Setup.t.sol diff --git a/.env.example b/.env.example index 9845036..71f4b97 100644 --- a/.env.example +++ b/.env.example @@ -1,75 +1,21 @@ -# Fork Test mode: -# "fork-deploy" will run against the live network fork, deploying new contracts via a new instance of the factory -# "fork-existing" will run against the live network fork, using the existing factory & therefore the existing contracts -FORK_TEST_MODE="fork-deploy" - -# With false, the script will deploy mock tokens with open mint functions -DEPLOY_AS_PRODUCTION=false - -# If deploying against a fork, pass the address of a large token holder who will be -# impersonated to distribute tokens to addresses inside test cases -# the whale should have >= 3000 tokens -TOKEN_TEST_WHALE="0x" - -# If the factory singleton is deployed, pass the address. "Fork existing" mode will use this previously -# deployed factory, and "fork deploy" will deploy a new factory instance -FACTORY="0x0000000000000000000000000000000000000000" - # NETWORK AND DEPLOYMENT WALLET -DEPLOYMENT_PRIVATE_KEY="..." +DEPLOYMENT_PRIVATE_KEY="..." # tbd +ALCHEMY_API_KEY="..." +ETHERSCAN_API_KEY="..." # tbd NETWORK="sepolia" -# API Keys (optional) -# Note that having these active will slow down unit tests even when not needed -# So recommended to only activate when needed -# ETHERSCAN_API_KEY="..." -# ALCHEMY_API_KEY="..." - +MULTISIG_MEMBERS_JSON_FILE_NAME="/script/multisig-members.json" # MULTISIG PARAMETERS -# define a list of multisig members - said multisig will be assigned administrator roles of the ve contracts -MULTISIG_MEMBERS_JSON_FILE_NAME="/script/multisig-members.json" -MIN_APPROVALS="1" # How many multisig approvals are required +MIN_APPROVALS="3" # How many multisig approvals are required MULTISIG_PROPOSAL_EXPIRATION_PERIOD="864000" # How long until a pending proposal expires (10 days) -# GAUGE VOTER PARAMETERS -# The token to be used for the escrow -TOKEN1_ADDRESS="0x0000000000000000000000000000000000000000" -VE_TOKEN1_NAME="Voting Escrow Token 1" -VE_TOKEN1_SYMBOL="veTK1" - -# Additional tokens these will have secondary escrow contracts -TOKEN2_ADDRESS="0x0000000000000000000000000000000000000000" # Ignored if 0x0 -VE_TOKEN2_NAME="Voting Escrow Token 2" -VE_TOKEN2_SYMBOL="veTK2" - -# 10_000 = 100% -FEE_PERCENT="0" - -# Min seconds after depositing before voting is possible -WARMUP_PERIOD="259200" # 3 days - -# Min seconds after queuing an exit before withdrawing is possible -COOLDOWN_PERIOD="259200" # 3 days - -# Min seconds a user must have locked in escrow before they can queue an exit -MIN_LOCK_DURATION="3600" # 1 hour - -# Prevent voting until manually activated by the multisig -VOTING_PAUSED=true - -# Initial minimum amount needed (in wei) to create a lock -MIN_DEPOSIT="1000000000000000000" # 1 ether - # PLUGIN REPO PARAMETERS (per-network) -# SEPOLIA -MULTISIG_PLUGIN_REPO_ADDRESS="0x9e7956C8758470dE159481e5DD0d08F8B59217A2" + +# Ethereum +MULTISIG_PLUGIN_REPO_ADDRESS="0x8c278e37D0817210E18A7958524b7D0a1fAA6F7b" MULTISIG_PLUGIN_RELEASE="1" MULTISIG_PLUGIN_BUILD="2" -SIMPLE_GAUGE_VOTER_REPO_ENS_SUBDOMAIN="my-simple-gauge-voter-0" -# OSx IMPLEMENTATIONS ADDRESSES (network dependent, see active_contracts.json on lib/osx) -# SEPOLIA -DAO_FACTORY="0x7a62da7B56fB3bfCdF70E900787010Bc4c9Ca42e" -PLUGIN_SETUP_PROCESSOR="0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f" -PLUGIN_REPO_FACTORY="0x07f49c49Ce2A99CF7C28F66673d406386BDD8Ff4" +DAO_FACTORY="0xf96e6FD76BD0A15580604e1Ea5818D448b1041C0" +PLUGIN_SETUP_PROCESSOR="0xE978942c691e43f65c1B7c7F8f1dc8cDF061B13f" diff --git a/README.md b/README.md index 2b1b9a5..30ed004 100644 --- a/README.md +++ b/README.md @@ -48,18 +48,6 @@ make coverage make ft-sepolia-fork ``` -## Running fork tests - -Fork testing has 2 modes: - -1. "fork-deploy" will run against the live network fork, deploying new contracts via a new instance of the factory - -2. "fork-existing" will run against the live network fork, using the existing factory & therefore the existing contracts - -In both cases, you will need to find the correct Aragon OSx contracts for the chain you wish to fork against. These can be found in the [OSx commons repo](https://github.com/aragon/osx-commons/tree/main/configs/src/deployments/json) - -> If running frequent fork tests it's recommended you pass a block number to enable caching - ## Deployment Deployments are done using the deployment factory. This is a singleton contract that will: @@ -69,26 +57,17 @@ Deployments are done using the deployment factory. This is a singleton contract - Transfer ownership to a freshly deployed multisig - Store the addresses of the deployment in a single, queriable place. -Check the `Makefile` for examples of deployments on different networks. - ### Deployment Checklist -- [x] I have reviewed the parameters for the veDAO I want to deploy -- [x] I have reviewed the multisig file for the correct addresses - - [x] I have ensured all multisig members have undergone a proper security review and are aware of the security implications of being on said multisig -- [x] I have updated the `.env` with these parameters -- [x] I have updated the `CurveConstantLib` and `Clock` with any new constants. -- [x] All my unit tests pass -- [x] I have run a fork test in `fork-deploy` mode against the OSx contracts on my target testnet -- [x] I have deployed my contracts successfully to a target testnet -- [x] I have previewed my deploy -- [x] My deployer address is a fresh wallet or setup for repeat production deploys in a safe manner. -- [x] My wallet has sufficient native token for gas -- [] I have confirmed my tests still work in `fork-existing` mode with the live tokens and the factory. - -**Puffer Only** - -- [] I have deployed the Router contract +- [ ] I have reviewed the parameters for the veDAO I want to deploy +- [ ] I have reviewed the multisig file for the correct addresses + - [ ] I have ensured all multisig members have undergone a proper security review and are aware of the security implications of being on said multisig +- [ ] I have updated the `.env` with these parameters +- [ ] I have reviewed the deployment script and the Factory contract +- [ ] I have deployed my contracts successfully to a target testnet +- [ ] I have previewed my deploy +- [ ] My deployer address is a fresh wallet or setup for repeat production deploys in a safe manner. +- [ ] My wallet has sufficient native token for gas ### Manual from the command line @@ -99,24 +78,19 @@ You can of course run all commands from the command line: source .env ``` -```sh -# run unit tests -forge test --no-match-path "test/fork/**/*.sol" -``` - ```sh # Set the right RPC URL -RPC_URL="https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}" +RPC_URL="https://mainnet.mode.network" ``` ```sh -# Run the deployment script +# Check the deployment script +make deploy-preview-mode -# If using Etherscan -forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify +# Run the deployment -# If using BlockScout -forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify --verifier blockscout --verifier-url "https://sepolia.explorer.mode.network/api\?" +# forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify --verifier blockscout --verifier-url "https://sepolia.explorer.mode.network/api\?" +make deploy-mode ``` If you get the error Failed to get EIP-1559 fees, add `--legacy` to the command: @@ -130,65 +104,3 @@ If some contracts fail to verify on Etherscan, retry with this command: ```sh forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --verify --legacy --private-key "$DEPLOYMENT_PRIVATE_KEY" --resume ``` - -## Contracts Overview - -The primary contracts in the governance hub are found in the `src` directory. The key contracts include - -- `VotingEscrowIncreasing.sol`: ERC721 veNFT designed to be used with escrow systems that reward users for longer lock times. -- `SimpleGaugeVoter.sol`: allows split voting across arbitrary options. Votes are simply registered in the gauge voter, they do not perform any onchain actions - -The main workflow in the Mode Governance build is as follows: - -## Escrow - -- Users lock a whitelisted token into the Escrow Contract. -- The user is minted a veNFT which stores: - - The amount they locked - - The start of their lock - users begin their locks starting from the next deposit interval - - In the base case, this means a user will start their lock from the start of the upcoming week -- The user's voting power increases over time, starting from a baseline of the locked amount, up to a maximum voting power -- The user is unable to vote during an initial "warmup period". -- The user can exit their position at any time. In this case, they are entered into an "Exit Queue", whereupon their NFT is held in the queue for a "cooldown" period of X Days. After the period ends, they can burn the NFT to receieve their underlying balance back. -- It's possible to add a `minLock` period whereby a user is prevented from entering the exit queue before a certain time. This means they have their NFT available to vote but can't enter the exit process. - - Voting power is removed from the NFT at this time -- The exit queue can optionally set an exit fee that will be charged on exit. - -## Voting - -- Administrators setup voting options on the `SimpleGaugeVoter.sol`, we call these `gauges`. -- Administrators can activate voting at which point a timestamp is recorded. `EpochDurationbLib` tracks 2 week epochs in single week blocks: - - A Voting phase (default is 1 week), where votes are accepted. - - A distribution phase of (default is 1 week), where votes are not accepted (this is done in order to allow governance to compute and allocate rewards). -- Users can vote as much as they want during the voting period. -- Users' NFTs are locked unless they `reset` their votes and remove their voting power. - -## Parameterization - -- Various elements of these contracts can be parameterised in order to support different ve mechanisms. These include: - - - Custom exit queue logic via custom exit queue managers - - Custom escrow curves - - Custom voting contracts other than SimpleGaugeVoter - - Custom epoch clock logic via the `Clock.sol` contract - -- Additionally, we use libraries like `CurveCoefficientLib` and `SignedFixedPointMathLib` that allow users to make minimal, consistent and gas-efficient customisations to things like epoch length and curve shapes. - -## Rewards - -- The current versions of the contracts assume an offchain rewards distribution mechanism. - -## Caveats - -- This version of the repository defines user-based logic and initial framework for: - - Voting Escrow Lockers w. veNFT functionality - - Voting Escrow Curves - - Exit Queues -- Rewards and emissions are assumed to be offchain -- veNFT transfers are disabled by default in the current implementation, but can be enabled. Fully supporting transfers would require support for allowing for custom transfer logic (resetting voting power) which is as yet not implemented. -- Delegation checkpointing is not yet implemented. -- Total supply is not yet implemented due to complexities in scheduling slope changes for higher order polynomials. We have setup a user-point system where this can be added in the future: please see the linked research below for details. - -## Curve design - -To build a flexible approach to curve design, we reviewed implementations such as seen in Curve and Aerodrome and attempted to generalise to higher order polynomials [Details on the curve design research can be found here](https://github.com/jordaniza/ve-explainer/blob/main/README.md) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 1f861f3..f48e6bb 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -3,20 +3,15 @@ pragma solidity ^0.8.17; import {Script, console} from "forge-std/Script.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {GaugesDaoFactory, DeploymentParameters, Deployment, TokenParameters} from "../src/factory/GaugesDaoFactory.sol"; +import {MultisigDaoFactory, DeploymentParameters, Deployment} from "../src/factory/MultisigDaoFactory.sol"; import {MultisigSetup as MultisigPluginSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol"; -import {VotingEscrow, Clock, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; -import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; -import {MockERC20} from "@mocks/MockERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; contract Deploy is Script { using SafeCast for uint256; - SimpleGaugeVoterSetup simpleGaugeVoterSetup; - /// @dev Thrown when attempting to deploy a multisig with no members error EmptyMultisig(); @@ -37,47 +32,27 @@ contract Deploy is Script { DeploymentParameters memory parameters = getDeploymentParameters(isProduction); // Create the DAO - GaugesDaoFactory factory = new GaugesDaoFactory(parameters); + MultisigDaoFactory factory = new MultisigDaoFactory(parameters); factory.deployOnce(); // Done printDeploymentSummary(factory); } - function getDeploymentParameters( - bool isProduction - ) public returns (DeploymentParameters memory parameters) { + function getDeploymentParameters(bool) public view returns (DeploymentParameters memory parameters) { address[] memory multisigMembers = readMultisigMembers(); - TokenParameters[] memory tokenParameters = getTokenParameters(isProduction); - - // NOTE: Multisig is already deployed, using the existing Aragon's repo - // NOTE: Deploying the plugin setup from the current script to avoid code size constraints - - SimpleGaugeVoterSetup gaugeVoterPluginSetup = deploySimpleGaugeVoterPluginSetup(); parameters = DeploymentParameters({ // Multisig settings minApprovals: vm.envUint("MIN_APPROVALS").toUint8(), multisigMembers: multisigMembers, - // Gauge Voter - tokenParameters: tokenParameters, - feePercent: vm.envUint("FEE_PERCENT").toUint16(), - warmupPeriod: vm.envUint("WARMUP_PERIOD").toUint48(), - cooldownPeriod: vm.envUint("COOLDOWN_PERIOD").toUint48(), - minLockDuration: vm.envUint("MIN_LOCK_DURATION").toUint48(), - votingPaused: vm.envBool("VOTING_PAUSED"), - minDeposit: vm.envUint("MIN_DEPOSIT"), // Standard multisig repo multisigPluginRepo: PluginRepo(vm.envAddress("MULTISIG_PLUGIN_REPO_ADDRESS")), multisigPluginRelease: vm.envUint("MULTISIG_PLUGIN_RELEASE").toUint8(), multisigPluginBuild: vm.envUint("MULTISIG_PLUGIN_BUILD").toUint16(), - // Voter plugin setup and ENS - voterPluginSetup: gaugeVoterPluginSetup, - voterEnsSubdomain: vm.envString("SIMPLE_GAUGE_VOTER_REPO_ENS_SUBDOMAIN"), // OSx addresses osxDaoFactory: vm.envAddress("DAO_FACTORY"), - pluginSetupProcessor: PluginSetupProcessor(vm.envAddress("PLUGIN_SETUP_PROCESSOR")), - pluginRepoFactory: PluginRepoFactory(vm.envAddress("PLUGIN_REPO_FACTORY")) + pluginSetupProcessor: PluginSetupProcessor(vm.envAddress("PLUGIN_SETUP_PROCESSOR")) }); } @@ -91,76 +66,7 @@ contract Deploy is Script { if (result.length == 0) revert EmptyMultisig(); } - function deploySimpleGaugeVoterPluginSetup() internal returns (SimpleGaugeVoterSetup result) { - result = new SimpleGaugeVoterSetup( - address(new SimpleGaugeVoter()), - address(new QuadraticIncreasingEscrow()), - address(new ExitQueue()), - address(new VotingEscrow()), - address(new Clock()), - address(new Lock()) - ); - } - - function getTokenParameters( - bool isProduction - ) internal returns (TokenParameters[] memory tokenParameters) { - if (isProduction) { - // USE TOKEN(s) - console.log("Using production parameters"); - - bool hasTwoTokens = vm.envAddress("TOKEN2_ADDRESS") != address(0); - tokenParameters = new TokenParameters[](hasTwoTokens ? 2 : 1); - - tokenParameters[0] = TokenParameters({ - token: vm.envAddress("TOKEN1_ADDRESS"), - veTokenName: vm.envString("VE_TOKEN1_NAME"), - veTokenSymbol: vm.envString("VE_TOKEN1_SYMBOL") - }); - - if (hasTwoTokens) { - tokenParameters[1] = TokenParameters({ - token: vm.envAddress("TOKEN2_ADDRESS"), - veTokenName: vm.envString("VE_TOKEN2_NAME"), - veTokenSymbol: vm.envString("VE_TOKEN2_SYMBOL") - }); - } - } else { - // MINT TEST TOKEN - console.log("Using testing parameters (minting 2 dev tokens)"); - - address[] memory multisigMembers = readMultisigMembers(); - tokenParameters = new TokenParameters[](2); - tokenParameters[0] = TokenParameters({ - token: createTestToken(multisigMembers), - veTokenName: "VE Token 1", - veTokenSymbol: "veTK1" - }); - tokenParameters[1] = TokenParameters({ - token: createTestToken(multisigMembers), - veTokenName: "VE Token 2", - veTokenSymbol: "veTK2" - }); - } - } - - function createTestToken(address[] memory holders) internal returns (address) { - console.log(""); - MockERC20 newToken = new MockERC20(); - - for (uint i = 0; i < holders.length; ) { - newToken.mint(holders[i], 50 ether); - console.log("Minting 50 eth for", holders[i]); - - unchecked { - i++; - } - } - - return address(newToken); - } - - function printDeploymentSummary(GaugesDaoFactory factory) internal view { + function printDeploymentSummary(MultisigDaoFactory factory) internal view { DeploymentParameters memory deploymentParameters = factory.getDeploymentParameters(); Deployment memory deployment = factory.getDeployment(); @@ -175,32 +81,10 @@ contract Deploy is Script { console.log("- Multisig plugin:", address(deployment.multisigPlugin)); console.log(""); - for (uint i = 0; i < deployment.gaugeVoterPluginSets.length; ) { - console.log("- Using token:", address(deploymentParameters.tokenParameters[i].token)); - console.log( - " Gauge voter plugin:", - address(deployment.gaugeVoterPluginSets[i].plugin) - ); - console.log(" Curve:", address(deployment.gaugeVoterPluginSets[i].curve)); - console.log(" Exit Queue:", address(deployment.gaugeVoterPluginSets[i].exitQueue)); - console.log( - " Voting Escrow:", - address(deployment.gaugeVoterPluginSets[i].votingEscrow) - ); - console.log(" Clock:", address(deployment.gaugeVoterPluginSets[i].clock)); - console.log(" NFT Lock:", address(deployment.gaugeVoterPluginSets[i].nftLock)); - console.log(""); - - unchecked { - i++; - } - } - console.log("Plugin repositories"); console.log( "- Multisig plugin repository (existing):", address(deploymentParameters.multisigPluginRepo) ); - console.log("- Gauge voter plugin repository:", address(deployment.gaugeVoterPluginRepo)); } } diff --git a/script/DeployRouter.s.sol b/script/DeployRouter.s.sol deleted file mode 100644 index b4dff2f..0000000 --- a/script/DeployRouter.s.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import {Script, console} from "forge-std/Script.sol"; -import {VotingEscrow} from "src/voting/SimpleGaugeVoterSetup.sol"; -import {GaugesDaoFactory, GaugePluginSet, Deployment} from "src/factory/GaugesDaoFactory.sol"; - -contract Router { - address public escrow; - - constructor(address _escrow) { - escrow = _escrow; - } - - function getLocked(uint _tokenId) public view returns (uint256) { - return VotingEscrow(escrow).locked(_tokenId).amount; - } - - function getTotalLocked(address _user) public view returns (uint256) { - uint256[] memory ids = VotingEscrow(escrow).ownedTokens(_user); - - uint256 totalLocked = 0; - - for (uint i = 0; i < ids.length; i++) { - totalLocked += VotingEscrow(escrow).locked(ids[i]).amount; - } - return totalLocked; - } -} - -contract DeployRouter is Script { - function run() public { - address factoryAddress = vm.envAddress("FACTORY"); - if (factoryAddress == address(0)) { - revert("Factory address not set"); - } - GaugesDaoFactory factory = GaugesDaoFactory(factoryAddress); - - // set our contracts - Deployment memory deployment = factory.getDeployment(); - - // if deploying multiple tokens, you can adjust the index here - GaugePluginSet memory pluginSet = deployment.gaugeVoterPluginSets[0]; - - vm.startBroadcast(vm.envUint("DEPLOYMENT_PRIVATE_KEY")); - { - Router router = new Router({_escrow: address(pluginSet.votingEscrow)}); - console.log("Router deployed at:", address(router)); - } - vm.stopBroadcast(); - } -} diff --git a/script/multisig-members.json b/script/multisig-members.json index eae9e52..88c3329 100644 --- a/script/multisig-members.json +++ b/script/multisig-members.json @@ -1,3 +1,3 @@ { - "members": ["0x7771c1510509C0dA515BDD12a57dbDd8C58E5363"] + "members": [] } diff --git a/src/clock/Clock.sol b/src/clock/Clock.sol deleted file mode 100644 index 100bef2..0000000 --- a/src/clock/Clock.sol +++ /dev/null @@ -1,226 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -// interfaces -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {IClock} from "./IClock.sol"; - -// contracts -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {DaoAuthorizableUpgradeable as DaoAuthorizable} from "@aragon/osx/core/plugin/dao-authorizable/DaoAuthorizableUpgradeable.sol"; - -/// @title Clock -contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable { - bytes32 public constant CLOCK_ADMIN_ROLE = keccak256("CLOCK_ADMIN_ROLE"); - - /// @dev Epoch encompasses a voting and non-voting period - uint256 internal constant EPOCH_DURATION = 2 weeks; - - /// @dev Checkpoint interval is the time between each voting checkpoint - uint256 internal constant CHECKPOINT_INTERVAL = 1 weeks; - - /// @dev Voting duration is the time during which votes can be cast - uint256 internal constant VOTE_DURATION = 1 weeks; - - /// @dev Opens and closes the voting window slightly early to avoid timing attacks - uint256 internal constant VOTE_WINDOW_BUFFER = 1 hours; - - /*/////////////////////////////////////////////////////////////// - Initialization - //////////////////////////////////////////////////////////////*/ - - constructor() { - _disableInitializers(); - } - - function initialize(address _dao) external initializer { - __DaoAuthorizableUpgradeable_init(IDAO(_dao)); - // uups not needdd - } - - /*/////////////////////////////////////////////////////////////// - Getters - //////////////////////////////////////////////////////////////*/ - - function epochDuration() external pure returns (uint256) { - return EPOCH_DURATION; - } - - function checkpointInterval() external pure returns (uint256) { - return CHECKPOINT_INTERVAL; - } - - function voteDuration() external pure returns (uint256) { - return VOTE_DURATION; - } - - function voteWindowBuffer() external pure returns (uint256) { - return VOTE_WINDOW_BUFFER; - } - - /*/////////////////////////////////////////////////////////////// - Epochs - //////////////////////////////////////////////////////////////*/ - - function currentEpoch() external view returns (uint256) { - return resolveEpoch(block.timestamp); - } - - function resolveEpoch(uint256 timestamp) public pure returns (uint256) { - unchecked { - return timestamp / EPOCH_DURATION; - } - } - - function elapsedInEpoch() external view returns (uint256) { - return resolveElapsedInEpoch(block.timestamp); - } - - function resolveElapsedInEpoch(uint256 timestamp) public pure returns (uint256) { - unchecked { - return timestamp % EPOCH_DURATION; - } - } - - function epochStartsIn() external view returns (uint256) { - return resolveEpochStartsIn(block.timestamp); - } - - /// @notice Number of seconds until the start of the next epoch (relative) - /// @dev If exactly at the start of the epoch, returns 0 - function resolveEpochStartsIn(uint256 timestamp) public pure returns (uint256) { - unchecked { - uint256 elapsed = resolveElapsedInEpoch(timestamp); - return (elapsed == 0) ? 0 : EPOCH_DURATION - elapsed; - } - } - - function epochStartTs() external view returns (uint256) { - return resolveEpochStartTs(block.timestamp); - } - - /// @notice Timestamp of the start of the next epoch (absolute) - function resolveEpochStartTs(uint256 timestamp) public pure returns (uint256) { - unchecked { - return timestamp + resolveEpochStartsIn(timestamp); - } - } - - /*/////////////////////////////////////////////////////////////// - Voting - //////////////////////////////////////////////////////////////*/ - - function votingActive() external view returns (bool) { - return resolveVotingActive(block.timestamp); - } - - function resolveVotingActive(uint256 timestamp) public pure returns (bool) { - bool afterVoteStart = timestamp >= resolveEpochVoteStartTs(timestamp); - bool beforeVoteEnd = timestamp < resolveEpochVoteEndTs(timestamp); - return afterVoteStart && beforeVoteEnd; - } - - function epochVoteStartsIn() external view returns (uint256) { - return resolveEpochVoteStartsIn(block.timestamp); - } - - /// @notice Number of seconds until voting starts. - /// @dev If voting is active, returns 0. - function resolveEpochVoteStartsIn(uint256 timestamp) public pure returns (uint256) { - unchecked { - uint256 elapsed = resolveElapsedInEpoch(timestamp); - - // if less than the offset has past, return the time until the offset - if (elapsed < VOTE_WINDOW_BUFFER) { - return VOTE_WINDOW_BUFFER - elapsed; - } - // if voting is active (we are in the voting period) return 0 - else if (elapsed < VOTE_DURATION - VOTE_WINDOW_BUFFER) { - return 0; - } - // else return the time until the next epoch + the offset - else return resolveEpochStartsIn(timestamp) + VOTE_WINDOW_BUFFER; - } - } - - function epochVoteStartTs() external view returns (uint256) { - return resolveEpochVoteStartTs(block.timestamp); - } - - /// @notice Timestamp of the start of the next voting period (absolute) - function resolveEpochVoteStartTs(uint256 timestamp) public pure returns (uint256) { - unchecked { - return timestamp + resolveEpochVoteStartsIn(timestamp); - } - } - - function epochVoteEndsIn() external view returns (uint256) { - return resolveEpochVoteEndsIn(block.timestamp); - } - - /// @notice Number of seconds until the end of the current voting period (relative) - /// @dev If we are outside the voting period, returns 0 - function resolveEpochVoteEndsIn(uint256 timestamp) public pure returns (uint256) { - unchecked { - uint256 elapsed = resolveElapsedInEpoch(timestamp); - uint VOTING_WINDOW = VOTE_DURATION - VOTE_WINDOW_BUFFER; - // if we are outside the voting period, return 0 - if (elapsed >= VOTING_WINDOW) return 0; - // if we are in the voting period, return the remaining time - else return VOTING_WINDOW - elapsed; - } - } - - function epochVoteEndTs() external view returns (uint256) { - return resolveEpochVoteEndTs(block.timestamp); - } - - /// @notice Timestamp of the end of the current voting period (absolute) - function resolveEpochVoteEndTs(uint256 timestamp) public pure returns (uint256) { - unchecked { - return timestamp + resolveEpochVoteEndsIn(timestamp); - } - } - - /*/////////////////////////////////////////////////////////////// - Checkpointing - //////////////////////////////////////////////////////////////*/ - - function epochNextCheckpointIn() external view returns (uint256) { - return resolveEpochNextCheckpointIn(block.timestamp); - } - - /// @notice Number of seconds until the next checkpoint interval (relative) - /// @dev If exactly at the start of the checkpoint interval, returns 0 - function resolveEpochNextCheckpointIn(uint256 timestamp) public pure returns (uint256) { - unchecked { - uint256 elapsed = resolveElapsedInEpoch(timestamp); - // elapsed > deposit interval, then subtract the interval - if (elapsed >= CHECKPOINT_INTERVAL) elapsed -= CHECKPOINT_INTERVAL; - return CHECKPOINT_INTERVAL - elapsed; - } - } - - function epochNextCheckpointTs() external view returns (uint256) { - return resolveEpochNextCheckpointTs(block.timestamp); - } - - /// @notice Timestamp of the next deposit interval (absolute) - function resolveEpochNextCheckpointTs(uint256 timestamp) public pure returns (uint256) { - unchecked { - return timestamp + resolveEpochNextCheckpointIn(timestamp); - } - } - - /*/////////////////////////////////////////////////////////////// - UUPS Getters - //////////////////////////////////////////////////////////////*/ - - function _authorizeUpgrade(address) internal override auth(CLOCK_ADMIN_ROLE) {} - - function implementation() external view returns (address) { - return _getImplementation(); - } - - uint256[50] private __gap; -} diff --git a/src/clock/IClock.sol b/src/clock/IClock.sol deleted file mode 100644 index 7fb19dc..0000000 --- a/src/clock/IClock.sol +++ /dev/null @@ -1,60 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IClockUser { - function clock() external view returns (address); -} - -interface IClock { - function epochDuration() external pure returns (uint256); - - function checkpointInterval() external pure returns (uint256); - - function voteDuration() external pure returns (uint256); - - function voteWindowBuffer() external pure returns (uint256); - - function currentEpoch() external view returns (uint256); - - function resolveEpoch(uint256 timestamp) external pure returns (uint256); - - function elapsedInEpoch() external view returns (uint256); - - function resolveElapsedInEpoch(uint256 timestamp) external pure returns (uint256); - - function epochStartsIn() external view returns (uint256); - - function resolveEpochStartsIn(uint256 timestamp) external pure returns (uint256); - - function epochStartTs() external view returns (uint256); - - function resolveEpochStartTs(uint256 timestamp) external pure returns (uint256); - - function votingActive() external view returns (bool); - - function resolveVotingActive(uint256 timestamp) external pure returns (bool); - - function epochVoteStartsIn() external view returns (uint256); - - function resolveEpochVoteStartsIn(uint256 timestamp) external pure returns (uint256); - - function epochVoteStartTs() external view returns (uint256); - - function resolveEpochVoteStartTs(uint256 timestamp) external pure returns (uint256); - - function epochVoteEndsIn() external view returns (uint256); - - function resolveEpochVoteEndsIn(uint256 timestamp) external pure returns (uint256); - - function epochVoteEndTs() external view returns (uint256); - - function resolveEpochVoteEndTs(uint256 timestamp) external pure returns (uint256); - - function epochNextCheckpointIn() external view returns (uint256); - - function resolveEpochNextCheckpointIn(uint256 timestamp) external pure returns (uint256); - - function epochNextCheckpointTs() external view returns (uint256); - - function resolveEpochNextCheckpointTs(uint256 timestamp) external pure returns (uint256); -} diff --git a/src/escrow/increasing/ExitQueue.sol b/src/escrow/increasing/ExitQueue.sol deleted file mode 100644 index 8539d93..0000000 --- a/src/escrow/increasing/ExitQueue.sol +++ /dev/null @@ -1,230 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {IExitQueue} from "./interfaces/IExitQueue.sol"; -import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import {IVotingEscrowIncreasing as IVotingEscrow} from "@escrow-interfaces/IVotingEscrowIncreasing.sol"; -import {IClockUser, IClock} from "@clock/IClock.sol"; - -import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; - -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {DaoAuthorizableUpgradeable as DaoAuthorizable} from "@aragon/osx/core/plugin/dao-authorizable/DaoAuthorizableUpgradeable.sol"; - -/// @title ExitQueue -/// @notice Token IDs associated with an NFT are given a ticket when they are queued for exit. -/// After a cooldown period, the ticket holder can exit the NFT. -contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable { - using SafeERC20 for IERC20; - - /// @notice role required to manage the exit queue - bytes32 public constant QUEUE_ADMIN_ROLE = keccak256("QUEUE_ADMIN"); - - /// @notice role required to withdraw tokens from the escrow contract - bytes32 public constant WITHDRAW_ROLE = keccak256("WITHDRAW_ROLE"); - - /// @dev 10_000 = 100% - uint16 private constant MAX_FEE_PERCENT = 10_000; - - /// @notice the fee percent charged on withdrawals - uint256 public feePercent; - - /// @notice address of the escrow contract - address public escrow; - - /// @notice clock contract for epoch duration - address public clock; - - /// @notice time in seconds between exit and withdrawal - uint48 public cooldown; - - /// @notice minimum time from the original lock date before one can enter the queue - uint48 public minLock; - - /// @notice tokenId => Ticket - mapping(uint256 => Ticket) internal _queue; - - /*////////////////////////////////////////////////////////////// - Constructor - //////////////////////////////////////////////////////////////*/ - constructor() { - _disableInitializers(); - } - - /// @param _escrow address of the escrow contract where tokens are stored - /// @param _cooldown time in seconds between exit and withdrawal - /// @param _dao address of the DAO that will be able to set the queue - function initialize( - address _escrow, - uint48 _cooldown, - address _dao, - uint256 _feePercent, - address _clock, - uint48 _minLock - ) external initializer { - __DaoAuthorizableUpgradeable_init(IDAO(_dao)); - escrow = _escrow; - clock = _clock; - _setMinLock(_minLock); - _setFeePercent(_feePercent); - _setCooldown(_cooldown); - } - - /*////////////////////////////////////////////////////////////// - Modifiers - //////////////////////////////////////////////////////////////*/ - - modifier onlyEscrow() { - if (msg.sender != escrow) revert OnlyEscrow(); - _; - } - - /*////////////////////////////////////////////////////////////// - Admin Functions - //////////////////////////////////////////////////////////////*/ - - /// @notice The exit queue manager can set the cooldown period - /// @param _cooldown time in seconds between exit and withdrawal - function setCooldown(uint48 _cooldown) external auth(QUEUE_ADMIN_ROLE) { - _setCooldown(_cooldown); - } - - function _setCooldown(uint48 _cooldown) internal { - cooldown = _cooldown; - emit CooldownSet(_cooldown); - } - - /// @notice The exit queue manager can set the fee percent - /// @param _feePercent the fee percent charged on withdrawals - function setFeePercent(uint256 _feePercent) external auth(QUEUE_ADMIN_ROLE) { - _setFeePercent(_feePercent); - } - - function _setFeePercent(uint256 _feePercent) internal { - if (_feePercent > MAX_FEE_PERCENT) revert FeeTooHigh(MAX_FEE_PERCENT); - feePercent = _feePercent; - emit FeePercentSet(_feePercent); - } - - /// @notice The exit queue manager can set the minimum lock time - /// @param _minLock the minimum time from the original lock date before one can enter the queue - /// @dev Min 1 second to prevent single block deposit-withdrawal attacks - function setMinLock(uint48 _minLock) external auth(QUEUE_ADMIN_ROLE) { - _setMinLock(_minLock); - } - - function _setMinLock(uint48 _minLock) internal { - if (_minLock == 0) revert MinLockOutOfBounds(); - minLock = _minLock; - emit MinLockSet(_minLock); - } - - /*////////////////////////////////////////////////////////////// - WITHDRAWER - //////////////////////////////////////////////////////////////*/ - - /// @notice withdraw staked tokens sent as part of fee collection to the caller - /// @dev The caller must be authorized to withdraw by the DAO - function withdraw(uint256 _amount) external auth(WITHDRAW_ROLE) { - IERC20 underlying = IERC20(IVotingEscrow(escrow).token()); - underlying.transfer(msg.sender, _amount); - } - - /*////////////////////////////////////////////////////////////// - Exit Logic - //////////////////////////////////////////////////////////////*/ - - /// @notice queue an exit for a given tokenId, granting the ticket to the passed holder - /// @param _tokenId the tokenId to queue an exit for - /// @param _ticketHolder the address that will be granted the ticket - /// @dev we don't check that the ticket holder is the caller - /// this is because the escrow contract is the only one that can queue an exit - /// and we leave that logic to the escrow contract - function queueExit(uint256 _tokenId, address _ticketHolder) external onlyEscrow { - if (_ticketHolder == address(0)) revert ZeroAddress(); - if (_queue[_tokenId].holder != address(0)) revert AlreadyQueued(); - - // get time to min lock and revert if it hasn't been reached - uint48 minLockTime = timeToMinLock(_tokenId); - if (minLockTime > block.timestamp) revert MinLockNotReached(_tokenId, minLock, minLockTime); - - uint exitDate = nextExitDate(); - - _queue[_tokenId] = Ticket(_ticketHolder, exitDate); - emit ExitQueued(_tokenId, _ticketHolder, exitDate); - } - - /// @notice Returns the next exit date for a ticket - /// @dev The next exit date is the later of the cooldown expiry and the next checkpoint - function nextExitDate() public view returns (uint256) { - // snap to next checkpoint interval, we can't cooldown before this - uint nextCP = IClock(clock).epochNextCheckpointTs(); - uint cooldownExpiry = block.timestamp + cooldown; - - // if the next cp is after the cooldown, return the next cp - return nextCP > cooldownExpiry ? nextCP : cooldownExpiry; - } - - /// @notice Exits the queue for that tokenID. - /// @dev The holder is not checked. This is left up to the escrow contract to manage. - function exit(uint256 _tokenId) external onlyEscrow returns (uint256 fee) { - if (!canExit(_tokenId)) revert CannotExit(); - - // reset the ticket for that tokenId - _queue[_tokenId] = Ticket(address(0), 0); - - // return the fee to the caller - fee = calculateFee(_tokenId); - emit Exit(_tokenId, fee); - } - - /// @notice Calculate the exit fee for a given tokenId - function calculateFee(uint256 _tokenId) public view returns (uint256) { - if (feePercent == 0) return 0; - uint underlyingBalance = IVotingEscrow(escrow).locked(_tokenId).amount; - if (underlyingBalance == 0) revert NoLockBalance(); - return (underlyingBalance * feePercent) / MAX_FEE_PERCENT; - } - - /*////////////////////////////////////////////////////////////// - View Functions - //////////////////////////////////////////////////////////////*/ - - /// @return true if the tokenId corresponds to a valid ticket and the cooldown period has passed - /// @dev If the admin chages the cooldown, this will affect all ticket holders. We may not want this. - function canExit(uint256 _tokenId) public view returns (bool) { - Ticket memory ticket = _queue[_tokenId]; - if (ticket.holder == address(0)) return false; - return block.timestamp > ticket.exitDate; - } - - /// @return holder of a ticket for a given tokenId - function ticketHolder(uint256 _tokenId) external view returns (address) { - return _queue[_tokenId].holder; - } - - function queue(uint256 _tokenId) external view override returns (Ticket memory) { - return _queue[_tokenId]; - } - - function timeToMinLock(uint256 _tokenId) public view returns (uint48) { - uint48 lockStart = IVotingEscrow(escrow).locked(_tokenId).start; - return lockStart + minLock; - } - - /*/////////////////////////////////////////////////////////////// - UUPS Upgrade - //////////////////////////////////////////////////////////////*/ - - /// @notice Returns the address of the implementation contract in the [proxy storage slot](https://eips.ethereum.org/EIPS/eip-1967) slot the [UUPS proxy](https://eips.ethereum.org/EIPS/eip-1822) is pointing to. - /// @return The address of the implementation contract. - function implementation() public view returns (address) { - return _getImplementation(); - } - - /// @notice Internal method authorizing the upgrade of the contract via the [upgradeability mechanism for UUPS proxies](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable) (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). - function _authorizeUpgrade(address) internal virtual override auth(QUEUE_ADMIN_ROLE) {} - - uint256[46] private __gap; -} diff --git a/src/escrow/increasing/Lock.sol b/src/escrow/increasing/Lock.sol deleted file mode 100644 index b37dd86..0000000 --- a/src/escrow/increasing/Lock.sol +++ /dev/null @@ -1,127 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {ILock} from "@escrow-interfaces/ILock.sol"; -import {ERC721EnumerableUpgradeable as ERC721Enumerable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; -import {ReentrancyGuardUpgradeable as ReentrancyGuard} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {DaoAuthorizableUpgradeable as DaoAuthorizable} from "@aragon/osx/core/plugin/dao-authorizable/DaoAuthorizableUpgradeable.sol"; -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; - -/// @title NFT representation of an escrow locking mechanism -contract Lock is ILock, ERC721Enumerable, UUPSUpgradeable, DaoAuthorizable, ReentrancyGuard { - /// @dev enables transfers without whitelisting - address public constant WHITELIST_ANY_ADDRESS = - address(uint160(uint256(keccak256("WHITELIST_ANY_ADDRESS")))); - - /// @notice role to upgrade this contract - bytes32 public constant LOCK_ADMIN_ROLE = keccak256("LOCK_ADMIN"); - - /// @notice Address of the escrow contract that holds underyling assets - address public escrow; - - /// @notice Whitelisted contracts that are allowed to transfer - mapping(address => bool) public whitelisted; - - /*////////////////////////////////////////////////////////////// - Modifiers - //////////////////////////////////////////////////////////////*/ - - modifier onlyEscrow() { - if (msg.sender != escrow) revert OnlyEscrow(); - _; - } - - /*////////////////////////////////////////////////////////////// - ERC165 - //////////////////////////////////////////////////////////////*/ - - function supportsInterface( - bytes4 _interfaceId - ) public view override(ERC721Enumerable) returns (bool) { - return super.supportsInterface(_interfaceId) || _interfaceId == type(ILock).interfaceId; - } - - /*////////////////////////////////////////////////////////////// - Initializer - //////////////////////////////////////////////////////////////*/ - - constructor() { - _disableInitializers(); - } - - function initialize( - address _escrow, - string memory _name, - string memory _symbol, - address _dao - ) external initializer { - __ERC721_init(_name, _symbol); - __DaoAuthorizableUpgradeable_init(IDAO(_dao)); - __ReentrancyGuard_init(); - escrow = _escrow; - - // allow sending nfts to the escrow - whitelisted[escrow] = true; - emit WhitelistSet(address(escrow), true); - } - - /*////////////////////////////////////////////////////////////// - Transfers - //////////////////////////////////////////////////////////////*/ - - /// @notice Transfers disabled by default, only whitelisted addresses can receive transfers - function setWhitelisted(address _account, bool _isWhitelisted) external auth(LOCK_ADMIN_ROLE) { - if (_account == escrow) revert ForbiddenWhitelistAddress(); - whitelisted[_account] = _isWhitelisted; - emit WhitelistSet(_account, _isWhitelisted); - } - - /// @notice Enable transfers to any address without whitelisting - function enableTransfers() external auth(LOCK_ADMIN_ROLE) { - whitelisted[WHITELIST_ANY_ADDRESS] = true; - emit WhitelistSet(WHITELIST_ANY_ADDRESS, true); - } - - /// @dev Override the transfer to check if the recipient is whitelisted - /// This avoids needing to check for mint/burn but is less idomatic than beforeTokenTransfer - function _transfer(address _from, address _to, uint256 _tokenId) internal override { - if (whitelisted[WHITELIST_ANY_ADDRESS] || whitelisted[_to]) { - super._transfer(_from, _to, _tokenId); - } else revert NotWhitelisted(); - } - - /*////////////////////////////////////////////////////////////// - NFT Functions - //////////////////////////////////////////////////////////////*/ - - function isApprovedOrOwner(address _spender, uint256 _tokenId) external view returns (bool) { - return _isApprovedOrOwner(_spender, _tokenId); - } - - /// @notice Minting and burning functions that can only be called by the escrow contract - /// @dev Safe mint ensures contract addresses are ERC721 Receiver contracts - function mint(address _to, uint256 _tokenId) external onlyEscrow nonReentrant { - _safeMint(_to, _tokenId); - } - - /// @notice Minting and burning functions that can only be called by the escrow contract - function burn(uint256 _tokenId) external onlyEscrow nonReentrant { - _burn(_tokenId); - } - - /*////////////////////////////////////////////////////////////// - UUPS Upgrade - //////////////////////////////////////////////////////////////*/ - - /// @notice Returns the address of the implementation contract in the [proxy storage slot](https://eips.ethereum.org/EIPS/eip-1967) slot the [UUPS proxy](https://eips.ethereum.org/EIPS/eip-1822) is pointing to. - /// @return The address of the implementation contract. - function implementation() public view returns (address) { - return _getImplementation(); - } - - /// @notice Internal method authorizing the upgrade of the contract via the [upgradeability mechanism for UUPS proxies](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable) (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). - function _authorizeUpgrade(address) internal virtual override auth(LOCK_ADMIN_ROLE) {} - - uint256[48] private __gap; -} diff --git a/src/escrow/increasing/QuadraticIncreasingEscrow.sol b/src/escrow/increasing/QuadraticIncreasingEscrow.sol deleted file mode 100644 index ea1b966..0000000 --- a/src/escrow/increasing/QuadraticIncreasingEscrow.sol +++ /dev/null @@ -1,352 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -// interfaces -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {IVotingEscrowIncreasing as IVotingEscrow} from "@escrow-interfaces/IVotingEscrowIncreasing.sol"; -import {IEscrowCurveIncreasing as IEscrowCurve} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {IClockUser, IClock} from "@clock/IClock.sol"; - -// libraries -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {SignedFixedPointMath} from "@libs/SignedFixedPointMathLib.sol"; -import {CurveConstantLib} from "@libs/CurveConstantLib.sol"; - -// contracts -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {ReentrancyGuardUpgradeable as ReentrancyGuard} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import {DaoAuthorizableUpgradeable as DaoAuthorizable} from "@aragon/osx/core/plugin/dao-authorizable/DaoAuthorizableUpgradeable.sol"; - -/// @title Quadratic Increasing Escrow -contract QuadraticIncreasingEscrow is - IEscrowCurve, - IClockUser, - ReentrancyGuard, - DaoAuthorizable, - UUPSUpgradeable -{ - using SafeERC20 for IERC20; - using SafeCast for int256; - using SafeCast for uint256; - using SignedFixedPointMath for int256; - - error OnlyEscrow(); - - /// @notice Administrator role for the contract - bytes32 public constant CURVE_ADMIN_ROLE = keccak256("CURVE_ADMIN_ROLE"); - - /// @notice The VotingEscrow contract address - address public escrow; - - /// @notice The Clock contract address - address public clock; - - /// @notice tokenId => point epoch: incremented on a per-tokenId basis - mapping(uint256 => uint256) public tokenPointIntervals; - - /// @notice The warmup period for the curve - uint48 public warmupPeriod; - - /// @dev tokenId => tokenPointIntervals => TokenPoint - /// @dev The Array is fixed so we can write to it in the future - /// This implementation means that very short intervals may be challenging - mapping(uint256 => TokenPoint[1_000_000_000]) internal _tokenPointHistory; - - /*////////////////////////////////////////////////////////////// - MATH - //////////////////////////////////////////////////////////////*/ - - /// @dev precomputed coefficients of the quadratic curve - int256 private constant SHARED_QUADRATIC_COEFFICIENT = - CurveConstantLib.SHARED_QUADRATIC_COEFFICIENT; - - int256 private constant SHARED_LINEAR_COEFFICIENT = CurveConstantLib.SHARED_LINEAR_COEFFICIENT; - - int256 private constant SHARED_CONSTANT_COEFFICIENT = - CurveConstantLib.SHARED_CONSTANT_COEFFICIENT; - - uint256 private constant MAX_EPOCHS = CurveConstantLib.MAX_EPOCHS; - - /*////////////////////////////////////////////////////////////// - INITIALIZATION - //////////////////////////////////////////////////////////////*/ - - constructor() { - _disableInitializers(); - } - - /// @param _escrow VotingEscrow contract address - function initialize( - address _escrow, - address _dao, - uint48 _warmupPeriod, - address _clock - ) external initializer { - escrow = _escrow; - warmupPeriod = _warmupPeriod; - clock = _clock; - - __DaoAuthorizableUpgradeable_init(IDAO(_dao)); - __ReentrancyGuard_init(); - - // other initializers are empty - } - - /*////////////////////////////////////////////////////////////// - CURVE COEFFICIENTS - //////////////////////////////////////////////////////////////*/ - - /// @return The coefficient for the quadratic term of the quadratic curve, for the given amount - function _getQuadraticCoeff(uint256 amount) internal pure returns (int256) { - return (SignedFixedPointMath.toFP(amount.toInt256()).mul(SHARED_QUADRATIC_COEFFICIENT)); - } - - /// @return The coefficient for the linear term of the quadratic curve, for the given amount - function _getLinearCoeff(uint256 amount) internal pure returns (int256) { - return (SignedFixedPointMath.toFP(amount.toInt256())).mul(SHARED_LINEAR_COEFFICIENT); - } - - /// @return The constant coefficient of the quadratic curve, for the given amount - /// @dev In this case, the constant term is 1 so we just case the amount - function _getConstantCoeff(uint256 amount) public pure returns (int256) { - return (SignedFixedPointMath.toFP(amount.toInt256())).mul(SHARED_CONSTANT_COEFFICIENT); - } - - /// @return The coefficients of the quadratic curve, for the given amount - /// @dev The coefficients are returned in the order [constant, linear, quadratic] - function _getCoefficients(uint256 amount) public pure returns (int256[3] memory) { - return [_getConstantCoeff(amount), _getLinearCoeff(amount), _getQuadraticCoeff(amount)]; - } - - /// @return The coefficients of the quadratic curve, for the given amount - /// @dev The coefficients are returned in the order [constant, linear, quadratic] - /// and are converted to regular 256-bit signed integers instead of their fixed-point representation - function getCoefficients(uint256 amount) public pure returns (int256[3] memory) { - int256[3] memory coefficients = _getCoefficients(amount); - - return [ - SignedFixedPointMath.fromFP(coefficients[0]), - SignedFixedPointMath.fromFP(coefficients[1]), - SignedFixedPointMath.fromFP(coefficients[2]) - ]; - } - - /*////////////////////////////////////////////////////////////// - CURVE BIAS - //////////////////////////////////////////////////////////////*/ - - /// @notice Returns the bias for the given time elapsed and amount, up to the maximum time - function getBias(uint256 timeElapsed, uint256 amount) public view returns (uint256) { - int256[3] memory coefficients = _getCoefficients(amount); - return _getBias(timeElapsed, coefficients); - } - - function _getBias( - uint256 timeElapsed, - int256[3] memory coefficients - ) internal view returns (uint256) { - int256 quadratic = coefficients[2]; - int256 linear = coefficients[1]; - int256 const = coefficients[0]; - - // bound the time elapsed to the maximum time - uint256 MAX_TIME = _maxTime(); - timeElapsed = timeElapsed > MAX_TIME ? MAX_TIME : timeElapsed; - - // convert the time to fixed point - int256 t = SignedFixedPointMath.toFP(timeElapsed.toInt256()); - - // bias = a.t^2 + b.t + c - int256 tSquared = t.mul(t); // t*t much more gas efficient than t.pow(SD2) - int256 bias = quadratic.mul(tSquared).add(linear.mul(t)).add(const); - - // never return negative values - // in the increasing case, this should never happen - return bias.lt((0)) ? uint256(0) : SignedFixedPointMath.fromFP((bias)).toUint256(); - } - - function _maxTime() internal view returns (uint256) { - return IClock(clock).epochDuration() * MAX_EPOCHS; - } - - function previewMaxBias(uint256 amount) external view returns (uint256) { - return getBias(_maxTime(), amount); - } - - /*////////////////////////////////////////////////////////////// - Warmup - //////////////////////////////////////////////////////////////*/ - - function setWarmupPeriod(uint48 _warmupPeriod) external auth(CURVE_ADMIN_ROLE) { - warmupPeriod = _warmupPeriod; - emit WarmupSet(_warmupPeriod); - } - - /// @notice Returns whether the NFT is warm - function isWarm(uint256 tokenId) public view returns (bool) { - uint256 interval = _getPastTokenPointInterval(tokenId, block.timestamp); - TokenPoint memory point = _tokenPointHistory[tokenId][interval]; - if (point.bias == 0) return false; - else return _isWarm(point); - } - - function _isWarm(TokenPoint memory _point) public view returns (bool) { - return block.timestamp > _point.writtenTs + warmupPeriod; - } - - /*////////////////////////////////////////////////////////////// - BALANCE - //////////////////////////////////////////////////////////////*/ - - /// @notice Returns the TokenPoint at the passed interval - /// @param _tokenId The NFT to return the TokenPoint for - /// @param _tokenInterval The epoch to return the TokenPoint at - function tokenPointHistory( - uint256 _tokenId, - uint256 _tokenInterval - ) external view returns (TokenPoint memory) { - return _tokenPointHistory[_tokenId][_tokenInterval]; - } - - /// @notice Binary search to get the token point interval for a token id at or prior to a given timestamp - /// Once we have the point, we can apply the bias calculation to get the voting power. - /// @dev If a token point does not exist prior to the timestamp, this will return 0. - function _getPastTokenPointInterval( - uint256 _tokenId, - uint256 _timestamp - ) internal view returns (uint256) { - uint256 tokenInterval = tokenPointIntervals[_tokenId]; - if (tokenInterval == 0) return 0; - - // if the most recent point is before the timestamp, return it - if (_tokenPointHistory[_tokenId][tokenInterval].checkpointTs <= _timestamp) - return (tokenInterval); - - // Check if the first balance is after the timestamp - // this means that the first epoch has yet to start - if (_tokenPointHistory[_tokenId][1].checkpointTs > _timestamp) return 0; - - uint256 lower = 0; - uint256 upper = tokenInterval; - while (upper > lower) { - uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow - TokenPoint storage tokenPoint = _tokenPointHistory[_tokenId][center]; - if (tokenPoint.checkpointTs == _timestamp) { - return center; - } else if (tokenPoint.checkpointTs < _timestamp) { - lower = center; - } else { - upper = center - 1; - } - } - return lower; - } - - function votingPowerAt(uint256 _tokenId, uint256 _t) external view returns (uint256) { - uint256 interval = _getPastTokenPointInterval(_tokenId, _t); - - // epoch 0 is an empty point - if (interval == 0) return 0; - TokenPoint memory lastPoint = _tokenPointHistory[_tokenId][interval]; - - if (!_isWarm(lastPoint)) return 0; - uint256 timeElapsed = _t - lastPoint.checkpointTs; - - return _getBias(timeElapsed, lastPoint.coefficients); - } - - /// @notice [NOT IMPLEMENTED] Calculate total voting power at some point in the past - /// @dev This function will be implemented in a future version of the contract - function supplyAt(uint256) external pure returns (uint256) { - revert("Supply Not Implemented"); - } - - /*////////////////////////////////////////////////////////////// - CHECKPOINT - //////////////////////////////////////////////////////////////*/ - - /// @notice A checkpoint can be called by the VotingEscrow contract to snapshot the user's voting power - function checkpoint( - uint256 _tokenId, - IVotingEscrow.LockedBalance memory _oldLocked, - IVotingEscrow.LockedBalance memory _newLocked - ) external nonReentrant { - if (msg.sender != escrow) revert OnlyEscrow(); - _checkpoint(_tokenId, _oldLocked, _newLocked); - } - - /// @notice Record gper-user data to checkpoints. Used by VotingEscrow system. - /// @dev Curve finance style but just for users at this stage - /// @param _tokenId NFT token ID. - /// @param _newLocked New locked amount / end lock time for the user - function _checkpoint( - uint256 _tokenId, - IVotingEscrow.LockedBalance memory /* _oldLocked */, - IVotingEscrow.LockedBalance memory _newLocked - ) internal { - // this implementation doesn't yet support manual checkpointing - if (_tokenId == 0) revert InvalidTokenId(); - - // instantiate a new, empty token point - TokenPoint memory uNew; - uint amount = _newLocked.amount; - bool isExiting = amount == 0; - - if (!isExiting) { - int256[3] memory coefficients = _getCoefficients(amount); - // for a new lock, write the base bias (elapsed == 0) - uNew.coefficients = coefficients; - uNew.bias = _getBias(0, coefficients); - } - // write the new timestamp - in the case of an increasing curve - // we align the checkpoint to the start of the upcoming deposit interval - // to ensure global slope changes can be scheduled - // NOTE: the above global functionality is not implemented in this version of the contracts - // safe to cast as .start is 48 bit unsigned - uNew.checkpointTs = uint128(_newLocked.start); - - // log the written ts - this can be used to compute warmups and burn downs - uNew.writtenTs = block.timestamp.toUint128(); - - // check to see if we have an existing interval for this token - uint256 tokenInterval = tokenPointIntervals[_tokenId]; - - // if we don't have a point, we can write to the first interval - if (tokenInterval == 0) { - tokenPointIntervals[_tokenId] = ++tokenInterval; - } - // else we need to check the last point - else { - TokenPoint memory lastPoint = _tokenPointHistory[_tokenId][tokenInterval]; - - // can't do this: we can only write to same point or future - if (lastPoint.checkpointTs > uNew.checkpointTs) revert InvalidCheckpoint(); - - // if we're writing to a new point, increment the interval - if (lastPoint.checkpointTs != uNew.checkpointTs) { - tokenPointIntervals[_tokenId] = ++tokenInterval; - } - } - - // Record the new point - _tokenPointHistory[_tokenId][tokenInterval] = uNew; - } - - /*/////////////////////////////////////////////////////////////// - UUPS Upgrade - //////////////////////////////////////////////////////////////*/ - - /// @notice Returns the address of the implementation contract in the [proxy storage slot](https://eips.ethereum.org/EIPS/eip-1967) slot the [UUPS proxy](https://eips.ethereum.org/EIPS/eip-1822) is pointing to. - /// @return The address of the implementation contract. - function implementation() public view returns (address) { - return _getImplementation(); - } - - /// @notice Internal method authorizing the upgrade of the contract via the [upgradeability mechanism for UUPS proxies](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable) (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). - function _authorizeUpgrade(address) internal virtual override auth(CURVE_ADMIN_ROLE) {} - - /// @dev gap for upgradeable contract - uint256[45] private __gap; -} diff --git a/src/escrow/increasing/VotingEscrowIncreasing.sol b/src/escrow/increasing/VotingEscrowIncreasing.sol deleted file mode 100644 index 5da0f60..0000000 --- a/src/escrow/increasing/VotingEscrowIncreasing.sol +++ /dev/null @@ -1,412 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -// token interfaces -import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import {IERC20MetadataUpgradeable as IERC20Metadata} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; -import {IERC721EnumerableMintableBurnable as IERC721EMB} from "./interfaces/IERC721EMB.sol"; - -// veGovernance -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {ISimpleGaugeVoter} from "@voting/ISimpleGaugeVoter.sol"; -import {IClock} from "@clock/IClock.sol"; -import {IEscrowCurveIncreasing as IEscrowCurve} from "./interfaces/IEscrowCurveIncreasing.sol"; -import {IExitQueue} from "./interfaces/IExitQueue.sol"; -import {IVotingEscrowIncreasing as IVotingEscrow} from "./interfaces/IVotingEscrowIncreasing.sol"; - -// libraries -import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; -import {SafeCastUpgradeable as SafeCast} from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; - -// parents -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {ReentrancyGuardUpgradeable as ReentrancyGuard} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import {PausableUpgradeable as Pausable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; -import {DaoAuthorizableUpgradeable as DaoAuthorizable} from "@aragon/osx/core/plugin/dao-authorizable/DaoAuthorizableUpgradeable.sol"; - -contract VotingEscrow is - IVotingEscrow, - ReentrancyGuard, - Pausable, - DaoAuthorizable, - UUPSUpgradeable -{ - using SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @notice Role required to manage the Escrow curve, this typically will be the DAO - bytes32 public constant ESCROW_ADMIN_ROLE = keccak256("ESCROW_ADMIN"); - - /// @notice Role required to pause the contract - can be given to emergency contracts - bytes32 public constant PAUSER_ROLE = keccak256("PAUSER"); - - /// @notice Role required to withdraw underlying tokens from the contract - bytes32 public constant SWEEPER_ROLE = keccak256("SWEEPER"); - - /*////////////////////////////////////////////////////////////// - NFT Data - //////////////////////////////////////////////////////////////*/ - - /// @notice Decimals of the voting power - uint8 public constant decimals = 18; - - /// @notice Minimum deposit amount - uint256 public minDeposit; - - /// @notice Auto-incrementing ID for the most recently created lock, does not decrease on withdrawal - uint256 public lastLockId; - - /// @notice Total supply of underlying tokens deposited in the contract - uint256 public totalLocked; - - /// @dev tracks the locked balance of each NFT - mapping(uint256 => LockedBalance) private _locked; - - /*////////////////////////////////////////////////////////////// - Helper Contracts - //////////////////////////////////////////////////////////////*/ - - /// @notice Address of the underying ERC20 token. - /// @dev Only tokens with 18 decimals and no transfer fees are supported - address public token; - - /// @notice Address of the gauge voting contract. - /// @dev We need to ensure votes are not left in this contract before allowing positing changes - address public voter; - - /// @notice Address of the voting Escrow Curve contract that will calculate the voting power - address public curve; - - /// @notice Address of the contract that manages exit queue logic for withdrawals - address public queue; - - /// @notice Address of the clock contract that manages epoch and voting periods - address public clock; - - /// @notice Address of the NFT contract that is the lock - address public lockNFT; - - bool private _lockNFTSet; - - /*////////////////////////////////////////////////////////////// - Initialization - //////////////////////////////////////////////////////////////*/ - - constructor() { - _disableInitializers(); - } - - function initialize( - address _token, - address _dao, - address _clock, - uint256 _initialMinDeposit - ) external initializer { - __DaoAuthorizableUpgradeable_init(IDAO(_dao)); - __ReentrancyGuard_init(); - __Pausable_init(); - - if (IERC20Metadata(_token).decimals() != 18) revert MustBe18Decimals(); - token = _token; - clock = _clock; - minDeposit = _initialMinDeposit; - emit MinDepositSet(_initialMinDeposit); - } - - /*////////////////////////////////////////////////////////////// - Admin Setters - //////////////////////////////////////////////////////////////*/ - - /// @notice Sets the curve contract that calculates the voting power - function setCurve(address _curve) external auth(ESCROW_ADMIN_ROLE) { - curve = _curve; - } - - /// @notice Sets the voter contract that tracks votes - function setVoter(address _voter) external auth(ESCROW_ADMIN_ROLE) { - voter = _voter; - } - - /// @notice Sets the exit queue contract that manages withdrawal eligibility - function setQueue(address _queue) external auth(ESCROW_ADMIN_ROLE) { - queue = _queue; - } - - /// @notice Sets the clock contract that manages epoch and voting periods - function setClock(address _clock) external auth(ESCROW_ADMIN_ROLE) { - clock = _clock; - } - - /// @notice Sets the NFT contract that is the lock - /// @dev By default this can only be set once due to the high risk of changing the lock - /// and having the ability to steal user funds. - function setLockNFT(address _nft) external auth(ESCROW_ADMIN_ROLE) { - if (_lockNFTSet) revert LockNFTAlreadySet(); - lockNFT = _nft; - _lockNFTSet = true; - } - - function pause() external auth(PAUSER_ROLE) { - _pause(); - } - - function unpause() external auth(PAUSER_ROLE) { - _unpause(); - } - - function setMinDeposit(uint256 _minDeposit) external auth(ESCROW_ADMIN_ROLE) { - minDeposit = _minDeposit; - emit MinDepositSet(_minDeposit); - } - - /*////////////////////////////////////////////////////////////// - Getters: ERC721 Functions - //////////////////////////////////////////////////////////////*/ - - function isApprovedOrOwner(address _spender, uint256 _tokenId) external view returns (bool) { - return IERC721EMB(lockNFT).isApprovedOrOwner(_spender, _tokenId); - } - - /// @notice Fetch all NFTs owned by an address by leveraging the ERC721Enumerable interface - /// @param _owner Address to query - /// @return tokenIds Array of token IDs owned by the address - function ownedTokens(address _owner) public view returns (uint256[] memory tokenIds) { - IERC721EMB enumerable = IERC721EMB(lockNFT); - uint256 balance = enumerable.balanceOf(_owner); - uint256[] memory tokens = new uint256[](balance); - for (uint256 i = 0; i < balance; i++) { - tokens[i] = enumerable.tokenOfOwnerByIndex(_owner, i); - } - return tokens; - } - - /*/////////////////////////////////////////////////////////////// - Getters: Voting - //////////////////////////////////////////////////////////////*/ - - /// @return The voting power of the NFT at the current block - function votingPower(uint256 _tokenId) public view returns (uint256) { - return votingPowerAt(_tokenId, block.timestamp); - } - - /// @return The voting power of the NFT at a specific timestamp - function votingPowerAt(uint256 _tokenId, uint256 _t) public view returns (uint256) { - return IEscrowCurve(curve).votingPowerAt(_tokenId, _t); - } - - /// @return The total voting power at the current block - /// @dev Currently unsupported - function totalVotingPower() external view returns (uint256) { - return totalVotingPowerAt(block.timestamp); - } - - /// @return The total voting power at a specific timestamp - /// @dev Currently unsupported - function totalVotingPowerAt(uint256 _timestamp) public view returns (uint256) { - return IEscrowCurve(curve).supplyAt(_timestamp); - } - - /// @return The details of the underlying lock for a given veNFT - function locked(uint256 _tokenId) external view returns (LockedBalance memory) { - return _locked[_tokenId]; - } - - /// @return accountVotingPower The voting power of an account at the current block - /// @dev We cannot do historic voting power at this time because we don't current track - /// histories of token transfers. - function votingPowerForAccount( - address _account - ) external view returns (uint256 accountVotingPower) { - uint256[] memory tokens = ownedTokens(_account); - - for (uint256 i = 0; i < tokens.length; i++) { - accountVotingPower += votingPowerAt(tokens[i], block.timestamp); - } - } - - /// @notice Checks if the NFT is currently voting. We require the user to reset their votes if so. - function isVoting(uint256 _tokenId) public view returns (bool) { - return ISimpleGaugeVoter(voter).isVoting(_tokenId); - } - - /*////////////////////////////////////////////////////////////// - ESCROW LOGIC - //////////////////////////////////////////////////////////////*/ - - function createLock(uint256 _value) external nonReentrant whenNotPaused returns (uint256) { - return _createLockFor(_value, _msgSender()); - } - - /// @notice Creates a lock on behalf of someone else. Restricted by default. - function createLockFor( - uint256 _value, - address _to - ) external nonReentrant whenNotPaused returns (uint256) { - return _createLockFor(_value, _to); - } - - /// @dev Deposit `_value` tokens for `_to` starting at next deposit interval - /// @param _value Amount to deposit - /// @param _to Address to deposit - function _createLockFor(uint256 _value, address _to) internal returns (uint256) { - if (_value == 0) revert ZeroAmount(); - if (_value < minDeposit) revert AmountTooSmall(); - - // query the duration lib to get the next time we can deposit - uint256 startTime = IClock(clock).epochNextCheckpointTs(); - - // increment the total locked supply and get the new tokenId - totalLocked += _value; - uint256 newTokenId = ++lastLockId; - - // write the lock and checkpoint the voting power - LockedBalance memory lock = LockedBalance(_value.toUint208(), startTime.toUint48()); - _locked[newTokenId] = lock; - - // we don't allow edits in this implementation, so only the new lock is used - _checkpoint(newTokenId, lock); - - uint256 balanceBefore = IERC20(token).balanceOf(address(this)); - - // transfer the tokens into the contract - IERC20(token).safeTransferFrom(_msgSender(), address(this), _value); - - // we currently don't support tokens that adjust balances on transfer - if (IERC20(token).balanceOf(address(this)) != balanceBefore + _value) - revert TransferBalanceIncorrect(); - - // mint the NFT before and emit the event to complete the lock - IERC721EMB(lockNFT).mint(_to, newTokenId); - emit Deposit(_to, newTokenId, startTime, _value, totalLocked); - - return newTokenId; - } - - /// @notice Record per-user data to checkpoints. Used by VotingEscrow system. - /// @param _tokenId NFT token ID - /// @dev Old locked balance is unused in the increasing case, at least in this implementation - /// @param _newLocked New locked amount / start lock time for the user - function _checkpoint(uint256 _tokenId, LockedBalance memory _newLocked) private { - IEscrowCurve(curve).checkpoint(_tokenId, LockedBalance(0, 0), _newLocked); - } - - /// @dev resets the voting power for a given tokenId. Checkpoint is written to the end of the epoch. - /// @param _tokenId The tokenId to reset the voting power for - /// @dev We don't need to fetch the old locked balance as it's not used in this implementation - function _checkpointClear(uint256 _tokenId) private { - uint256 checkpointClearTime = IClock(clock).epochNextCheckpointTs(); - IEscrowCurve(curve).checkpoint( - _tokenId, - LockedBalance(0, 0), - LockedBalance(0, checkpointClearTime.toUint48()) - ); - } - - /*////////////////////////////////////////////////////////////// - Exit and Withdraw Logic - //////////////////////////////////////////////////////////////*/ - - /// @notice Resets the votes and begins the withdrawal process for a given tokenId - /// @dev Convenience function, the user must have authorized this contract to act on their behalf. - function resetVotesAndBeginWithdrawal(uint256 _tokenId) external whenNotPaused { - ISimpleGaugeVoter(voter).reset(_tokenId); - beginWithdrawal(_tokenId); - } - - /// @notice Enters a tokenId into the withdrawal queue by transferring to this contract and creating a ticket. - /// @param _tokenId The tokenId to begin withdrawal for. Will be transferred to this contract before burning. - /// @dev The user must not have active votes in the voter contract. - function beginWithdrawal(uint256 _tokenId) public nonReentrant whenNotPaused { - // can't exit if you have votes pending - if (isVoting(_tokenId)) revert CannotExit(); - - // in the event of an increasing curve, 0 voting power means voting isn't active - if (votingPower(_tokenId) == 0) revert CannotExit(); - - address owner = IERC721EMB(lockNFT).ownerOf(_tokenId); - - // we can remove the user's voting power as it's no longer locked - _checkpointClear(_tokenId); - - // transfer NFT to this and queue the exit - IERC721EMB(lockNFT).transferFrom(_msgSender(), address(this), _tokenId); - IExitQueue(queue).queueExit(_tokenId, owner); - } - - /// @notice Withdraws tokens from the contract - function withdraw(uint256 _tokenId) external nonReentrant whenNotPaused { - address sender = _msgSender(); - - // we force the sender to be the ticket holder - if (!(IExitQueue(queue).ticketHolder(_tokenId) == sender)) revert NotTicketHolder(); - - // check that this ticket can exit - if (!(IExitQueue(queue).canExit(_tokenId))) revert CannotExit(); - - LockedBalance memory oldLocked = _locked[_tokenId]; - uint256 value = oldLocked.amount; - - // check for fees to be transferred - // do this before clearing the lock or it will be incorrect - uint256 fee = IExitQueue(queue).exit(_tokenId); - if (fee > 0) { - IERC20(token).safeTransfer(address(queue), fee); - } - - // clear out the token data - _locked[_tokenId] = LockedBalance(0, 0); - totalLocked -= value; - - // Burn the NFT and transfer the tokens to the user - IERC721EMB(lockNFT).burn(_tokenId); - IERC20(token).safeTransfer(sender, value - fee); - - emit Withdraw(sender, _tokenId, value - fee, block.timestamp, totalLocked); - } - - /// @notice withdraw excess tokens from the contract - possibly by accident - function sweep() external nonReentrant auth(SWEEPER_ROLE) { - // if there are extra tokens in the contract - // balance will be greater than the total locked - uint balance = IERC20(token).balanceOf(address(this)); - uint excess = balance - totalLocked; - - // if there isn't revert the tx - if (excess == 0) revert NothingToSweep(); - - // if there is, send them to the caller - IERC20(token).safeTransfer(_msgSender(), excess); - emit Sweep(_msgSender(), excess); - } - - /// @notice the sweeper can send NFTs mistakenly sent to the contract to a designated address - /// @param _tokenId the tokenId to sweep - must be currently in this contract - /// @param _to the address to send the NFT to - must be a whitelisted address for transfers - /// @dev Cannot sweep NFTs that are in the exit queue for obvious reasons - function sweepNFT(uint256 _tokenId, address _to) external nonReentrant auth(SWEEPER_ROLE) { - // if the token id is not in the contract, revert - if (IERC721EMB(lockNFT).ownerOf(_tokenId) != address(this)) revert NothingToSweep(); - - // if the token id is in the queue, we cannot sweep it - if (IExitQueue(queue).ticketHolder(_tokenId) != address(0)) revert CannotExit(); - - IERC721EMB(lockNFT).transferFrom(address(this), _to, _tokenId); - emit SweepNFT(_to, _tokenId); - } - - /*/////////////////////////////////////////////////////////////// - UUPS Upgrade - //////////////////////////////////////////////////////////////*/ - - /// @notice Returns the address of the implementation contract in the [proxy storage slot](https://eips.ethereum.org/EIPS/eip-1967) slot the [UUPS proxy](https://eips.ethereum.org/EIPS/eip-1822) is pointing to. - /// @return The address of the implementation contract. - function implementation() public view returns (address) { - return _getImplementation(); - } - - /// @notice Internal method authorizing the upgrade of the contract via the [upgradeability mechanism for UUPS proxies](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable) (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). - function _authorizeUpgrade(address) internal virtual override auth(ESCROW_ADMIN_ROLE) {} - - /// @dev Reserved storage space to allow for layout changes in the future. - uint256[39] private __gap; -} diff --git a/src/escrow/increasing/interfaces/IERC721EMB.sol b/src/escrow/increasing/interfaces/IERC721EMB.sol deleted file mode 100644 index 8f0ba2e..0000000 --- a/src/escrow/increasing/interfaces/IERC721EMB.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {IERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; - -interface IERC721EnumerableMintableBurnable is IERC721Enumerable { - function mint(address to, uint256 tokenId) external; - - function burn(uint256 tokenId) external; - - function isApprovedOrOwner(address spender, uint256 tokenId) external view returns (bool); -} diff --git a/src/escrow/increasing/interfaces/IEscrowCurveIncreasing.sol b/src/escrow/increasing/interfaces/IEscrowCurveIncreasing.sol deleted file mode 100644 index 98ec0a5..0000000 --- a/src/escrow/increasing/interfaces/IEscrowCurveIncreasing.sol +++ /dev/null @@ -1,137 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {ILockedBalanceIncreasing} from "./IVotingEscrowIncreasing.sol"; - -/*/////////////////////////////////////////////////////////////// - Global Curve -//////////////////////////////////////////////////////////////*/ - -interface IEscrowCurveGlobalStorage { - /// @notice Captures the shape of the aggregate voting curve at a specific point in time - /// @param bias The y intercept of the aggregate voting curve at the given time - /// @param ts The timestamp at which the we last updated the aggregate voting curve - /// @param coefficients The coefficients of the aggregated curve, supports up to quadratic curves. - /// @dev Coefficients are stored in the following order: [constant, linear, quadratic] - /// and not all coefficients are used for all curves. - struct GlobalPoint { - uint128 bias; - uint256 ts; - int256[3] coefficients; - } -} - -interface IEscrowCurveGlobal is IEscrowCurveGlobalStorage { - /// @notice Returns the GlobalPoint at the passed epoch - /// @param _loc The epoch to return the GlobalPoint at - function pointHistory(uint256 _loc) external view returns (GlobalPoint memory); -} - -/*/////////////////////////////////////////////////////////////// - Token Curve -//////////////////////////////////////////////////////////////*/ - -interface IEscrowCurveTokenStorage { - /// @notice Captures the shape of the user's voting curve at a specific point in time - /// @param bias The y intercept of the user's voting curve at the given time - /// @param checkpointTs The checkpoint when the user voting curve is/was/will be updated - /// @param writtenTs The timestamp at which we locked the checkpoint - /// @param coefficients The coefficients of the curve, supports up to quadratic curves. - /// @dev Coefficients are stored in the following order: [constant, linear, quadratic] - /// and not all coefficients are used for all curves. - struct TokenPoint { - uint256 bias; - uint128 checkpointTs; - uint128 writtenTs; - int256[3] coefficients; - } -} - -interface IEscrowCurveToken is IEscrowCurveTokenStorage { - /// @notice returns the token point at time `timestamp` - function tokenPointIntervals(uint256 timestamp) external view returns (uint256); - - /// @notice Returns the TokenPoint at the passed epoch - /// @param _tokenId The NFT to return the TokenPoint for - /// @param _loc The epoch to return the TokenPoint at - function tokenPointHistory( - uint256 _tokenId, - uint256 _loc - ) external view returns (TokenPoint memory); -} - -/*/////////////////////////////////////////////////////////////// - Core Functions -//////////////////////////////////////////////////////////////*/ - -interface IEscrowCurveErrorsAndEvents { - error InvalidTokenId(); - error InvalidCheckpoint(); -} - -interface IEscrowCurveCore is IEscrowCurveErrorsAndEvents { - /// @notice Get the current voting power for `_tokenId` - /// @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility - /// Fetches last token point prior to a certain timestamp, then walks forward to timestamp. - /// @param _tokenId NFT for lock - /// @param _t Epoch time to return voting power at - /// @return Token voting power - function votingPowerAt(uint256 _tokenId, uint256 _t) external view returns (uint256); - - /// @notice Calculate total voting power at some point in the past - /// @param _t Time to calculate the total voting power at - /// @return Total voting power at that time - function supplyAt(uint256 _t) external view returns (uint256); - - /// @notice Writes a snapshot of voting power at the current epoch - /// @param _tokenId Snapshot a specific token - /// @param _oldLocked The token's previous locked balance - /// @param _newLocked The token's new locked balance - function checkpoint( - uint256 _tokenId, - ILockedBalanceIncreasing.LockedBalance memory _oldLocked, - ILockedBalanceIncreasing.LockedBalance memory _newLocked - ) external; -} - -interface IEscrowCurveMath { - /// @notice Preview the curve coefficients for curves up to quadratic. - /// @param amount The amount of tokens to calculate the coefficients for - given a fixed algebraic representation - /// @return coefficients in the form [constant, linear, quadratic] - /// @dev Not all coefficients are used for all curves - function getCoefficients(uint256 amount) external view returns (int256[3] memory coefficients); - - /// @notice Bias is the token's voting weight - function getBias(uint256 timeElapsed, uint256 amount) external view returns (uint256 bias); -} - -/*/////////////////////////////////////////////////////////////// - WARMUP CURVE -//////////////////////////////////////////////////////////////*/ - -interface IWarmupEvents { - event WarmupSet(uint48 warmup); -} - -interface IWarmup is IWarmupEvents { - /// @notice Set the warmup period for the curve - function setWarmupPeriod(uint48 _warmup) external; - - /// @notice the warmup period for the curve - function warmupPeriod() external view returns (uint48); - - /// @notice check if the curve is past the warming period - function isWarm(uint256 _tokenId) external view returns (bool); -} - -/*/////////////////////////////////////////////////////////////// - INCREASING CURVE -//////////////////////////////////////////////////////////////*/ - -/// @dev first version only accounts for token-level point histories -interface IEscrowCurveIncreasing is - IEscrowCurveCore, - IEscrowCurveMath, - IEscrowCurveToken, - IWarmup -{} diff --git a/src/escrow/increasing/interfaces/IExitQueue.sol b/src/escrow/increasing/interfaces/IExitQueue.sol deleted file mode 100644 index fcd6bf1..0000000 --- a/src/escrow/increasing/interfaces/IExitQueue.sol +++ /dev/null @@ -1,115 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IExitQueueCoreErrorsAndEvents { - error OnlyEscrow(); - error AlreadyQueued(); - error ZeroAddress(); - error CannotExit(); - error NoLockBalance(); - event ExitQueued(uint256 indexed tokenId, address indexed holder, uint256 exitDate); - event Exit(uint256 indexed tokenId, uint256 fee); -} - -interface ITicket { - struct Ticket { - address holder; - uint256 exitDate; - } -} - -/*/////////////////////////////////////////////////////////////// - Fee Collection -//////////////////////////////////////////////////////////////*/ - -interface IExitQueueFeeErrorsAndEvents { - error FeeTooHigh(uint256 maxFee); - - event Withdraw(address indexed to, uint256 amount); - event FeePercentSet(uint256 feePercent); -} - -interface IExitQueueFee is IExitQueueFeeErrorsAndEvents { - /// @notice optional fee charged for exiting the queue - function feePercent() external view returns (uint256); - - /// @notice The exit queue manager can set the fee - function setFeePercent(uint256 _fee) external; - - /// @notice withdraw accumulated fees - function withdraw(uint256 _amount) external; -} - -/*/////////////////////////////////////////////////////////////// - Cooldown -//////////////////////////////////////////////////////////////*/ - -interface IExitQueueCooldownErrorsAndEvents { - error CooldownTooHigh(); - - event CooldownSet(uint48 cooldown); -} - -interface IExitQueueCooldown is IExitQueueCooldownErrorsAndEvents { - /// @notice time in seconds between exit and withdrawal - function cooldown() external view returns (uint48); - - /// @notice The exit queue manager can set the cooldown period - /// @param _cooldown time in seconds between exit and withdrawal - function setCooldown(uint48 _cooldown) external; -} - -/*/////////////////////////////////////////////////////////////// - Min Lock -//////////////////////////////////////////////////////////////*/ - -interface IExitMinLockCooldownErrorsAndEvents { - event MinLockSet(uint48 minLock); - error MinLockOutOfBounds(); - error MinLockNotReached(uint256 tokenId, uint48 minLock, uint48 earliestExitDate); -} - -interface IExitQueueMinLock is IExitMinLockCooldownErrorsAndEvents { - /// @notice minimum time from the original lock date before one can enter the queue - function minLock() external view returns (uint48); - - /// @notice The exit queue manager can set the minimum lock time - function setMinLock(uint48 _cooldown) external; -} - -/*/////////////////////////////////////////////////////////////// - Exit Queue -//////////////////////////////////////////////////////////////*/ - -interface IExitQueueErrorsAndEvents is - IExitQueueCoreErrorsAndEvents, - IExitQueueFeeErrorsAndEvents, - IExitQueueCooldownErrorsAndEvents, - IExitMinLockCooldownErrorsAndEvents -{} - -interface IExitQueue is - IExitQueueErrorsAndEvents, - ITicket, - IExitQueueFee, - IExitQueueCooldown, - IExitQueueMinLock -{ - /// @notice tokenId => Ticket - function queue(uint256 _tokenId) external view returns (Ticket memory); - - /// @notice queue an exit for a given tokenId, granting the ticket to the passed holder - /// @param _tokenId the tokenId to queue an exit for - /// @param _ticketHolder the address that will be granted the ticket - function queueExit(uint256 _tokenId, address _ticketHolder) external; - - /// @notice exit the queue for a given tokenId. Requires the cooldown period to have passed - /// @return exitAmount the amount of tokens that can be withdrawn - function exit(uint256 _tokenId) external returns (uint256 exitAmount); - - /// @return true if the tokenId corresponds to a valid ticket and the cooldown period has passed - function canExit(uint256 _tokenId) external view returns (bool); - - /// @return the ticket holder for a given tokenId - function ticketHolder(uint256 _tokenId) external view returns (address); -} diff --git a/src/escrow/increasing/interfaces/ILock.sol b/src/escrow/increasing/interfaces/ILock.sol deleted file mode 100644 index 82a22a8..0000000 --- a/src/escrow/increasing/interfaces/ILock.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/*/////////////////////////////////////////////////////////////// - WHITELIST -//////////////////////////////////////////////////////////////*/ -interface IWhitelistEvents { - event WhitelistSet(address indexed account, bool status); -} - -interface IWhitelistErrors { - error NotWhitelisted(); - error ForbiddenWhitelistAddress(); -} - -interface IWhitelist is IWhitelistEvents, IWhitelistErrors { - /// @notice Set whitelist status for an address - function setWhitelisted(address addr, bool isWhitelisted) external; - - /// @notice Check if an address is whitelisted - function whitelisted(address addr) external view returns (bool); -} - -interface ILock is IWhitelist { - error OnlyEscrow(); - - /// @notice Address of the escrow contract that holds underyling assets - function escrow() external view returns (address); -} diff --git a/src/escrow/increasing/interfaces/IVotes.sol b/src/escrow/increasing/interfaces/IVotes.sol deleted file mode 100644 index 0585269..0000000 --- a/src/escrow/increasing/interfaces/IVotes.sol +++ /dev/null @@ -1,84 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// Modified IVotes interface for tokenId based voting -/// OZ 721 Votes assumes that 1 NFT = 1 Vote and this very much does not hold in our case -interface IVotes { - /** - * @dev Emitted when an account changes their delegate. - */ - event DelegateChanged( - address indexed delegator, - uint256 indexed fromDelegate, - uint256 indexed toDelegate - ); - - /** - * @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of votes. - */ - event DelegateVotesChanged( - address indexed delegate, - uint256 previousBalance, - uint256 newBalance - ); - - /** - * @dev Returns the current amount of votes that `tokenId` has. - * If the account passed in is not the owner, returns 0. - */ - function getVotes(address account, uint256 tokenId) external view returns (uint256); - - /** - * @dev Returns the current amount of votes that `tokenId` has, irrespective of the owner. - */ - function getVotes(uint256 tokenId) external view returns (uint256); - - /** - * @dev Returns the amount of votes that `tokenId` had at a specific moment in the past. - * If the account passed in is not the owner, returns 0. - */ - function getPastVotes( - address account, - uint256 tokenId, - uint256 timepoint - ) external view returns (uint256); - - /** - * @dev Returns the amount of votes that `tokenId` had at a specific moment in the past, irrespective of the owner. - */ - function getPastVotes(uint256 tokenId, uint256 timepoint) external view returns (uint256); - - /** - * @dev Returns the total supply of votes available at a specific moment in the past. If the `clock()` is - * configured to use block numbers, this will return the value the end of the corresponding block. - * - * NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. - * Votes that have not been delegated are still part of total supply, even though they would not participate in a - * vote. - */ - function getPastTotalSupply(uint256 timepoint) external view returns (uint256); - - /** - * @dev Returns the delegate that `tokenId` has chosen. Can never be equal to the delegator's `tokenId`. - * Returns 0 if not delegated. - */ - function delegates(uint256 tokenId) external view returns (uint256); - - /** - * @dev Delegates votes from the sender to `delegatee`. - */ - function delegate(uint256 delegator, uint256 delegatee) external; - - /** - * @dev Delegates votes from `delegator` to `delegatee`. Signer must own `delegator`. - */ - function delegateBySig( - uint256 delegator, - uint256 delegatee, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) external; -} diff --git a/src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol b/src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol deleted file mode 100644 index 9523c33..0000000 --- a/src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol +++ /dev/null @@ -1,192 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/*/////////////////////////////////////////////////////////////// - CORE FUNCTIONALITY -//////////////////////////////////////////////////////////////*/ - -interface ILockedBalanceIncreasing { - struct LockedBalance { - uint208 amount; - uint48 start; // mirrors oz ERC20 timestamp clocks - } -} - -interface IVotingEscrowCoreErrors { - error NoLockFound(); - error NotOwner(); - error NonExistentToken(); - error NotApprovedOrOwner(); - error ZeroAddress(); - error ZeroAmount(); - error ZeroBalance(); - error SameAddress(); - error LockNFTAlreadySet(); - error MustBe18Decimals(); - error TransferBalanceIncorrect(); - error AmountTooSmall(); -} - -interface IVotingEscrowCoreEvents { - event MinDepositSet(uint256 minDeposit); - - event Deposit( - address indexed depositor, - uint256 indexed tokenId, - uint256 indexed startTs, - uint256 value, - uint256 newTotalLocked - ); - event Withdraw( - address indexed depositor, - uint256 indexed tokenId, - uint256 value, - uint256 ts, - uint256 newTotalLocked - ); -} - -interface IVotingEscrowCore is - ILockedBalanceIncreasing, - IVotingEscrowCoreErrors, - IVotingEscrowCoreEvents -{ - /// @notice Address of the underying ERC20 token. - function token() external view returns (address); - - /// @notice Address of the lock receipt NFT. - function lockNFT() external view returns (address); - - /// @notice Total underlying tokens deposited in the contract - function totalLocked() external view returns (uint256); - - /// @notice Get the raw locked balance for `_tokenId` - function locked(uint256 _tokenId) external view returns (LockedBalance memory); - - /// @notice Deposit `_value` tokens for `msg.sender` - /// @param _value Amount to deposit - /// @return TokenId of created veNFT - function createLock(uint256 _value) external returns (uint256); - - /// @notice Deposit `_value` tokens for `_to` - /// @param _value Amount to deposit - /// @param _to Address to deposit - /// @return TokenId of created veNFT - function createLockFor(uint256 _value, address _to) external returns (uint256); - - /// @notice Withdraw all tokens for `_tokenId` - function withdraw(uint256 _tokenId) external; - - /// @notice helper utility for NFT checks - function isApprovedOrOwner(address spender, uint256 tokenId) external view returns (bool); -} - -/*/////////////////////////////////////////////////////////////// - WITHDRAWAL QUEUE -//////////////////////////////////////////////////////////////*/ - -interface IWithdrawalQueueErrors { - error NotTicketHolder(); - error CannotExit(); -} - -interface IWithdrawalQueueEvents {} - -interface IWithdrawalQueue is IWithdrawalQueueErrors, IWithdrawalQueueEvents { - /// @notice Enters a tokenId into the withdrawal queue by transferring to this contract and creating a ticket. - /// @param _tokenId The tokenId to begin withdrawal for. Will be transferred to this contract before burning. - /// @dev The user must not have active votes in the voter contract. - function beginWithdrawal(uint256 _tokenId) external; - - /// @notice Address of the contract that manages exit queue logic for withdrawals - function queue() external view returns (address); -} - -/*/////////////////////////////////////////////////////////////// - SWEEPER -//////////////////////////////////////////////////////////////*/ - -interface ISweeperEvents { - event Sweep(address indexed to, uint256 amount); - event SweepNFT(address indexed to, uint256 tokenId); -} - -interface ISweeperErrors { - error NothingToSweep(); -} - -interface ISweeper is ISweeperEvents, ISweeperErrors { - /// @notice sweeps excess tokens from the contract to a designated address - function sweep() external; - - function sweepNFT(uint256 _tokenId, address _to) external; -} - -/*/////////////////////////////////////////////////////////////// - DYNAMIC VOTER -//////////////////////////////////////////////////////////////*/ - -interface IDynamicVoterErrors { - error NotVoter(); - error OwnershipChange(); - error AlreadyVoted(); -} - -interface IDynamicVoter is IDynamicVoterErrors { - /// @notice Address of the voting contract. - /// @dev We need to ensure votes are not left in this contract before allowing positing changes - function voter() external view returns (address); - - /// @notice Address of the voting Escrow Curve contract that will calculate the voting power - function curve() external view returns (address); - - /// @notice Get the voting power for _tokenId at the current timestamp - /// @dev Returns 0 if called in the same block as a transfer. - /// @param _tokenId . - /// @return Voting power - function votingPower(uint256 _tokenId) external view returns (uint256); - - /// @notice Get the voting power for _tokenId at a given timestamp - /// @param _tokenId . - /// @param _t Timestamp to query voting power - /// @return Voting power - function votingPowerAt(uint256 _tokenId, uint256 _t) external view returns (uint256); - - /// @notice Get the voting power for _account at the current timestamp - /// Aggregtes all voting power for all tokens owned by the account - /// @dev This cannot be used historically without token snapshots - function votingPowerForAccount(address _account) external view returns (uint256); - - /// @notice Calculate total voting power at current timestamp - /// @return Total voting power at current timestamp - function totalVotingPower() external view returns (uint256); - - /// @notice Calculate total voting power at a given timestamp - /// @param _t Timestamp to query total voting power - /// @return Total voting power at given timestamp - function totalVotingPowerAt(uint256 _t) external view returns (uint256); - - /// @notice See if a queried _tokenId has actively voted - /// @return True if voted, else false - function isVoting(uint256 _tokenId) external view returns (bool); - - /// @notice Set the global state voter - function setVoter(address _voter) external; -} - -/*/////////////////////////////////////////////////////////////// - INCREASED ESCROW -//////////////////////////////////////////////////////////////*/ - -interface IVotingEscrowIncreasing is IVotingEscrowCore, IDynamicVoter, IWithdrawalQueue, ISweeper {} - -/// @dev useful for testing -interface IVotingEscrowEventsStorageErrorsEvents is - IVotingEscrowCoreErrors, - IVotingEscrowCoreEvents, - IWithdrawalQueueErrors, - IWithdrawalQueueEvents, - ILockedBalanceIncreasing, - ISweeperEvents, - ISweeperErrors -{} diff --git a/src/factory/GaugesDaoFactory.sol b/src/factory/MultisigDaoFactory.sol similarity index 55% rename from src/factory/GaugesDaoFactory.sol rename to src/factory/MultisigDaoFactory.sol index 5f9ea0a..5a4644e 100644 --- a/src/factory/GaugesDaoFactory.sol +++ b/src/factory/MultisigDaoFactory.sol @@ -3,13 +3,8 @@ pragma solidity ^0.8.17; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {DAOFactory} from "@aragon/osx/framework/dao/DAOFactory.sol"; -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; -import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; -import {VotingEscrow, Clock, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; import {hashHelpers, PluginSetupRef} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol"; -import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; import {Multisig} from "@aragon/osx/plugins/governance/multisig/Multisig.sol"; @@ -20,72 +15,32 @@ import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; /// @notice The struct containing all the parameters to deploy the DAO /// @param minApprovals The amount of approvals required for the multisig to be able to execute a proposal on the DAO /// @param multisigMembers The list of addresses to be defined as the initial multisig signers -/// @param tokenParameters A list with the tokens and metadata for which a plugin and a VE should be deployed -/// @param feePercent The fee taken on withdrawals (1 ether = 100%) -/// @param warmupPeriod Delay in seconds after depositing before voting becomes possible -/// @param cooldownPeriod Delay seconds after queuing an exit before withdrawing becomes possible -/// @param minLockDuration Min seconds a user must have locked in escrow before they can queue an exit -/// @param votingPaused Prevent voting until manually activated by the multisig /// @param multisigPluginRepo Address of Aragon's multisig plugin repository on the given network /// @param multisigPluginRelease The release of the multisig plugin to target /// @param multisigPluginBuild The build of the multisig plugin to target -/// @param voterPluginSetup The address of the Gauges Voter plugin setup contract to create a repository with -/// @param voterEnsSubdomain The ENS subdomain under which the plugin reposiroty will be created /// @param osxDaoFactory The address of the OSx DAO factory contract, used to retrieve the DAO implementation address /// @param pluginSetupProcessor The address of the OSx PluginSetupProcessor contract on the target chain -/// @param pluginRepoFactory The address of the OSx PluginRepoFactory contract on the target chain struct DeploymentParameters { // Multisig settings uint16 minApprovals; address[] multisigMembers; - // Gauge Voter - TokenParameters[] tokenParameters; - uint16 feePercent; - uint48 warmupPeriod; - uint48 cooldownPeriod; - uint48 minLockDuration; - bool votingPaused; - uint256 minDeposit; // Voter plugin setup and ENS PluginRepo multisigPluginRepo; uint8 multisigPluginRelease; uint16 multisigPluginBuild; - SimpleGaugeVoterSetup voterPluginSetup; - string voterEnsSubdomain; // OSx addresses address osxDaoFactory; PluginSetupProcessor pluginSetupProcessor; - PluginRepoFactory pluginRepoFactory; -} - -struct TokenParameters { - address token; - string veTokenName; - string veTokenSymbol; -} - -/// @notice Struct containing the plugin and all of its helpers -struct GaugePluginSet { - SimpleGaugeVoter plugin; - QuadraticIncreasingEscrow curve; - ExitQueue exitQueue; - VotingEscrow votingEscrow; - Clock clock; - Lock nftLock; } /// @notice Contains the artifacts that resulted from running a deployment struct Deployment { DAO dao; - // Plugins Multisig multisigPlugin; - GaugePluginSet[] gaugeVoterPluginSets; - // Plugin repo's - PluginRepo gaugeVoterPluginRepo; } /// @notice A singleton contract designed to run the deployment once and become a read-only store of the contracts deployed -contract GaugesDaoFactory { +contract MultisigDaoFactory { /// @notice Thrown when attempting to call deployOnce() when the DAO is already deployed. error AlreadyDeployed(); @@ -98,28 +53,12 @@ contract GaugesDaoFactory { parameters.minApprovals = _parameters.minApprovals; parameters.multisigMembers = _parameters.multisigMembers; - for (uint i = 0; i < _parameters.tokenParameters.length; ) { - parameters.tokenParameters.push(_parameters.tokenParameters[i]); - - unchecked { - i++; - } - } - - parameters.minDeposit = _parameters.minDeposit; - parameters.feePercent = _parameters.feePercent; - parameters.warmupPeriod = _parameters.warmupPeriod; - parameters.cooldownPeriod = _parameters.cooldownPeriod; - parameters.minLockDuration = _parameters.minLockDuration; - parameters.votingPaused = _parameters.votingPaused; parameters.multisigPluginRepo = _parameters.multisigPluginRepo; parameters.multisigPluginRelease = _parameters.multisigPluginRelease; parameters.multisigPluginBuild = _parameters.multisigPluginBuild; - parameters.voterPluginSetup = _parameters.voterPluginSetup; - parameters.voterEnsSubdomain = _parameters.voterEnsSubdomain; + parameters.osxDaoFactory = _parameters.osxDaoFactory; parameters.pluginSetupProcessor = _parameters.pluginSetupProcessor; - parameters.pluginRepoFactory = _parameters.pluginRepoFactory; } /// @notice Run the deployment and store the artifacts in a read-only store that can be retrieved via `getDeployment()` and `getDeploymentParameters()` @@ -154,45 +93,6 @@ contract GaugesDaoFactory { ); } - // GAUGE VOTER(s) - { - IPluginSetup.PreparedSetupData memory preparedVoterSetupData; - - PluginRepo.Tag memory repoTag = PluginRepo.Tag(1, 1); - GaugePluginSet memory pluginSet; - - PluginRepo pluginRepo = prepareSimpleGaugeVoterPluginRepo(dao); - - for (uint i = 0; i < parameters.tokenParameters.length; ) { - ( - pluginSet, - deployment.gaugeVoterPluginRepo, - preparedVoterSetupData - ) = prepareSimpleGaugeVoterPlugin( - dao, - parameters.tokenParameters[i], - pluginRepo, - repoTag - ); - - deployment.gaugeVoterPluginSets.push(pluginSet); - - applyPluginInstallation( - dao, - address(pluginSet.plugin), - deployment.gaugeVoterPluginRepo, - repoTag, - preparedVoterSetupData - ); - - activateSimpleGaugeVoterInstallation(dao, pluginSet); - - unchecked { - i++; - } - } - } - // Clean up revokeApplyInstallationPermissions(dao); @@ -267,62 +167,6 @@ contract GaugesDaoFactory { return (Multisig(plugin), preparedSetupData); } - function prepareSimpleGaugeVoterPluginRepo(DAO dao) internal returns (PluginRepo pluginRepo) { - // Publish repo - pluginRepo = PluginRepoFactory(parameters.pluginRepoFactory) - .createPluginRepoWithFirstVersion( - parameters.voterEnsSubdomain, - address(parameters.voterPluginSetup), - address(dao), - " ", - " " - ); - } - - function prepareSimpleGaugeVoterPlugin( - DAO dao, - TokenParameters memory tokenParameters, - PluginRepo pluginRepo, - PluginRepo.Tag memory repoTag - ) internal returns (GaugePluginSet memory, PluginRepo, IPluginSetup.PreparedSetupData memory) { - // Plugin settings - bytes memory settingsData = parameters.voterPluginSetup.encodeSetupData( - ISimpleGaugeVoterSetupParams({ - isPaused: parameters.votingPaused, - token: tokenParameters.token, - veTokenName: tokenParameters.veTokenName, - veTokenSymbol: tokenParameters.veTokenSymbol, - feePercent: parameters.feePercent, - warmup: parameters.warmupPeriod, - cooldown: parameters.cooldownPeriod, - minLock: parameters.minLockDuration, - minDeposit: parameters.minDeposit - }) - ); - - (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = parameters - .pluginSetupProcessor - .prepareInstallation( - address(dao), - PluginSetupProcessor.PrepareInstallationParams( - PluginSetupRef(repoTag, pluginRepo), - settingsData - ) - ); - - address[] memory helpers = preparedSetupData.helpers; - GaugePluginSet memory pluginSet = GaugePluginSet({ - plugin: SimpleGaugeVoter(plugin), - curve: QuadraticIncreasingEscrow(helpers[0]), - exitQueue: ExitQueue(helpers[1]), - votingEscrow: VotingEscrow(helpers[2]), - clock: Clock(helpers[3]), - nftLock: Lock(helpers[4]) - }); - - return (pluginSet, pluginRepo, preparedSetupData); - } - function applyPluginInstallation( DAO dao, address plugin, @@ -341,28 +185,6 @@ contract GaugesDaoFactory { ); } - function activateSimpleGaugeVoterInstallation( - DAO dao, - GaugePluginSet memory pluginSet - ) internal { - dao.grant( - address(pluginSet.votingEscrow), - address(this), - pluginSet.votingEscrow.ESCROW_ADMIN_ROLE() - ); - - pluginSet.votingEscrow.setCurve(address(pluginSet.curve)); - pluginSet.votingEscrow.setQueue(address(pluginSet.exitQueue)); - pluginSet.votingEscrow.setVoter(address(pluginSet.plugin)); - pluginSet.votingEscrow.setLockNFT(address(pluginSet.nftLock)); - - dao.revoke( - address(pluginSet.votingEscrow), - address(this), - pluginSet.votingEscrow.ESCROW_ADMIN_ROLE() - ); - } - function grantApplyInstallationPermissions(DAO dao) internal { // The PSP can manage permissions on the new DAO dao.grant(address(dao), address(parameters.pluginSetupProcessor), dao.ROOT_PERMISSION_ID()); diff --git a/src/libs/CurveConstantLib.sol b/src/libs/CurveConstantLib.sol deleted file mode 100644 index 06b63eb..0000000 --- a/src/libs/CurveConstantLib.sol +++ /dev/null @@ -1,22 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @title CurveConstantLib -/// @notice Precomputed coefficients for escrow curve -/// Below are the shared coefficients for the linear and quadratic terms -/// @dev This curve goes from 1x -> 2x voting power over a 2 year time horizon -/// Epochs are still 2 weeks long -library CurveConstantLib { - int256 internal constant SHARED_CONSTANT_COEFFICIENT = 1e18; - - /// @dev straight line so the curve is increasing only in the linear term - /// 1 / (52 * SECONDS_IN_2_WEEKS) - int256 internal constant SHARED_LINEAR_COEFFICIENT = 15898453398; - - /// @dev this curve is linear - int256 internal constant SHARED_QUADRATIC_COEFFICIENT = 0; - - /// @dev the maxiumum number of epochs the cure can keep increasing - /// 26 epochs in a year, 2 years = 52 epochs - uint256 internal constant MAX_EPOCHS = 52; -} diff --git a/src/libs/ProxyLib.sol b/src/libs/ProxyLib.sol deleted file mode 100644 index a3cc18d..0000000 --- a/src/libs/ProxyLib.sol +++ /dev/null @@ -1,42 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; - -/// @title ProxyLib -/// @author Aragon X - 2024 -/// @notice A library containing methods for the deployment of proxies via the UUPS pattern (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)) and minimal proxy pattern (see [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167)). -/// @custom:security-contact sirt@aragon.org -library ProxyLib { - using Address for address; - using Clones for address; - - /// @notice Creates an [ERC-1967](https://eips.ethereum.org/EIPS/eip-1967) UUPS proxy contract pointing to a logic contract and allows to immediately initialize it. - /// @param _logic The logic contract the proxy is pointing to. - /// @param _initCalldata The initialization data for this contract. - /// @return uupsProxy The address of the UUPS proxy contract created. - /// @dev If `_initCalldata` is non-empty, it is used in a delegate call to the `_logic` contract. This will typically be an encoded function call initializing the storage of the proxy (see [OpenZeppelin ERC1967Proxy-constructor](https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Proxy-constructor-address-bytes-)). - function deployUUPSProxy( - address _logic, - bytes memory _initCalldata - ) internal returns (address uupsProxy) { - uupsProxy = address(new ERC1967Proxy({_logic: _logic, _data: _initCalldata})); - } - - /// @notice Creates an [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167) minimal proxy contract, also known as clones, pointing to a logic contract and allows to immediately initialize it. - /// @param _logic The logic contract the proxy is pointing to. - /// @param _initCalldata The initialization data for this contract. - /// @return minimalProxy The address of the minimal proxy contract created. - /// @dev If `_initCalldata` is non-empty, it is used in a call to the clone contract. This will typically be an encoded function call initializing the storage of the contract. - function deployMinimalProxy( - address _logic, - bytes memory _initCalldata - ) internal returns (address minimalProxy) { - minimalProxy = _logic.clone(); - if (_initCalldata.length > 0) { - minimalProxy.functionCall({data: _initCalldata}); - } - } -} diff --git a/src/libs/SignedFixedPointMathLib.sol b/src/libs/SignedFixedPointMathLib.sol deleted file mode 100644 index ff03eee..0000000 --- a/src/libs/SignedFixedPointMathLib.sol +++ /dev/null @@ -1,51 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@solmate/utils/SignedWadMath.sol"; - -error NegativeBase(); - -// shared interface for fixed point math implementations. -library SignedFixedPointMath { - // solmate does this unchecked to save gas, easier to do this here - // be extremely careful that you are doing all operations in FP - // unlike PRB Math (unsupported in solidity 0.8.17) - // solmate will not warn you that you are operating in scaled down mode - function toFP(int256 x) internal pure returns (int256) { - return x * 1e18; - } - - function fromFP(int256 x) internal pure returns (int256) { - return x / 1e18; - } - - function mul(int256 x, int256 y) internal pure returns (int256) { - return wadMul(x, y); - } - - function div(int256 x, int256 y) internal pure returns (int256) { - return wadDiv(x, y); - } - - function add(int256 x, int256 y) internal pure returns (int256) { - return x + y; - } - - function sub(int256 x, int256 y) internal pure returns (int256) { - return x - y; - } - - function pow(int256 x, int256 y) internal pure returns (int256) { - if (x < 0) revert NegativeBase(); - if (x == 0) return 0; - return wadPow(x, y); - } - - function lt(int256 x, int256 y) internal pure returns (bool) { - return x < y; - } - - function gt(int256 x, int256 y) internal pure returns (bool) { - return x > y; - } -} diff --git a/src/voting/ISimpleGaugeVoter.sol b/src/voting/ISimpleGaugeVoter.sol deleted file mode 100644 index 19ef8a8..0000000 --- a/src/voting/ISimpleGaugeVoter.sol +++ /dev/null @@ -1,137 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IGauge { - /// @param metadataURI URI for the metadata of the gauge - struct Gauge { - bool active; - uint256 created; // timestamp or epoch - string metadataURI; - // more space for data as this is a struct in a mapping - } -} - -interface IGaugeVote { - /// @param votes gauge => votes cast at that time - /// @param gaugesVotedFor array of gauges we have active votes for - /// @param usedVotingPower total voting power used at the time of the vote - /// @dev this changes so we need an historic snapshot - /// @param lastVoted is the last time the user voted - struct TokenVoteData { - mapping(address => uint256) votes; - address[] gaugesVotedFor; - uint256 usedVotingPower; - uint256 lastVoted; - } - - /// @param weight proportion of voting power the token will allocate to the gauge. Will be normalised. - /// @param gauge address of the gauge to vote for - struct GaugeVote { - uint256 weight; - address gauge; - } -} - -/*/////////////////////////////////////////////////////////////// - Gauge Manager -//////////////////////////////////////////////////////////////*/ - -interface IGaugeManagerEvents { - event GaugeCreated(address indexed gauge, address indexed creator, string metadataURI); - event GaugeDeactivated(address indexed gauge); - event GaugeActivated(address indexed gauge); - event GaugeMetadataUpdated(address indexed gauge, string metadataURI); -} - -interface IGaugeManagerErrors { - error ZeroGauge(); - error GaugeActivationUnchanged(); - error GaugeExists(); -} - -interface IGaugeManager is IGaugeManagerEvents, IGaugeManagerErrors { - function isActive(address gauge) external view returns (bool); - - function createGauge(address _gauge, string calldata _metadata) external returns (address); - - function deactivateGauge(address _gauge) external; - - function activateGauge(address _gauge) external; - - function updateGaugeMetadata(address _gauge, string calldata _metadata) external; -} - -/*/////////////////////////////////////////////////////////////// - Gauge Voter -//////////////////////////////////////////////////////////////*/ - -interface IGaugeVoterEvents { - /// @param votingPowerCastForGauge votes cast by this token for this gauge in this vote - /// @param totalVotingPowerInGauge total voting power in the gauge at the time of the vote, after applying the vote - /// @param totalVotingPowerInContract total voting power in the contract at the time of the vote, after applying the vote - event Voted( - address indexed voter, - address indexed gauge, - uint256 indexed epoch, - uint256 tokenId, - uint256 votingPowerCastForGauge, - uint256 totalVotingPowerInGauge, - uint256 totalVotingPowerInContract, - uint256 timestamp - ); - - /// @param votingPowerRemovedFromGauge votes removed by this token for this gauge, at the time of this rest - /// @param totalVotingPowerInGauge total voting power in the gauge at the time of the reset, after applying the reset - /// @param totalVotingPowerInContract total voting power in the contract at the time of the reset, after applying the reset - event Reset( - address indexed voter, - address indexed gauge, - uint256 indexed epoch, - uint256 tokenId, - uint256 votingPowerRemovedFromGauge, - uint256 totalVotingPowerInGauge, - uint256 totalVotingPowerInContract, - uint256 timestamp - ); -} - -interface IGaugeVoterErrors { - error AlreadyVoted(uint256 tokenId); - error VotingInactive(); - error NotApprovedOrOwner(); - error GaugeDoesNotExist(address _pool); - error GaugeInactive(address _gauge); - error DoubleVote(); - error NoVotes(); - error NoVotingPower(); - error NotCurrentlyVoting(); -} - -interface IGaugeVoter is IGaugeVoterEvents, IGaugeVoterErrors, IGaugeVote { - /// @notice Called by users to vote for pools. Votes distributed proportionally based on weights. - /// @param _tokenId Id of veNFT you are voting with. - /// @param _votes Array of votes to be cast, contains gauge address and weight. - function vote(uint256 _tokenId, GaugeVote[] memory _votes) external; - - /// @notice Called by users to reset voting state. Required when withdrawing or transferring veNFT. - /// @param _tokenId Id of veNFT you are reseting. - function reset(uint256 _tokenId) external; - - /// @notice Can be called to check if a token is currently voting - function isVoting(uint256 _tokenId) external view returns (bool); -} - -/*/////////////////////////////////////////////////////////////// - Simple Gauge Voter -//////////////////////////////////////////////////////////////*/ - -interface ISimpleGaugeVoter is IGaugeVoter, IGaugeManager, IGauge { - -} - -interface ISimpleGaugeVoterStorageEventsErrors is - IGaugeManagerEvents, - IGaugeManagerErrors, - IGaugeVoterEvents, - IGaugeVoterErrors -{} diff --git a/src/voting/SimpleGaugeVoter.sol b/src/voting/SimpleGaugeVoter.sol deleted file mode 100644 index a2aa905..0000000 --- a/src/voting/SimpleGaugeVoter.sol +++ /dev/null @@ -1,339 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {IVotingEscrowIncreasing as IVotingEscrow} from "@escrow-interfaces/IVotingEscrowIncreasing.sol"; -import {IClockUser, IClock} from "@clock/IClock.sol"; -import {ISimpleGaugeVoter} from "./ISimpleGaugeVoter.sol"; - -import {ReentrancyGuardUpgradeable as ReentrancyGuard} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import {PausableUpgradeable as Pausable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; -import {PluginUUPSUpgradeable} from "@aragon/osx/core/plugin/PluginUUPSUpgradeable.sol"; - -contract SimpleGaugeVoter is - ISimpleGaugeVoter, - IClockUser, - ReentrancyGuard, - Pausable, - PluginUUPSUpgradeable -{ - /// @notice The Gauge admin can can create and manage voting gauges for token holders - bytes32 public constant GAUGE_ADMIN_ROLE = keccak256("GAUGE_ADMIN"); - - /// @notice Address of the voting escrow contract that will track voting power - address public escrow; - - /// @notice Clock contract for epoch duration - address public clock; - - /// @notice The total votes that have accumulated in this contract - uint256 public totalVotingPowerCast; - - /// @notice enumerable list of all gauges that can be voted on - address[] public gaugeList; - - /// @notice address => gauge data - mapping(address => Gauge) public gauges; - - /// @notice gauge => total votes (global) - mapping(address => uint256) public gaugeVotes; - - /// @dev tokenId => tokenVoteData - mapping(uint256 => TokenVoteData) internal tokenVoteData; - - /*/////////////////////////////////////////////////////////////// - Initialization - //////////////////////////////////////////////////////////////*/ - - constructor() { - _disableInitializers(); - } - - function initialize( - address _dao, - address _escrow, - bool _startPaused, - address _clock - ) external initializer { - __PluginUUPSUpgradeable_init(IDAO(_dao)); - __ReentrancyGuard_init(); - __Pausable_init(); - escrow = _escrow; - clock = _clock; - if (_startPaused) _pause(); - } - - /*/////////////////////////////////////////////////////////////// - Modifiers - //////////////////////////////////////////////////////////////*/ - - function pause() external auth(GAUGE_ADMIN_ROLE) { - _pause(); - } - - function unpause() external auth(GAUGE_ADMIN_ROLE) { - _unpause(); - } - - modifier whenVotingActive() { - if (!votingActive()) revert VotingInactive(); - _; - } - - /*/////////////////////////////////////////////////////////////// - Voting - //////////////////////////////////////////////////////////////*/ - - /// @notice extrememly simple for loop. We don't need reentrancy checks in this implementation - /// because the plugin doesn't do anything other than signal. - function voteMultiple( - uint256[] calldata _tokenIds, - GaugeVote[] calldata _votes - ) external nonReentrant whenNotPaused whenVotingActive { - for (uint256 i = 0; i < _tokenIds.length; i++) { - _vote(_tokenIds[i], _votes); - } - } - - function vote( - uint256 _tokenId, - GaugeVote[] calldata _votes - ) public nonReentrant whenNotPaused whenVotingActive { - _vote(_tokenId, _votes); - } - - function _vote(uint256 _tokenId, GaugeVote[] calldata _votes) internal { - // ensure the user is allowed to vote on this - if (!IVotingEscrow(escrow).isApprovedOrOwner(_msgSender(), _tokenId)) { - revert NotApprovedOrOwner(); - } - - uint256 votingPower = IVotingEscrow(escrow).votingPower(_tokenId); - if (votingPower == 0) revert NoVotingPower(); - - uint256 numVotes = _votes.length; - if (numVotes == 0) revert NoVotes(); - - // clear any existing votes - if (isVoting(_tokenId)) _reset(_tokenId); - - // voting power continues to increase over the voting epoch. - // this means you can revote later in the epoch to increase votes. - // while not a huge problem, it's worth noting that when rewards are fully - // on chain, this could be a vector for gaming. - TokenVoteData storage voteData = tokenVoteData[_tokenId]; - uint256 votingPowerUsed = 0; - uint256 sumOfWeights = 0; - - for (uint256 i = 0; i < numVotes; i++) { - sumOfWeights += _votes[i].weight; - } - - // this is technically redundant as checks below will revert div by zero - // but it's clearer to the caller if we revert here - if (sumOfWeights == 0) revert NoVotes(); - - // iterate over votes and distribute weight - for (uint256 i = 0; i < numVotes; i++) { - // the gauge must exist and be active, - // it also can't have any votes or we haven't reset properly - address gauge = _votes[i].gauge; - - if (!gaugeExists(gauge)) revert GaugeDoesNotExist(gauge); - if (!isActive(gauge)) revert GaugeInactive(gauge); - - // prevent double voting - if (voteData.votes[gauge] != 0) revert DoubleVote(); - - // calculate the weight for this gauge - uint256 votesForGauge = (_votes[i].weight * votingPower) / sumOfWeights; - if (votesForGauge == 0) revert NoVotes(); - - // record the vote for the token - voteData.gaugesVotedFor.push(gauge); - voteData.votes[gauge] += votesForGauge; - - // update the total weights accruing to this gauge - gaugeVotes[gauge] += votesForGauge; - - // track the running changes to the total - // this might differ from the total voting power - // due to rounding, so aggregating like this ensures consistency - votingPowerUsed += votesForGauge; - - emit Voted({ - voter: _msgSender(), - gauge: gauge, - epoch: epochId(), - tokenId: _tokenId, - votingPowerCastForGauge: votesForGauge, - totalVotingPowerInGauge: gaugeVotes[gauge], - totalVotingPowerInContract: totalVotingPowerCast + votingPowerUsed, - timestamp: block.timestamp - }); - } - - // record the total weight used for this vote - totalVotingPowerCast += votingPowerUsed; - voteData.usedVotingPower = votingPowerUsed; - - // setting the last voted also has the second-order effect of indicating the user has voted - voteData.lastVoted = block.timestamp; - } - - function reset(uint256 _tokenId) external nonReentrant whenNotPaused whenVotingActive { - if (!IVotingEscrow(escrow).isApprovedOrOwner(msg.sender, _tokenId)) - revert NotApprovedOrOwner(); - if (!isVoting(_tokenId)) revert NotCurrentlyVoting(); - _reset(_tokenId); - } - - function _reset(uint256 _tokenId) internal { - // get what we need - TokenVoteData storage voteData = tokenVoteData[_tokenId]; - address[] storage pastVotes = voteData.gaugesVotedFor; - - // reset the global state variables we don't need - voteData.usedVotingPower = 0; - voteData.lastVoted = 0; - - // iterate over all the gauges voted for and reset the votes - uint256 votingPowerToRemove = 0; - for (uint256 i = 0; i < pastVotes.length; i++) { - address gauge = pastVotes[i]; - uint256 _votes = voteData.votes[gauge]; - - // remove from the total weight and globals - gaugeVotes[gauge] -= _votes; - votingPowerToRemove += _votes; - - delete voteData.votes[gauge]; - - emit Reset({ - voter: _msgSender(), - gauge: gauge, - epoch: epochId(), - tokenId: _tokenId, - votingPowerRemovedFromGauge: _votes, - totalVotingPowerInGauge: gaugeVotes[gauge], - totalVotingPowerInContract: totalVotingPowerCast - votingPowerToRemove, - timestamp: block.timestamp - }); - } - - // clear the remaining state - voteData.gaugesVotedFor = new address[](0); - totalVotingPowerCast -= votingPowerToRemove; - } - - /*/////////////////////////////////////////////////////////////// - Gauge Management - //////////////////////////////////////////////////////////////*/ - - function gaugeExists(address _gauge) public view returns (bool) { - // this doesn't revert if you create multiple gauges at genesis - // but that's not a practical concern - return gauges[_gauge].created > 0; - } - - function isActive(address _gauge) public view returns (bool) { - return gauges[_gauge].active; - } - - function createGauge( - address _gauge, - string calldata _metadataURI - ) external auth(GAUGE_ADMIN_ROLE) nonReentrant returns (address gauge) { - if (_gauge == address(0)) revert ZeroGauge(); - if (gaugeExists(_gauge)) revert GaugeExists(); - - gauges[_gauge] = Gauge(true, block.timestamp, _metadataURI); - gaugeList.push(_gauge); - - emit GaugeCreated(_gauge, _msgSender(), _metadataURI); - return _gauge; - } - - function deactivateGauge(address _gauge) external auth(GAUGE_ADMIN_ROLE) { - if (!gaugeExists(_gauge)) revert GaugeDoesNotExist(_gauge); - if (!isActive(_gauge)) revert GaugeActivationUnchanged(); - gauges[_gauge].active = false; - emit GaugeDeactivated(_gauge); - } - - function activateGauge(address _gauge) external auth(GAUGE_ADMIN_ROLE) { - if (!gaugeExists(_gauge)) revert GaugeDoesNotExist(_gauge); - if (isActive(_gauge)) revert GaugeActivationUnchanged(); - gauges[_gauge].active = true; - emit GaugeActivated(_gauge); - } - - function updateGaugeMetadata( - address _gauge, - string calldata _metadataURI - ) external auth(GAUGE_ADMIN_ROLE) { - if (!gaugeExists(_gauge)) revert GaugeDoesNotExist(_gauge); - gauges[_gauge].metadataURI = _metadataURI; - emit GaugeMetadataUpdated(_gauge, _metadataURI); - } - - /*/////////////////////////////////////////////////////////////// - Getters: Epochs & Time - //////////////////////////////////////////////////////////////*/ - - /// @notice autogenerated epoch id based on elapsed time - function epochId() public view returns (uint256) { - return IClock(clock).currentEpoch(); - } - - /// @notice whether voting is active in the current epoch - function votingActive() public view returns (bool) { - return IClock(clock).votingActive(); - } - - /// @notice timestamp of the start of the next epoch - function epochStart() external view returns (uint256) { - return IClock(clock).epochStartTs(); - } - - /// @notice timestamp of the start of the next voting period - function epochVoteStart() external view returns (uint256) { - return IClock(clock).epochVoteStartTs(); - } - - /// @notice timestamp of the end of the current voting period - function epochVoteEnd() external view returns (uint256) { - return IClock(clock).epochVoteEndTs(); - } - - /*/////////////////////////////////////////////////////////////// - Getters: Mappings - //////////////////////////////////////////////////////////////*/ - - function getGauge(address _gauge) external view returns (Gauge memory) { - return gauges[_gauge]; - } - - function getAllGauges() external view returns (address[] memory) { - return gaugeList; - } - - function isVoting(uint256 _tokenId) public view returns (bool) { - return tokenVoteData[_tokenId].lastVoted > 0; - } - - function votes(uint256 _tokenId, address _gauge) external view returns (uint256) { - return tokenVoteData[_tokenId].votes[_gauge]; - } - - function gaugesVotedFor(uint256 _tokenId) external view returns (address[] memory) { - return tokenVoteData[_tokenId].gaugesVotedFor; - } - - function usedVotingPower(uint256 _tokenId) external view returns (uint256) { - return tokenVoteData[_tokenId].usedVotingPower; - } - - /// Rest of UUPS logic is handled by OSx plugin - uint256[43] private __gap; -} diff --git a/src/voting/SimpleGaugeVoterSetup.sol b/src/voting/SimpleGaugeVoterSetup.sol deleted file mode 100644 index fdf5db2..0000000 --- a/src/voting/SimpleGaugeVoterSetup.sol +++ /dev/null @@ -1,346 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; - -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; -import {IProposal} from "@aragon/osx/core/plugin/proposal/IProposal.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; -import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; -import {PluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol"; - -// these should be interfaces -import {SimpleGaugeVoter} from "@voting/SimpleGaugeVoter.sol"; -import {VotingEscrow} from "@escrow/VotingEscrowIncreasing.sol"; -import {ExitQueue} from "@escrow/ExitQueue.sol"; -import {QuadraticIncreasingEscrow} from "@escrow/QuadraticIncreasingEscrow.sol"; -import {Clock} from "@clock/Clock.sol"; -import {Lock} from "@escrow/Lock.sol"; - -/// @param isPaused Whether the voter contract is deployed in a paused state -/// @param veTokenName The name of the voting escrow token -/// @param veTokenSymbol The symbol of the voting escrow token -/// @param token The underlying token for the escrow -/// @param cooldown The cooldown period for the exit queue -/// @param warmup The warmup period for the escrow curve -struct ISimpleGaugeVoterSetupParams { - // voter - bool isPaused; - // escrow - NFT - string veTokenName; - string veTokenSymbol; - // escrow - main - address token; - uint256 minDeposit; - // queue - uint256 feePercent; - uint48 cooldown; - uint48 minLock; - // curve - uint48 warmup; -} - -contract SimpleGaugeVoterSetup is PluginSetup { - using Address for address; - using Clones for address; - using ERC165Checker for address; - using ProxyLib for address; - - /// @notice The identifier of the `EXECUTE_PERMISSION` permission. - bytes32 public constant EXECUTE_PERMISSION_ID = keccak256("EXECUTE_PERMISSION"); - - /// @notice Thrown if passed helpers array is of wrong length. - /// @param length The array length of passed helpers. - error WrongHelpersArrayLength(uint256 length); - - /// @dev implementation of the gaugevoting plugin - address voterBase; - - /// @dev implementation of the escrow voting curve - address curveBase; - - /// @dev implementation of the exit queue - address queueBase; - - /// @dev implementation of the escrow locker - address escrowBase; - - /// @dev implementation of the clock - address clockBase; - - /// @dev implementation of the escrow NFT - address nftBase; - - /// @notice Deploys the setup by binding the implementation contracts required during installation. - constructor( - address _voterBase, - address _curveBase, - address _queueBase, - address _escrowBase, - address _clockBase, - address _nftBase - ) PluginSetup() { - voterBase = _voterBase; - curveBase = _curveBase; - queueBase = _queueBase; - escrowBase = _escrowBase; - clockBase = _clockBase; - nftBase = _nftBase; - } - - function implementation() external view returns (address) { - return voterBase; - } - - /// @inheritdoc IPluginSetup - /// @dev You need to set the helpers on the plugin as a post install action. - function prepareInstallation( - address _dao, - bytes calldata _data - ) external returns (address plugin, PreparedSetupData memory preparedSetupData) { - ISimpleGaugeVoterSetupParams memory params = abi.decode( - _data, - (ISimpleGaugeVoterSetupParams) - ); - - // deploy the clock - address clock = address( - Clock( - clockBase.deployUUPSProxy(abi.encodeWithSelector(Clock.initialize.selector, _dao)) - ) - ); - - // deploy the escrow locker - VotingEscrow escrow = VotingEscrow( - escrowBase.deployUUPSProxy( - abi.encodeCall( - VotingEscrow.initialize, - (params.token, _dao, clock, params.minDeposit) - ) - ) - ); - - // deploy the voting contract (plugin) - SimpleGaugeVoter voter = SimpleGaugeVoter( - voterBase.deployUUPSProxy( - abi.encodeCall( - SimpleGaugeVoter.initialize, - (_dao, address(escrow), params.isPaused, clock) - ) - ) - ); - plugin = address(voter); - - // deploy the curve - address curve = curveBase.deployUUPSProxy( - abi.encodeCall( - QuadraticIncreasingEscrow.initialize, - (address(escrow), _dao, params.warmup, clock) - ) - ); - - // deploy the exit queue - address exitQueue = queueBase.deployUUPSProxy( - abi.encodeCall( - ExitQueue.initialize, - (address(escrow), params.cooldown, _dao, params.feePercent, clock, params.minLock) - ) - ); - - // deploy the escrow NFT - address nftLock = nftBase.deployUUPSProxy( - abi.encodeCall( - Lock.initialize, - (address(escrow), params.veTokenName, params.veTokenSymbol, _dao) - ) - ); - - // encode our setup data with permissions and helpers - PermissionLib.MultiTargetPermission[] memory permissions = getPermissions( - _dao, - plugin, - curve, - exitQueue, - address(escrow), - clock, - nftLock, - PermissionLib.Operation.Grant - ); - - address[] memory helpers = new address[](5); - - helpers[0] = curve; - helpers[1] = exitQueue; - helpers[2] = address(escrow); - helpers[3] = clock; - helpers[4] = address(nftLock); - - preparedSetupData.helpers = helpers; - preparedSetupData.permissions = permissions; - } - - /// @inheritdoc IPluginSetup - function prepareUninstallation( - address _dao, - SetupPayload calldata _payload - ) external view returns (PermissionLib.MultiTargetPermission[] memory permissions) { - // check the helpers length - if (_payload.currentHelpers.length != 5) { - revert WrongHelpersArrayLength(_payload.currentHelpers.length); - } - - address curve = _payload.currentHelpers[0]; - address queue = _payload.currentHelpers[1]; - address escrow = _payload.currentHelpers[2]; - address clock = _payload.currentHelpers[3]; - address nftLock = _payload.currentHelpers[4]; - - permissions = getPermissions( - _dao, - _payload.plugin, - curve, - queue, - escrow, - clock, - nftLock, - PermissionLib.Operation.Revoke - ); - } - - /// @notice Returns the permissions required for the plugin install and uninstall. - /// @param _dao The DAO address on this chain. - /// @param _plugin The plugin address. - /// @param _grantOrRevoke The operation to perform - function getPermissions( - address _dao, - address _plugin, - address _curve, - address _queue, - address _escrow, - address _clock, - address _nft, - PermissionLib.Operation _grantOrRevoke - ) public view returns (PermissionLib.MultiTargetPermission[] memory) { - PermissionLib.MultiTargetPermission[] - memory permissions = new PermissionLib.MultiTargetPermission[](10); - - permissions[0] = PermissionLib.MultiTargetPermission({ - permissionId: SimpleGaugeVoter(_plugin).GAUGE_ADMIN_ROLE(), - where: _plugin, - who: _dao, - operation: _grantOrRevoke, - condition: PermissionLib.NO_CONDITION - }); - - permissions[1] = PermissionLib.MultiTargetPermission({ - permissionId: VotingEscrow(_escrow).ESCROW_ADMIN_ROLE(), - where: _escrow, - who: _dao, - operation: _grantOrRevoke, - condition: PermissionLib.NO_CONDITION - }); - - permissions[2] = PermissionLib.MultiTargetPermission({ - permissionId: ExitQueue(_queue).QUEUE_ADMIN_ROLE(), - where: _queue, - who: _dao, - operation: _grantOrRevoke, - condition: PermissionLib.NO_CONDITION - }); - - permissions[3] = PermissionLib.MultiTargetPermission({ - permissionId: QuadraticIncreasingEscrow(_curve).CURVE_ADMIN_ROLE(), - where: _curve, - who: _dao, - operation: _grantOrRevoke, - condition: PermissionLib.NO_CONDITION - }); - - permissions[4] = PermissionLib.MultiTargetPermission({ - permissionId: SimpleGaugeVoter(_plugin).UPGRADE_PLUGIN_PERMISSION_ID(), - where: _plugin, - who: _dao, - operation: _grantOrRevoke, - condition: PermissionLib.NO_CONDITION - }); - - permissions[5] = PermissionLib.MultiTargetPermission({ - permissionId: Clock(_clock).CLOCK_ADMIN_ROLE(), - where: _clock, - who: _dao, - operation: _grantOrRevoke, - condition: PermissionLib.NO_CONDITION - }); - - permissions[6] = PermissionLib.MultiTargetPermission({ - permissionId: Lock(_nft).LOCK_ADMIN_ROLE(), - where: _nft, - who: _dao, - operation: _grantOrRevoke, - condition: PermissionLib.NO_CONDITION - }); - - permissions[7] = PermissionLib.MultiTargetPermission({ - permissionId: VotingEscrow(_escrow).PAUSER_ROLE(), - where: _escrow, - who: _dao, - operation: _grantOrRevoke, - condition: PermissionLib.NO_CONDITION - }); - - permissions[8] = PermissionLib.MultiTargetPermission({ - permissionId: VotingEscrow(_escrow).SWEEPER_ROLE(), - where: _escrow, - who: _dao, - operation: _grantOrRevoke, - condition: PermissionLib.NO_CONDITION - }); - - permissions[9] = PermissionLib.MultiTargetPermission({ - permissionId: ExitQueue(_queue).WITHDRAW_ROLE(), - where: _queue, - who: _dao, - operation: _grantOrRevoke, - condition: PermissionLib.NO_CONDITION - }); - return permissions; - } - - function encodeSetupData( - ISimpleGaugeVoterSetupParams calldata _params - ) external pure returns (bytes memory) { - return abi.encode(_params); - } - - /// @notice Simple utility for external applications create the encoded setup data. - function encodeSetupData( - bool isPaused, - string calldata veTokenName, - string calldata veTokenSymbol, - address token, - uint48 cooldown, - uint48 warmup, - uint256 feePercent, - uint48 minLock, - uint256 minDeposit - ) external pure returns (bytes memory) { - return - abi.encode( - ISimpleGaugeVoterSetupParams({ - isPaused: isPaused, - token: token, - veTokenName: veTokenName, - veTokenSymbol: veTokenSymbol, - warmup: warmup, - cooldown: cooldown, - feePercent: feePercent, - minLock: minLock, - minDeposit: minDeposit - }) - ); - } -} diff --git a/test/escrow/curve/QuadraticCurveBase.t.sol b/test/escrow/curve/QuadraticCurveBase.t.sol deleted file mode 100644 index 6f8a1d8..0000000 --- a/test/escrow/curve/QuadraticCurveBase.t.sol +++ /dev/null @@ -1,59 +0,0 @@ -pragma solidity ^0.8.17; - -import {TestHelpers} from "@helpers/TestHelpers.sol"; -import {console2 as console} from "forge-std/console2.sol"; -import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; - -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO, createTestDAO} from "@mocks/MockDAO.sol"; -import {QuadraticIncreasingEscrow, IVotingEscrow, IEscrowCurve} from "src/escrow/increasing/QuadraticIncreasingEscrow.sol"; -import {Clock} from "@clock/Clock.sol"; -import {IVotingEscrowIncreasing, ILockedBalanceIncreasing} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; - -contract MockEscrow { - address public token; - QuadraticIncreasingEscrow public curve; - - function setCurve(QuadraticIncreasingEscrow _curve) external { - curve = _curve; - } - - function checkpoint( - uint256 _tokenId, - IVotingEscrow.LockedBalance memory _oldLocked, - IVotingEscrow.LockedBalance memory _newLocked - ) external { - return curve.checkpoint(_tokenId, _oldLocked, _newLocked); - } -} - -contract QuadraticCurveBase is TestHelpers, ILockedBalanceIncreasing { - using ProxyLib for address; - QuadraticIncreasingEscrow internal curve; - MockEscrow internal escrow; - - function setUp() public virtual override { - super.setUp(); - escrow = new MockEscrow(); - - address impl = address(new QuadraticIncreasingEscrow()); - - bytes memory initCalldata = abi.encodeCall( - QuadraticIncreasingEscrow.initialize, - (address(escrow), address(dao), 3 days, address(clock)) - ); - - curve = QuadraticIncreasingEscrow(impl.deployUUPSProxy(initCalldata)); - - // grant this address admin privileges - DAO(payable(address(dao))).grant({ - _who: address(this), - _where: address(curve), - _permissionId: curve.CURVE_ADMIN_ROLE() - }); - - escrow.setCurve(curve); - } -} diff --git a/test/escrow/curve/QuadraticCurveLogic.t.sol b/test/escrow/curve/QuadraticCurveLogic.t.sol deleted file mode 100644 index 058c1c5..0000000 --- a/test/escrow/curve/QuadraticCurveLogic.t.sol +++ /dev/null @@ -1,45 +0,0 @@ -pragma solidity ^0.8.17; - -import {console2 as console} from "forge-std/console2.sol"; - -import {QuadraticIncreasingEscrow, IVotingEscrow, IEscrowCurve} from "src/escrow/increasing/QuadraticIncreasingEscrow.sol"; -import {IVotingEscrowIncreasing, ILockedBalanceIncreasing} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; - -import {QuadraticCurveBase, MockEscrow} from "./QuadraticCurveBase.t.sol"; - -contract TestQuadraticIncreasingCurveLogic is QuadraticCurveBase { - address attacker = address(0x1); - error InvalidCheckpoint(); - - function testUUPSUpgrade() public { - address newImpl = address(new QuadraticIncreasingEscrow()); - curve.upgradeTo(newImpl); - assertEq(curve.implementation(), newImpl); - - bytes memory err = _authErr(attacker, address(curve), curve.CURVE_ADMIN_ROLE()); - vm.prank(attacker); - vm.expectRevert(err); - curve.upgradeTo(newImpl); - } - - function testCannotWriteNewCheckpointInPast() public { - LockedBalance memory first = LockedBalance({amount: 100, start: 100}); - LockedBalance memory second = LockedBalance({amount: 200, start: 99}); - - escrow.checkpoint(1, LockedBalance(0, 0), first); - vm.expectRevert(InvalidCheckpoint.selector); - escrow.checkpoint(1, first, second); - } - - function testCanWriteNewCheckpointsAtSameTime() public { - LockedBalance memory first = LockedBalance({amount: 100, start: 100}); - LockedBalance memory second = LockedBalance({amount: 200, start: 100}); - - escrow.checkpoint(1, LockedBalance(0, 0), first); - escrow.checkpoint(1, first, second); - - // check we have only 1 token interval - assertEq(curve.tokenPointIntervals(1), 1); - assertEq(curve.tokenPointHistory(1, 1).bias, 200); - } -} diff --git a/test/escrow/curve/QuadraticCurveMath.t.sol b/test/escrow/curve/QuadraticCurveMath.t.sol deleted file mode 100644 index e89a880..0000000 --- a/test/escrow/curve/QuadraticCurveMath.t.sol +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import {console2 as console} from "forge-std/console2.sol"; - -import {QuadraticIncreasingEscrow, IVotingEscrow, IEscrowCurve} from "src/escrow/increasing/QuadraticIncreasingEscrow.sol"; -import {IVotingEscrowIncreasing, ILockedBalanceIncreasing} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; - -import {QuadraticCurveBase} from "./QuadraticCurveBase.t.sol"; - -contract TestQuadraticIncreasingCurve is QuadraticCurveBase { - function test_votingPowerComputesCorrect() public view { - uint256 amount = 100e18; - - int256[3] memory coefficients = curve.getCoefficients(100e18); - - uint256 const = uint256(coefficients[0]); - uint256 linear = uint256(coefficients[1]); - uint256 quadratic = uint256(coefficients[2]); - - assertEq(const, amount); - - console.log("Coefficients: %st^2 + %st + %s", quadratic, linear, const); - - for (uint i; i <= 53; i++) { - uint period = 2 weeks * i; - console.log( - "Period: %d Voting Power : %s", - i, - curve.getBias(period, 100e18) / 1e18 - ); - console.log( - "Period: %d Voting Power Bound: %s", - i, - curve.getBias(period, 100e18) / 1e18 - ); - console.log("Period: %d Voting Power Raw: %s\n", i, curve.getBias(period, 100e18)); - } - - // uncomment to see the full curve - // for (uint i; i <= 14 * 6; i++) { - // uint day = i * 1 days; - // uint week = day / 7 days; - // uint period = day / 2 weeks; - - // console.log("[Day: %d | Week %d | Period %d]", i, week, period); - // console.log("Voting Power : %s", curve.getBias(day, 100e18) / 1e18); - // console.log("Voting Power (raw): %s\n", curve.getBias(day, 100e18)); - // } - } - - // write a new checkpoint - /* - * for the 1000 tokens (Python) (extend to 1bn with more zeros) - * 0 Voting Power: 1000000000000000000000 - * 1 minute Voting Power: 1000000953907203932160 - * 1 hour Voting Power: 1000057234432234487808 - * 1 day Voting Power: 1001373626373626396672 - * WARMUP_PERIOD (3 days) Voting Power: 1004120879120879058944 - * WARMUP_PERIOD + 1s Voting Power: 1004120895019332534272 - * 1 week Voting Power: 1009615384615384645632 - * 1 period (2 weeks) Voting Power: 1019230769230769160192 - * 10 periods (10 * PERIOD) Voting Power: 1192307692307692388352 - * 50% periods (26 * PERIOD) Voting Power: 1500000000000000000000 - * 35 periods (35 * PERIOD) Voting Power: 1673076923076923097088 - * PERIOD_END (26 * PERIOD) Voting Power: 2000000000000000000000 - - * for the 420.69 tokens (Python) - * 0 Voting Power: 420690000000000000000 - * 1 minute Voting Power: 420690401299221577728 - * 1 hour Voting Power: 420714077953296695296 - * 1 day Voting Power: 421267870879120883712 - * WARMUP_PERIOD (3 days) Voting Power: 422423612637362651136 - * WARMUP_PERIOD + 1s Voting Power: 422423619325683040256 - * 1 week Voting Power: 424735096153846120448 - * 1 period (2 weeks) Voting Power: 428780192307692306432 - * 10 periods (10 * PERIOD) Voting Power: 501591923076923064320 - * 50% periods (26 * PERIOD) Voting Power: 631035000000000032768 - * 35 periods (35 * PERIOD) Voting Power: 703846730769230856192 - * PERIOD_END (26 * PERIOD) Voting Power: 841380000000000000000 - */ - function testWritesCheckpoint() public { - uint tokenIdFirst = 1; - uint tokenIdSecond = 2; - uint208 depositFirst = 420.69e18; - uint208 depositSecond = 1_000_000_000e18; - uint start = 52 weeks; - - // initial conditions, no balance - assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance before deposit"); - - vm.warp(start); - vm.roll(420); - - // still no balance - assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance before deposit"); - - escrow.checkpoint( - tokenIdFirst, - LockedBalance(0, 0), - LockedBalance(depositFirst, uint48(block.timestamp)) - ); - escrow.checkpoint( - tokenIdSecond, - LockedBalance(0, 0), - LockedBalance(depositSecond, uint48(block.timestamp)) - ); - - // check the token point is registered - IEscrowCurve.TokenPoint memory tokenPoint = curve.tokenPointHistory(tokenIdFirst, 1); - assertEq(tokenPoint.bias, depositFirst, "Bias is incorrect"); - assertEq(tokenPoint.checkpointTs, block.timestamp, "CP Timestamp is incorrect"); - assertEq(tokenPoint.writtenTs, block.timestamp, "Written Timestamp is incorrect"); - - // balance now is zero but Warm up - assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance after deposit before warmup"); - assertEq(curve.isWarm(tokenIdFirst), false, "Not warming up"); - - // wait for warmup - vm.warp(block.timestamp + curve.warmupPeriod()); - assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance after deposit before warmup"); - assertEq(curve.isWarm(tokenIdFirst), false, "Not warming up"); - assertEq(curve.isWarm(tokenIdSecond), false, "Not warming up II"); - - // warmup complete - vm.warp(block.timestamp + 1); - - assertEq( - curve.votingPowerAt(tokenIdFirst, block.timestamp), - 422423619325633557508, - "Balance incorrect after warmup" - ); - assertEq(curve.isWarm(tokenIdFirst), true, "Still warming up"); - - assertEq( - curve.votingPowerAt(tokenIdSecond, block.timestamp), - 1004120895019214998000000000, - "Balance incorrect after warmup II" - ); - - // warp to the start of period 2 - vm.warp(start + clock.epochDuration()); - assertEq( - curve.votingPowerAt(tokenIdFirst, block.timestamp), - 428780192307461588352, - "Balance incorrect after p1" - ); - - uint256 expectedMaxI = 841379999988002594304; - uint256 expectedMaxII = 1999999999971481600000000000; - - // warp to the final period - // TECHNICALLY, this should round to a whole max - // but FP arithmetic has a small rounding error and it finishes just below - vm.warp(start + clock.epochDuration() * 52); - assertEq( - curve.votingPowerAt(tokenIdFirst, block.timestamp), - expectedMaxI, - "Balance incorrect after pend" - ); - assertEq( - curve.votingPowerAt(tokenIdSecond, block.timestamp), - expectedMaxII, - "Balance incorrect after pend II " - ); - - // warp to the future and balance should be the same - vm.warp(520 weeks); - assertEq( - curve.votingPowerAt(tokenIdFirst, block.timestamp), - expectedMaxI, - "Balance incorrect after 10 years" - ); - } -} diff --git a/test/escrow/escrow/EscrowAdmin.t.sol b/test/escrow/escrow/EscrowAdmin.t.sol deleted file mode 100644 index ba9104d..0000000 --- a/test/escrow/escrow/EscrowAdmin.t.sol +++ /dev/null @@ -1,173 +0,0 @@ -pragma solidity ^0.8.17; - -import {EscrowBase} from "./EscrowBase.sol"; - -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; - -import {Lock} from "@escrow/Lock.sol"; -import {VotingEscrow} from "@escrow/VotingEscrowIncreasing.sol"; -import {QuadraticIncreasingEscrow} from "@escrow/QuadraticIncreasingEscrow.sol"; -import {ExitQueue} from "@escrow/ExitQueue.sol"; -import {SimpleGaugeVoter, SimpleGaugeVoterSetup} from "src/voting/SimpleGaugeVoterSetup.sol"; - -contract TestEscrowAdmin is EscrowBase { - address attacker = address(1); - - function testCannotResetLockNFT() public { - vm.expectRevert(LockNFTAlreadySet.selector); - escrow.setLockNFT(address(0)); - } - - function testSetCurve(address _newCurve) public { - escrow.setCurve(_newCurve); - assertEq(escrow.curve(), _newCurve); - - bytes memory err = _authErr(attacker, address(escrow), escrow.ESCROW_ADMIN_ROLE()); - vm.prank(attacker); - vm.expectRevert(err); - escrow.setCurve(_newCurve); - escrow.setCurve(address(0)); - } - - function testSetMinDeposit(uint256 _newMinDeposit) public { - vm.expectEmit(false, false, false, true); - emit MinDepositSet(_newMinDeposit); - escrow.setMinDeposit(_newMinDeposit); - assertEq(escrow.minDeposit(), _newMinDeposit); - - bytes memory err = _authErr(attacker, address(escrow), escrow.ESCROW_ADMIN_ROLE()); - vm.prank(attacker); - vm.expectRevert(err); - escrow.setMinDeposit(_newMinDeposit); - } - - function testSetVoter(address _newVoter) public { - escrow.setVoter(_newVoter); - assertEq(escrow.voter(), _newVoter); - - bytes memory err = _authErr(attacker, address(escrow), escrow.ESCROW_ADMIN_ROLE()); - vm.prank(attacker); - vm.expectRevert(err); - escrow.setVoter(_newVoter); - escrow.setVoter(address(0)); - } - - function testSetQueue(address _newQueue) public { - escrow.setQueue(_newQueue); - assertEq(escrow.queue(), _newQueue); - - bytes memory err = _authErr(attacker, address(escrow), escrow.ESCROW_ADMIN_ROLE()); - vm.prank(attacker); - vm.expectRevert(err); - escrow.setQueue(_newQueue); - } - - function testUUPSUpgrade() public { - address newImpl = address(new VotingEscrow()); - escrow.upgradeTo(newImpl); - assertEq(escrow.implementation(), newImpl); - - bytes memory err = _authErr(attacker, address(escrow), escrow.ESCROW_ADMIN_ROLE()); - vm.prank(attacker); - vm.expectRevert(err); - escrow.upgradeTo(newImpl); - } - - function testPause() public { - escrow.pause(); - assertTrue(escrow.paused()); - - escrow.unpause(); - assertFalse(escrow.paused()); - - bytes memory err = _authErr(attacker, address(escrow), escrow.PAUSER_ROLE()); - vm.startPrank(attacker); - { - vm.expectRevert(err); - escrow.pause(); - - vm.expectRevert(err); - escrow.unpause(); - } - vm.stopPrank(); - } - - function testCannotCallPausedFunctions() public { - escrow.pause(); - - bytes memory PAUSEABLE_ERROR = "Pausable: paused"; - - vm.expectRevert(PAUSEABLE_ERROR); - escrow.createLock(100); - - vm.expectRevert(PAUSEABLE_ERROR); - escrow.createLockFor(100, address(this)); - - vm.expectRevert(PAUSEABLE_ERROR); - escrow.beginWithdrawal(100); - - vm.expectRevert(PAUSEABLE_ERROR); - escrow.withdraw(100); - } - - function testWhitelist() public { - address addr = address(1); - vm.expectEmit(true, false, false, true); - emit WhitelistSet(addr, true); - - nftLock.setWhitelisted(addr, true); - assertTrue(nftLock.whitelisted(addr)); - - nftLock.setWhitelisted(addr, false); - assertFalse(nftLock.whitelisted(addr)); - - nftLock.enableTransfers(); - assertTrue( - nftLock.whitelisted(address(uint160(uint256(keccak256("WHITELIST_ANY_ADDRESS"))))) - ); - - bytes memory err = _authErr(attacker, address(nftLock), nftLock.LOCK_ADMIN_ROLE()); - - vm.startPrank(attacker); - { - vm.expectRevert(err); - nftLock.setWhitelisted(addr, true); - - vm.expectRevert(err); - nftLock.enableTransfers(); - } - vm.stopPrank(); - } - - // test unusued function revert - function testUnusedFunctionRevert() public { - vm.expectRevert(); - escrow.totalVotingPowerAt(0); - - vm.expectRevert(); - escrow.totalVotingPower(); - } - - // test upgrading the lock - function testUpgradeLock() public { - address newImpl = address(new Lock()); - nftLock.upgradeTo(newImpl); - assertEq(nftLock.implementation(), newImpl); - - bytes memory err = _authErr(attacker, address(nftLock), nftLock.LOCK_ADMIN_ROLE()); - vm.prank(attacker); - vm.expectRevert(err); - nftLock.upgradeTo(newImpl); - } - - // hal-16 should not be possible to unwhitelist the escrow - function testUnwhitelistEscrow() public { - address escrowAddr = nftLock.escrow(); - vm.expectRevert(ForbiddenWhitelistAddress.selector); - nftLock.setWhitelisted(escrowAddr, false); - } -} diff --git a/test/escrow/escrow/EscrowBase.sol b/test/escrow/escrow/EscrowBase.sol deleted file mode 100644 index 0167425..0000000 --- a/test/escrow/escrow/EscrowBase.sol +++ /dev/null @@ -1,230 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {Test} from "forge-std/Test.sol"; - -// aragon contracts -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {MockPluginSetupProcessor} from "@mocks/osx/MockPSP.sol"; -import {MockDAOFactory} from "@mocks/osx/MockDAOFactory.sol"; -import {MockERC20} from "@mocks/MockERC20.sol"; -import {createTestDAO} from "@mocks/MockDAO.sol"; - -import "@helpers/OSxHelpers.sol"; -import {ProxyLib} from "@libs/ProxyLib.sol"; - -import {IVotingEscrowEventsStorageErrorsEvents} from "@escrow-interfaces/IVotingEscrowIncreasing.sol"; -import {IWhitelistErrors, IWhitelistEvents} from "@escrow-interfaces/ILock.sol"; -import {Lock} from "@escrow/Lock.sol"; -import {VotingEscrow} from "@escrow/VotingEscrowIncreasing.sol"; -import {QuadraticIncreasingEscrow} from "@escrow/QuadraticIncreasingEscrow.sol"; -import {ExitQueue} from "@escrow/ExitQueue.sol"; -import {SimpleGaugeVoter, SimpleGaugeVoterSetup} from "src/voting/SimpleGaugeVoterSetup.sol"; -import {Clock} from "@clock/Clock.sol"; - -contract EscrowBase is - Test, - IVotingEscrowEventsStorageErrorsEvents, - IWhitelistErrors, - IWhitelistEvents -{ - using ProxyLib for address; - string name = "Voting Escrow"; - string symbol = "VE"; - - MockPluginSetupProcessor psp; - MockDAOFactory daoFactory; - MockERC20 token; - - Lock nftLock; - VotingEscrow escrow; - QuadraticIncreasingEscrow curve; - SimpleGaugeVoter voter; - ExitQueue queue; - Clock clock; - - DAO dao; - Multisig multisig; - MultisigSetup multisigSetup; - address deployer = address(this); - - error OnlyEscrow(); - - function setUp() public virtual { - // _deployOSX(); - _deployDAO(); - - // deploy our contracts - token = new MockERC20(); - clock = _deployClock(address(dao)); - - escrow = _deployEscrow(address(token), address(dao), address(clock), 1); - curve = _deployCurve(address(escrow), address(dao), 3 days, address(clock)); - nftLock = _deployLock(address(escrow), name, symbol, address(dao)); - - // to be added as proxies - voter = _deployVoter(address(dao), address(escrow), false, address(clock)); - queue = _deployExitQueue(address(escrow), 3 days, address(dao), 0, address(clock), 1); - - // grant this contract admin privileges - dao.grant({ - _who: address(this), - _where: address(escrow), - _permissionId: escrow.ESCROW_ADMIN_ROLE() - }); - - // grant this contract pause role - dao.grant({ - _who: address(this), - _where: address(escrow), - _permissionId: escrow.PAUSER_ROLE() - }); - - // give this contract admin privileges on the peripherals - dao.grant({ - _who: address(this), - _where: address(voter), - _permissionId: voter.GAUGE_ADMIN_ROLE() - }); - - dao.grant({ - _who: address(this), - _where: address(queue), - _permissionId: queue.QUEUE_ADMIN_ROLE() - }); - - dao.grant({ - _who: address(this), - _where: address(curve), - _permissionId: curve.CURVE_ADMIN_ROLE() - }); - - dao.grant({ - _who: address(this), - _where: address(nftLock), - _permissionId: nftLock.LOCK_ADMIN_ROLE() - }); - - // link them - escrow.setCurve(address(curve)); - escrow.setVoter(address(voter)); - escrow.setQueue(address(queue)); - escrow.setLockNFT(address(nftLock)); - } - - function _authErr( - address _caller, - address _contract, - bytes32 _perm - ) internal view returns (bytes memory) { - return - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - _contract, - _caller, - _perm - ); - } - - function _deployEscrow( - address _token, - address _dao, - address _clock, - uint256 _minDeposit - ) public returns (VotingEscrow) { - VotingEscrow impl = new VotingEscrow(); - - bytes memory initCalldata = abi.encodeCall( - VotingEscrow.initialize, - (_token, _dao, _clock, _minDeposit) - ); - return VotingEscrow(address(impl).deployUUPSProxy(initCalldata)); - } - - function _deployLock( - address _escrow, - string memory _name, - string memory _symbol, - address _dao - ) public returns (Lock) { - Lock impl = new Lock(); - - bytes memory initCalldata = abi.encodeWithSelector( - Lock.initialize.selector, - _escrow, - _name, - _symbol, - _dao - ); - return Lock(address(impl).deployUUPSProxy(initCalldata)); - } - - function _deployCurve( - address _escrow, - address _dao, - uint48 _warmup, - address _clock - ) public returns (QuadraticIncreasingEscrow) { - QuadraticIncreasingEscrow impl = new QuadraticIncreasingEscrow(); - - bytes memory initCalldata = abi.encodeCall( - QuadraticIncreasingEscrow.initialize, - (_escrow, _dao, _warmup, _clock) - ); - return QuadraticIncreasingEscrow(address(impl).deployUUPSProxy(initCalldata)); - } - - function _deployVoter( - address _dao, - address _escrow, - bool _reset, - address _clock - ) public returns (SimpleGaugeVoter) { - SimpleGaugeVoter impl = new SimpleGaugeVoter(); - - bytes memory initCalldata = abi.encodeCall( - SimpleGaugeVoter.initialize, - (_dao, _escrow, _reset, _clock) - ); - return SimpleGaugeVoter(address(impl).deployUUPSProxy(initCalldata)); - } - - function _deployExitQueue( - address _escrow, - uint48 _cooldown, - address _dao, - uint256 _feePercent, - address _clock, - uint48 _minLock - ) public returns (ExitQueue) { - ExitQueue impl = new ExitQueue(); - - bytes memory initCalldata = abi.encodeCall( - ExitQueue.initialize, - (_escrow, _cooldown, _dao, _feePercent, _clock, _minLock) - ); - return ExitQueue(address(impl).deployUUPSProxy(initCalldata)); - } - - function _deployClock(address _dao) internal returns (Clock) { - address impl = address(new Clock()); - bytes memory initCalldata = abi.encodeWithSelector(Clock.initialize.selector, _dao); - return Clock(impl.deployUUPSProxy(initCalldata)); - } - - function _deployOSX() internal { - // deploy the mock PSP with the multisig plugin - multisigSetup = new MultisigSetup(); - psp = new MockPluginSetupProcessor(address(multisigSetup)); - daoFactory = new MockDAOFactory(psp); - } - - function _deployDAO() internal { - dao = createTestDAO(deployer); - } -} diff --git a/test/escrow/escrow/EscrowCreateLock.t.sol b/test/escrow/escrow/EscrowCreateLock.t.sol deleted file mode 100644 index d0d4522..0000000 --- a/test/escrow/escrow/EscrowCreateLock.t.sol +++ /dev/null @@ -1,373 +0,0 @@ -pragma solidity ^0.8.17; - -import {EscrowBase} from "./EscrowBase.sol"; - -import {console2 as console} from "forge-std/console2.sol"; -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; -import {MockERC20} from "@mocks/MockERC20.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; - -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {VotingEscrow} from "@escrow/VotingEscrowIncreasing.sol"; - -import {SimpleGaugeVoter, SimpleGaugeVoterSetup} from "src/voting/SimpleGaugeVoterSetup.sol"; - -contract TestCreateLock is EscrowBase, IEscrowCurveTokenStorage { - function setUp() public override { - super.setUp(); - - // token.mint(address(this), 1_000_000_000 ether); - } - - function _expTime(uint256 _time) internal pure returns (uint256) { - return uint(_time) + 1 weeks - (_time % 1 weeks); - } - - function testCannotCreateLockWithZeroValue() public { - vm.expectRevert(ZeroAmount.selector); - escrow.createLock(0); - } - - function testCantMintToZeroAddress() public { - token.mint(address(this), 1e18); - token.approve(address(escrow), 1e18); - - vm.expectRevert("ERC721: mint to the zero address"); - - escrow.createLockFor(1e18, address(0)); - } - - function testCantMintBelowMinDeposit(uint256 _minDeposit) public { - vm.assume(_minDeposit > 1); - escrow.setMinDeposit(_minDeposit); - - token.mint(address(123), _minDeposit); - vm.startPrank(address(123)); - { - token.approve(address(escrow), _minDeposit); - vm.expectRevert(AmountTooSmall.selector); - escrow.createLock(_minDeposit - 1); - } - vm.stopPrank(); - - // should not revert - escrow.setMinDeposit(1); - - vm.prank(address(123)); - escrow.createLock(1); - } - - /// @param _value is positive, we check this in a previous test. It needs to fit inside an int256 - /// so we use the maximum value for a uint128 - /// @param _depositor is not the zero address, we check this in a previous test we need to restrict to non contracts in fuzzing as too many revert cases - /// @param _time is bound to 32 bits to avoid overflow - seems reasonable as is not a user input - function testFuzz_createLock(uint128 _value, address _depositor, uint32 _time) public { - vm.assume(_value > 0); - vm.assume(_depositor != address(0) && address(_depositor).code.length == 0); - - // set the min deposit to _value - escrow.setMinDeposit(_value); - - // set zero warmup for this test - curve.setWarmupPeriod(0); - - vm.warp(_time); - token.mint(_depositor, _value); - - // start of next week - uint256 expectedTime = _expTime(_time); - - vm.startPrank(_depositor); - { - token.approve(address(escrow), _value); - vm.expectEmit(true, true, true, true); - emit Deposit(_depositor, 1, expectedTime, _value, _value); - escrow.createLock(_value); - } - vm.stopPrank(); - - // get all tokenIds for the user - uint256[] memory tokenIds = escrow.ownedTokens(_depositor); - assertEq(tokenIds.length, 1); - - uint256 tokenId = tokenIds[0]; - assertEq(tokenId, 1); - - // Essentially the below are tests within tests - // TODO extract these to separate functions with a common setup. - // not needed right now. - - // check the various getters - { - // warp to the start date - vm.warp(expectedTime); - - // voting power will be the same as the deposit - // as we have no cooldown - assertEq(escrow.votingPower(tokenId), _value, "value incorrect for the tokenid"); - assertEq( - escrow.votingPowerForAccount(_depositor), - _value, - "value incorrect for account" - ); - } - - // check the user has the nft: - { - assertEq(tokenId, tokenId, "wrong token id"); - assertEq(nftLock.ownerOf(tokenId), _depositor); - assertEq(nftLock.balanceOf(_depositor), tokenId); - assertEq(nftLock.totalSupply(), tokenId); - } - - // check the contract has tokens - { - assertEq(escrow.totalLocked(), _value, "!totalLocked"); - assertEq(token.balanceOf(address(escrow)), _value, "balance of NFT incorrect"); - } - - // Check the lock was created: - { - LockedBalance memory lock = escrow.locked(tokenId); - assertEq(lock.amount, _value); - assertEq(lock.start, expectedTime); - } - // Check the checkpoint was created - { - uint256 epoch = curve.tokenPointIntervals(tokenId); - TokenPoint memory checkpoint = curve.tokenPointHistory(tokenId, epoch); - assertEq(checkpoint.bias, _value); - assertEq(checkpoint.checkpointTs, expectedTime); - } - } - - // we don't fuzz or test aggregates here, that's covered by the above - // we just run for 2 known users - struct User { - uint value; - address addr; - } - - function testCreateMultipleLocks() public { - User[] memory users = new User[](2); - User memory matt = User(1_000_500 ether, address(0x1)); - User memory shane = User(2_500_900 ether, address(0x2)); - users[0] = matt; - users[1] = shane; - - uint total = matt.value + shane.value; - - // mint the dawgs some tokens - token.mint(matt.addr, matt.value); - token.mint(shane.addr, shane.value); - - uint expTime = _expTime(block.timestamp); - - // create the locks - { - vm.startPrank(matt.addr); - { - token.approve(address(escrow), matt.value); - vm.expectEmit(true, true, true, true); - emit Deposit(matt.addr, 1, expTime, matt.value, matt.value); - escrow.createLock(matt.value); - } - vm.stopPrank(); - - vm.startPrank(shane.addr); - { - token.approve(address(escrow), shane.value); - vm.expectEmit(true, true, true, true); - emit Deposit(shane.addr, 2, expTime, shane.value, total); - escrow.createLock(shane.value); - } - vm.stopPrank(); - } - - for (uint i = 0; i < users.length; ++i) { - User memory user = users[i]; - uint expectedTokenId = i + 1; - - uint256[] memory tokenIds = escrow.ownedTokens(user.addr); - assertEq(tokenIds.length, 1, "user should only have 1 token"); - uint256 tokenId = tokenIds[0]; - - // check the user has the nft: - { - assertEq(tokenId, expectedTokenId, "token id unexpected"); - assertEq(nftLock.ownerOf(tokenId), user.addr, "owner should be the user"); - assertEq(nftLock.balanceOf(user.addr), 1, "user should only have 1 token"); - } - - // check the user has the right lock - { - LockedBalance memory lock = escrow.locked(tokenId); - assertEq(lock.amount, user.value); - assertEq(lock.start, expTime); - } - } - - // check the aggregate values - { - assertEq(escrow.totalLocked(), total); - assertEq(token.balanceOf(address(escrow)), total); - assertEq(nftLock.totalSupply(), 2); - } - } - - function testTimeLogicSnapsToNextDepositDate() public { - // define 3 users: - - // shane deposits just before the next deposit date - address shane = address(0x1); - - // matt deposits ON the next deposit date - address matt = address(0x2); - - // phil deposits just after the next deposit date - address phil = address(0x3); - - // mint tokens to each - token.mint(shane, 1 ether); - token.mint(matt, 1 ether); - token.mint(phil, 1 ether); - - // warp to genesis: this makes it easy to calculate deposit dates - vm.warp(0); - - // now the next deposit is 1 week from now - uint expectedNextDeposit = clock.checkpointInterval(); - - // shane deposits just before the next deposit date - vm.warp(expectedNextDeposit - 1); - vm.startPrank(shane); - { - token.approve(address(escrow), 1 ether); - escrow.createLock(1 ether); - } - vm.stopPrank(); - - // matt deposits ON the next deposit date - vm.warp(expectedNextDeposit); - vm.startPrank(matt); - { - token.approve(address(escrow), 1 ether); - escrow.createLock(1 ether); - } - vm.stopPrank(); - - // phil deposits just after the next deposit date - vm.warp(expectedNextDeposit + 1); - vm.startPrank(phil); - { - token.approve(address(escrow), 1 ether); - escrow.createLock(1 ether); - } - vm.stopPrank(); - - // our expected behaviour: - // shane's lock should snap to the nearest deposit date (+1 second) - assertEq( - escrow.locked(1).start, - expectedNextDeposit, - "shane's lock should snap to the upcoming deposit date" - ); - // matt is an edge case, they should also snap to the next deposit date (+1 week) - assertEq( - escrow.locked(2).start, - expectedNextDeposit + clock.checkpointInterval(), - "matt's lock should snap to the next deposit date" - ); - // phil should snap to the next deposit date (+1 week) - assertEq( - escrow.locked(3).start, - expectedNextDeposit + clock.checkpointInterval(), - "phil's lock should snap to the next deposit date" - ); - } - - function testFuzz_createLockFor(uint128 _value) public { - vm.assume(_value > 0); - escrow.setMinDeposit(_value); - vm.warp(1); - - // try with regular user - address _who = address(0x1); - - token.mint(address(this), _value); - token.approve(address(escrow), _value); - - vm.expectEmit(true, true, true, true); - emit Deposit(_who, 1, 1 weeks, _value, _value); - escrow.createLockFor(_value, _who); - - // try with a contract - address _contract = address(new ERC721Receiver()); - - token.mint(address(this), _value); - token.approve(address(escrow), _value); - - vm.expectEmit(true, true, true, true); - emit Deposit(_contract, 2, 1 weeks, _value, 2 * uint(_value)); - escrow.createLockFor(_value, _contract); - } - - function testCannotUseAJankyERC20() public { - // deploy a janky token - TaxERC20 janky = new TaxERC20(); - - escrow = _deployEscrow(address(janky), address(dao), address(clock), 1); - curve = _deployCurve(address(escrow), address(dao), 3 days, address(clock)); - nftLock = _deployLock(address(escrow), name, symbol, address(dao)); - - // grant this contract admin privileges - dao.grant({ - _who: address(this), - _where: address(escrow), - _permissionId: escrow.ESCROW_ADMIN_ROLE() - }); - - escrow.setLockNFT(address(nftLock)); - escrow.setCurve(address(curve)); - - // mint some tokens - janky.mint(address(this), 1 ether); - janky.approve(address(escrow), 1 ether); - - // create a lock - vm.expectRevert(TransferBalanceIncorrect.selector); - escrow.createLock(1 ether); - } -} - -contract ERC721Receiver { - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public pure returns (bytes4) { - return this.onERC721Received.selector; - } -} - -contract TaxERC20 is MockERC20 { - function _transfer( - address sender, - address recipient, - uint256 amount - ) internal virtual override { - require(amount >= 1, "Transfer amount must be at least 1 wei"); - - uint256 burnAmount = 1; // Amount to burn on every transfer - uint256 sendAmount = amount - burnAmount; // Amount to send to recipient - - super._burn(sender, burnAmount); // Burn 1 wei from sender's balance - super._transfer(sender, recipient, sendAmount); // Transfer remaining amount to recipient - } -} - -contract Mock {} diff --git a/test/escrow/escrow/EscrowSweep.t.sol b/test/escrow/escrow/EscrowSweep.t.sol deleted file mode 100644 index 7c4d6cb..0000000 --- a/test/escrow/escrow/EscrowSweep.t.sol +++ /dev/null @@ -1,163 +0,0 @@ -pragma solidity ^0.8.17; - -import {EscrowBase} from "./EscrowBase.sol"; - -import {console2 as console} from "forge-std/console2.sol"; -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; - -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {VotingEscrow} from "@escrow/VotingEscrowIncreasing.sol"; - -import {SimpleGaugeVoter, SimpleGaugeVoterSetup} from "src/voting/SimpleGaugeVoterSetup.sol"; -import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; - -contract TestSweep is EscrowBase, IEscrowCurveTokenStorage, IGaugeVote { - function setUp() public override { - super.setUp(); - - // grant the sweeper role to this contract - dao.grant({ - _who: address(this), - _where: address(escrow), - _permissionId: escrow.SWEEPER_ROLE() - }); - } - - function testCanSweepExcessTokens() public { - token.mint(address(escrow), 1000); - - assertEq(token.balanceOf(address(escrow)), 1000); - assertEq(token.balanceOf(address(this)), 0); - assertEq(escrow.totalLocked(), 0); - - vm.expectEmit(true, false, false, true); - emit Sweep(address(this), 1000); - escrow.sweep(); - - assertEq(token.balanceOf(address(escrow)), 0); - assertEq(token.balanceOf(address(this)), 1000); - } - - function testCannotSweepFromLocked() public { - address user = address(1); - token.mint(address(user), 1000); - - vm.startPrank(user); - { - token.approve(address(escrow), 1000); - escrow.createLock(1000); - } - vm.stopPrank(); - - assertEq(token.balanceOf(address(escrow)), 1000); - assertEq(token.balanceOf(address(this)), 0); - assertEq(escrow.totalLocked(), 1000); - - // first try with no excess tokens - vm.expectRevert(NothingToSweep.selector); - escrow.sweep(); - - // check that nothing changed - assertEq(token.balanceOf(address(escrow)), 1000); - assertEq(token.balanceOf(address(this)), 0); - assertEq(escrow.totalLocked(), 1000); - - // now try with excess tokens - token.mint(address(escrow), 1000); - - assertEq(token.balanceOf(address(escrow)), 2000); - - escrow.sweep(); - - assertEq(token.balanceOf(address(escrow)), 1000); - assertEq(token.balanceOf(address(this)), 1000); - assertEq(escrow.totalLocked(), 1000); - } - - function testOnlySweeperRole() public { - address notThis = address(1); - - bytes memory err = _authErr(notThis, address(escrow), escrow.SWEEPER_ROLE()); - - vm.prank(notThis); - vm.expectRevert(err); - escrow.sweep(); - - vm.prank(notThis); - vm.expectRevert(err); - escrow.sweepNFT(1, address(this)); - } - - function testCannotSweepNFTIfNotInContract() public { - // create a lock - token.mint(address(this), 1000); - token.approve(address(escrow), 1000); - uint tokenId = escrow.createLock(1000); - - // try to sweep the NFT -- should fail as it's not in the contract - vm.expectRevert(NothingToSweep.selector); - escrow.sweepNFT(tokenId, address(this)); - } - - function testCannotSweepNFTIfInQueue() public { - // create lock, enter withdrawal - token.mint(address(this), 1000); - token.approve(address(escrow), 1000); - uint tokenId = escrow.createLock(1000); - - // warp to the min lock - vm.warp(1 weeks + 1); - - nftLock.approve(address(escrow), tokenId); - escrow.beginWithdrawal(tokenId); - - // try to sweep the NFT -- should fail as it's in the queue - vm.expectRevert(CannotExit.selector); - escrow.sweepNFT(tokenId, address(this)); - } - - function testCannotSweepNFTIfNotWhitelisted() public { - // create the lock and transfer the NFT to the contract - token.mint(address(this), 1000); - token.approve(address(escrow), 1000); - uint tokenId = escrow.createLock(1000); - nftLock.transferFrom(address(this), address(escrow), tokenId); - - // try to sweep the NFT -- should fail as this address is not whitelisted - vm.expectRevert(NotWhitelisted.selector); - escrow.sweepNFT(tokenId, address(this)); - } - - function testCanSweepNFT() public { - // create, transfer, whitelis, sweep - token.mint(address(this), 1000); - token.approve(address(escrow), 1000); - uint tokenId = escrow.createLock(1000); - nftLock.transferFrom(address(this), address(escrow), tokenId); - nftLock.setWhitelisted(address(this), true); - - assertEq(nftLock.balanceOf(address(this)), 0); - assertEq(nftLock.balanceOf(address(escrow)), 1); - - vm.expectEmit(true, false, false, true); - emit SweepNFT(address(this), tokenId); - escrow.sweepNFT(tokenId, address(this)); - - assertEq(nftLock.balanceOf(address(this)), 1); - assertEq(nftLock.balanceOf(address(escrow)), 0); - } - - // Needed for the ERC721Receiver interface - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) external pure returns (bytes4) { - return this.onERC721Received.selector; - } -} diff --git a/test/escrow/escrow/EscrowTransfers.t.sol b/test/escrow/escrow/EscrowTransfers.t.sol deleted file mode 100644 index 41f6e23..0000000 --- a/test/escrow/escrow/EscrowTransfers.t.sol +++ /dev/null @@ -1,60 +0,0 @@ -pragma solidity ^0.8.17; - -import {EscrowBase} from "./EscrowBase.sol"; - -import {console2 as console} from "forge-std/console2.sol"; -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; - -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {VotingEscrow} from "@escrow/VotingEscrowIncreasing.sol"; - -import {SimpleGaugeVoter, SimpleGaugeVoterSetup} from "src/voting/SimpleGaugeVoterSetup.sol"; - -contract TestEscrowTransfers is EscrowBase, IEscrowCurveTokenStorage { - uint deposit = 100e18; - uint tokenId; - - function setUp() public override { - super.setUp(); - - // create an NFT - token.mint(address(this), deposit); - token.approve(address(escrow), deposit); - tokenId = escrow.createLock(deposit); - - assertEq(nftLock.balanceOf(address(this)), 1); - } - - function testCannotTransferByDefault() public { - vm.expectRevert(NotWhitelisted.selector); - nftLock.transferFrom(address(this), address(123), tokenId); - - vm.expectRevert(NotWhitelisted.selector); - nftLock.safeTransferFrom(address(this), address(123), tokenId); - } - - function testCanTransferIfWhitelisted() public { - nftLock.setWhitelisted(address(123), true); - - assertEq(nftLock.balanceOf(address(123)), 0); - assertEq(nftLock.balanceOf(address(this)), 1); - - nftLock.transferFrom(address(this), address(123), tokenId); - - assertEq(nftLock.balanceOf(address(123)), 1); - assertEq(nftLock.balanceOf(address(this)), 0); - } - - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) external pure returns (bytes4) { - return this.onERC721Received.selector; - } -} diff --git a/test/escrow/escrow/EscrowWithdraw.t.sol b/test/escrow/escrow/EscrowWithdraw.t.sol deleted file mode 100644 index 646e7c1..0000000 --- a/test/escrow/escrow/EscrowWithdraw.t.sol +++ /dev/null @@ -1,339 +0,0 @@ -pragma solidity ^0.8.17; - -import {EscrowBase} from "./EscrowBase.sol"; - -import {console2 as console} from "forge-std/console2.sol"; -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; - -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {VotingEscrow} from "@escrow/VotingEscrowIncreasing.sol"; - -import {SimpleGaugeVoter, SimpleGaugeVoterSetup} from "@voting/SimpleGaugeVoterSetup.sol"; -import {IGaugeVote} from "@voting/ISimpleGaugeVoter.sol"; -import {ITicket} from "@escrow-interfaces/IExitQueue.sol"; - -contract TestWithdraw is EscrowBase, IEscrowCurveTokenStorage, IGaugeVote, ITicket { - address gauge = address(1); - - GaugeVote[] votes; - - function setUp() public override { - super.setUp(); - - vm.warp(1); - - // make a voting gauge - voter.createGauge(gauge, "metadata"); - votes.push(GaugeVote({gauge: gauge, weight: 1})); - - escrow.setMinDeposit(0); - } - - // setup a fee withdrawal - function testFuzz_feeWithdrawal(uint64 _fee, uint128 _dep, address _who) public { - vm.assume(_who != address(0) && address(_who).code.length == 0); - vm.assume(_dep > 0); - - if (_fee > 10_000) _fee = 10_000; - - queue.setFeePercent(_fee); - - token.mint(_who, _dep); - uint tokenId; - vm.startPrank(_who); - { - token.approve(address(escrow), _dep); - tokenId = escrow.createLock(_dep); - - // voting active after cooldown - vm.warp(block.timestamp + 2 weeks + 1 hours); - - // make a vote - voter.vote(tokenId, votes); - } - vm.stopPrank(); - - // can't enter a withdrawal while voting - vm.expectRevert(CannotExit.selector); - escrow.beginWithdrawal(tokenId); - - // enter a withdrawal - vm.startPrank(_who); - { - nftLock.approve(address(escrow), tokenId); - escrow.resetVotesAndBeginWithdrawal(tokenId); - } - vm.stopPrank(); - - // can't withdraw if not ticket holder - vm.expectRevert(NotTicketHolder.selector); - escrow.withdraw(tokenId); - - // can't force approve - vm.expectRevert("ERC721: approve caller is not token owner or approved for all"); - vm.prank(_who); - nftLock.approve(_who, tokenId); - - // must wait till end of queue - vm.warp(3 weeks); - vm.expectRevert(CannotExit.selector); - vm.prank(_who); - escrow.withdraw(tokenId); - - uint fee = queue.calculateFee(tokenId); - - // withdraw - vm.warp(3 weeks + 1); - vm.prank(_who); - vm.expectEmit(true, true, false, true); - emit Withdraw(_who, tokenId, _dep - fee, block.timestamp, 0); - escrow.withdraw(tokenId); - - // fee sent to queue - assertEq(token.balanceOf(address(queue)), fee); - - // remainder sent to user - assertEq(token.balanceOf(_who), _dep - fee); - - bool feeDepTooSmall = uint(_fee) * uint(_dep) < 10_000; - - if (_fee == 0 || feeDepTooSmall) { - assertEq(token.balanceOf(_who), _dep); - assertEq(token.balanceOf(address(queue)), 0); - } else { - assertGt(token.balanceOf(address(queue)), 0); - } - - // nft is burned - assertEq(nftLock.balanceOf(_who), 0); - assertEq(nftLock.balanceOf(address(escrow)), 0); - assertEq(escrow.totalLocked(), 0); - - assertEq(escrow.votingPowerForAccount(_who), 0); - } - - function testFuzz_enterWithdrawal(uint128 _dep, address _who) public { - vm.assume(_who != address(0) && address(_who).code.length == 0); - vm.assume(_dep > 0); - - // make a deposit - token.mint(_who, _dep); - uint tokenId; - vm.startPrank(_who); - { - token.approve(address(escrow), _dep); - tokenId = escrow.createLock(_dep); - - // voting active after cooldown - vm.warp(block.timestamp + 2 weeks + 1 hours); - - // make a vote - voter.vote(tokenId, votes); - } - vm.stopPrank(); - - // can't enter a withdrawal while voting - vm.expectRevert(CannotExit.selector); - escrow.beginWithdrawal(tokenId); - - // enter a withdrawal - vm.startPrank(_who); - { - nftLock.approve(address(escrow), tokenId); - escrow.resetVotesAndBeginWithdrawal(tokenId); - } - vm.stopPrank(); - - // should now have the nft in the escrow - assertEq(nftLock.balanceOf(_who), 0); - assertEq(nftLock.balanceOf(address(escrow)), 1); - - // voting power should still be there as the cp is still active - assertGt(escrow.votingPower(tokenId), 0); - - // but we should have written a token point in the future - TokenPoint memory up = curve.tokenPointHistory(tokenId, 2); - assertEq(up.bias, 0); - assertEq(up.writtenTs, block.timestamp); - assertEq(up.checkpointTs, 3 weeks); - - // should have a ticket expiring in a few days - assertEq(queue.canExit(tokenId), false); - assertEq(queue.queue(tokenId).exitDate, 3 weeks); - - // check the future to see the voting power expired - vm.warp(3 weeks + 1); - assertEq(escrow.votingPower(tokenId), 0); - } - - function testCanWithdrawAfterCooldownOnlyIfCrossesWeekBoundary() public { - address _who = address(1); - uint128 _dep = 100e18; - - // voting window is ea. 2 weeks + 1 hour - vm.warp(2 weeks + 1 hours + 1); - - assertTrue(voter.votingActive()); - - token.mint(_who, _dep); - uint tokenId; - vm.startPrank(_who); - { - token.approve(address(escrow), _dep); - tokenId = escrow.createLock(_dep); - - // voting active after cooldown - // +1 week: voting ends - // +2 weeks: next voting period opens - vm.warp(block.timestamp + 2 weeks); - - // make a vote - voter.vote(tokenId, votes); - - // warp so cooldown crosses the week boundary - vm.warp(block.timestamp + clock.checkpointInterval() - queue.cooldown() + 1); - - nftLock.approve(address(escrow), tokenId); - escrow.resetVotesAndBeginWithdrawal(tokenId); - } - vm.stopPrank(); - - // must wait till after end of cooldown - vm.warp(block.timestamp + queue.cooldown()); - vm.expectRevert(CannotExit.selector); - vm.prank(_who); - escrow.withdraw(tokenId); - - uint fee = queue.calculateFee(tokenId); - - // withdraw - vm.warp(block.timestamp + 1); - vm.prank(_who); - vm.expectEmit(true, true, false, true); - emit Withdraw(_who, tokenId, _dep - fee, block.timestamp, 0); - escrow.withdraw(tokenId); - - // asserts - assertEq(token.balanceOf(address(queue)), fee); - assertEq(token.balanceOf(_who), _dep - fee); - assertEq(nftLock.balanceOf(_who), 0); - assertEq(nftLock.balanceOf(address(escrow)), 0); - assertEq(escrow.totalLocked(), 0); - } - // HAL-13: locks are re-used causing reverts and duplications - function testCanCreateLockAfterBurning() public { - address USER1 = address(1); - address USER2 = address(2); - - // mint - token.mint(USER1, 100); - token.mint(USER2, 100); - - vm.prank(USER1); - token.approve(address(escrow), 100); - - vm.prank(USER2); - token.approve(address(escrow), 100); - - vm.prank(USER1); - uint256 tokenId = escrow.createLockFor(100, USER1); // Token ID 1 - - vm.prank(USER2); - uint256 tokenId2 = escrow.createLockFor(100, USER2); // Token ID 2 - - // approve - uint256 tokenId3; - vm.startPrank(USER1); - { - nftLock.approve(address(escrow), tokenId); - - vm.warp(1 weeks + 1 days); - - escrow.beginWithdrawal(tokenId); - - Ticket memory ticket = queue.queue(tokenId); - vm.warp(ticket.exitDate + 1); - - escrow.withdraw(tokenId); - token.approve(address(escrow), 100); - tokenId3 = escrow.createLockFor(100, USER1); // Token ID 2 - Duplicated - Reescrowrt - } - vm.stopPrank(); - - // assert that the lock Id is incremented - assertEq(tokenId3, 3); - assertNotEq(tokenId2, tokenId3); - assertEq(nftLock.totalSupply(), 2); - assertEq(escrow.lastLockId(), 3); - } - - function testCantDepositAndWithdrawInTheSameBlock() public { - // this is a timing - // deposit falls exactly on the week boundary, so that we start immediately - // then we try to withdraw in the same block - // we also need a zero warmup period or we will error with a zero voting power - curve.setWarmupPeriod(0); - - // warp to a week boundary - vm.warp(1 weeks); - - // deposit - token.mint(address(1), 100e18); - - uint tokenId; - - bytes memory data = abi.encodeWithSelector(CannotExit.selector); - // start the deposit - vm.startPrank(address(1)); - { - // create - token.approve(address(escrow), 100e18); - tokenId = escrow.createLock(100e18); - - // withdraw - nftLock.approve(address(escrow), tokenId); - vm.expectRevert(data); - escrow.beginWithdrawal(tokenId); - } - vm.stopPrank(); - } - - function testCannotExitDuringWarmupIfWarmupIsLong() public { - // warp to genesis - vm.warp(1); - - // set a long warmup that crosses an epoch boundary - curve.setWarmupPeriod(4 weeks); - - // make a deposit - token.mint(address(1), 100e18); - - uint tokenId; - - vm.startPrank(address(1)); - { - token.approve(address(escrow), 100e18); - tokenId = escrow.createLock(100e18); - } - vm.stopPrank(); - - // check the start date - uint start = escrow.locked(tokenId).start; - assertEq(start, 1 weeks); - - vm.warp(1 weeks); - - // should not be able to exit - vm.startPrank(address(1)); - { - nftLock.approve(address(escrow), tokenId); - vm.expectRevert(CannotExit.selector); - escrow.beginWithdrawal(tokenId); - } - vm.stopPrank(); - } -} diff --git a/test/escrow/escrow/Lock.t.sol b/test/escrow/escrow/Lock.t.sol deleted file mode 100644 index 4c5ae05..0000000 --- a/test/escrow/escrow/Lock.t.sol +++ /dev/null @@ -1,110 +0,0 @@ -pragma solidity ^0.8.17; - -import {EscrowBase} from "./EscrowBase.sol"; - -import {console2 as console} from "forge-std/console2.sol"; -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; - -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {VotingEscrow} from "@escrow/VotingEscrowIncreasing.sol"; -import {Lock} from "@escrow/Lock.sol"; - -import {SimpleGaugeVoter, SimpleGaugeVoterSetup} from "src/voting/SimpleGaugeVoterSetup.sol"; -import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; -import {ILock} from "@escrow-interfaces/ILock.sol"; - -contract TestLockMintBurn is EscrowBase, IEscrowCurveTokenStorage, IGaugeVote { - function testDeploy( - string memory _name, - string memory _symbol, - address _dao, - address _escrow - ) public { - vm.expectEmit(true, false, false, true); - emit WhitelistSet(address(_escrow), true); - Lock _nftLock = _deployLock(_escrow, _name, _symbol, _dao); - - assertEq(_nftLock.name(), _name); - assertEq(_nftLock.symbol(), _symbol); - assertEq(_nftLock.escrow(), _escrow); - assertEq(address(_nftLock.dao()), _dao); - } - - function testSupportsInterface() public view { - assertTrue(nftLock.supportsInterface(type(ILock).interfaceId)); - assertFalse(nftLock.supportsInterface(0xffffffff)); - } - - function testFuzz_OnlyEscrowCanMint(address _notEscrow) public { - vm.assume(_notEscrow != address(escrow)); - - vm.expectRevert(OnlyEscrow.selector); - - vm.prank(_notEscrow); - nftLock.mint(address(123), 1); - - assertEq(nftLock.balanceOf(address(123)), 0); - assertEq(nftLock.totalSupply(), 0); - - vm.prank(address(escrow)); - nftLock.mint(address(123), 1); - - assertEq(nftLock.balanceOf(address(123)), 1); - assertEq(nftLock.totalSupply(), 1); - } - - function testFuzz_OnlyEscrowCanBurn(address _notEscrow) public { - vm.assume(_notEscrow != address(escrow)); - - vm.prank(address(escrow)); - nftLock.mint(address(123), 1); - - vm.expectRevert(OnlyEscrow.selector); - - vm.prank(_notEscrow); - nftLock.burn(1); - - assertEq(nftLock.balanceOf(address(123)), 1); - assertEq(nftLock.totalSupply(), 1); - - vm.prank(address(escrow)); - nftLock.burn(1); - - assertEq(nftLock.balanceOf(address(123)), 0); - assertEq(nftLock.totalSupply(), 0); - } - - // HAL-14 receiver must implement ERC721Receiver - function testCannotMintToNonReceiver() public { - vm.prank(address(escrow)); - vm.expectRevert("ERC721: transfer to non ERC721Receiver implementer"); - nftLock.mint(address(this), 1); - } - - // HAL-14 test reentrancy with safe mint - function testReentrantCantCallMint() public { - NFTReentrant reentrant = new NFTReentrant(); - - Lock newLock = _deployLock(address(reentrant), "name", "symbol", address(dao)); - - vm.prank(address(reentrant)); - vm.expectRevert("revert"); - newLock.mint(address(reentrant), 1); - } -} - -contract NFTReentrant { - function onERC721Received(address, address, uint256, bytes memory) public returns (bytes4) { - (bool success, ) = msg.sender.call( - abi.encodeWithSignature("mint(address,uint256)", address(this), 1) - ); - if (!success) { - revert("revert"); - } - return this.onERC721Received.selector; - } -} diff --git a/test/escrow/queue/ExitQueue.t.sol b/test/escrow/queue/ExitQueue.t.sol deleted file mode 100644 index 0ec7735..0000000 --- a/test/escrow/queue/ExitQueue.t.sol +++ /dev/null @@ -1,301 +0,0 @@ -pragma solidity ^0.8.17; - -import {console2 as console} from "forge-std/console2.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; -import {DAO, createTestDAO} from "@mocks/MockDAO.sol"; -import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; -import {IExitQueue, ExitQueue} from "@escrow/ExitQueue.sol"; -import {ITicket} from "@escrow-interfaces/IExitQueue.sol"; -import {ExitQueueBase} from "./ExitQueueBase.sol"; - -contract TestExitQueue is ExitQueueBase, ITicket { - // test inital state - escrow, queue, cooldown is set in constructor + dao - function testFuzz_initialState( - address _escrow, - uint16 _fee, - uint48 _cooldown, - address _clock, - uint48 _minLock - ) public { - vm.assume(_fee <= 10_000); - vm.assume(_minLock > 0); - DAO dao_ = createTestDAO(address(this)); - queue = _deployExitQueue( - address(_escrow), - _cooldown, - address(dao_), - _fee, - _clock, - _minLock - ); - assertEq(queue.escrow(), _escrow); - assertEq(queue.cooldown(), _cooldown); - assertEq(queue.minLock(), _minLock); - assertEq(address(queue.dao()), address(dao_)); - assertEq(queue.feePercent(), _fee); - } - - function testFuzz_canUpdateMinLock(uint48 _minLock) public { - vm.assume(_minLock > 0); - vm.expectEmit(false, false, false, true); - emit MinLockSet(_minLock); - queue.setMinLock(_minLock); - assertEq(queue.minLock(), _minLock); - - // test that the minLock cannot be set to 0 - vm.expectRevert(MinLockOutOfBounds.selector); - queue.setMinLock(0); - } - - function testOnlyManagerCanUpdateMinLock(address _notThis) public { - vm.assume(_notThis != address(this)); - bytes memory data = abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(queue), - _notThis, - queue.QUEUE_ADMIN_ROLE() - ); - vm.expectRevert(data); - vm.prank(_notThis); - queue.setMinLock(123); - } - - function testFuzz_canUpdateFee(uint16 _fee) public { - vm.assume(_fee <= 10_000); - vm.expectEmit(false, false, true, false); - emit FeePercentSet(_fee); - queue.setFeePercent(_fee); - assertEq(queue.feePercent(), _fee); - } - - function testOnlyManagerCanUpdateFee(address _notThis) public { - vm.assume(_notThis != address(this)); - bytes memory data = abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(queue), - _notThis, - queue.QUEUE_ADMIN_ROLE() - ); - vm.expectRevert(data); - vm.prank(_notThis); - queue.setFeePercent(0); - } - - // test the exit queue manager can udpdate the cooldown && emits event - function testFuzz_canUpdateCooldown(uint48 _cooldown) public { - vm.expectEmit(false, false, false, true); - emit CooldownSet(_cooldown); - queue.setCooldown(_cooldown); - assertEq(queue.cooldown(), _cooldown); - } - - function testOnlyManagerCanUpdateCooldown(address _notThis) public { - vm.assume(_notThis != address(this)); - bytes memory data = abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(queue), - _notThis, - queue.QUEUE_ADMIN_ROLE() - ); - vm.expectRevert(data); - vm.prank(_notThis); - queue.setCooldown(0); - } - - // test only the escrow can call stateful functions - function testFuzz_onlyEscrowCanCall(address _notEscrow) public { - vm.assume(_notEscrow != address(escrow)); - bytes memory err = abi.encodeWithSelector(OnlyEscrow.selector); - - vm.startPrank(_notEscrow); - { - vm.expectRevert(err); - queue.exit(0); - - vm.expectRevert(err); - queue.queueExit(0, address(this)); - } - - vm.stopPrank(); - } - - // test queuing the ticket older reverts if address == 0 - function testQueueRevertZeroAddress() public { - bytes memory err = abi.encodeWithSelector(ZeroAddress.selector); - vm.expectRevert(err); - vm.prank(address(escrow)); - queue.queueExit(0, address(0)); - } - - // test can't double queue the same token - function testFuzzCannotDoubleQueueSameToken(address _holder, address _otherHolder) public { - vm.assume(_holder != address(0)); - vm.assume(_otherHolder != address(0)); - - vm.startPrank(address(escrow)); - { - queue.queueExit(0, _holder); - bytes memory err = abi.encodeWithSelector(AlreadyQueued.selector); - vm.expectRevert(err); - queue.queueExit(0, _holder); - - // other holder same issue - vm.expectRevert(err); - queue.queueExit(0, _otherHolder); - } - vm.stopPrank(); - } - - // test emits a queued event and writes to state - function testFuzz_canQueue(uint256 _tokenId, address _ticketHolder, uint32 _warp) public { - vm.assume(_ticketHolder != address(0)); - vm.assume(_warp > 0); // any time other than genesis - vm.warp(_warp); - // if there are less than cooldown seconds left, exit date is end of the - // week, else it's now + cooldown - uint expectedExitDate; - uint remainingSecondsBeforeNextCP = 1 weeks - (block.timestamp % 1 weeks); - - if (queue.cooldown() < remainingSecondsBeforeNextCP) { - expectedExitDate = block.timestamp + remainingSecondsBeforeNextCP; - } else { - expectedExitDate = block.timestamp + queue.cooldown(); - } - - vm.expectEmit(true, true, false, true); - emit ExitQueued(_tokenId, _ticketHolder, expectedExitDate); - vm.prank(address(escrow)); - queue.queueExit(_tokenId, _ticketHolder); - assertEq(queue.ticketHolder(_tokenId), _ticketHolder); - assertEq(queue.queue(_tokenId).exitDate, expectedExitDate); - } - - // test can exit updates only after the cooldown period - function testFuzz_canExit(uint48 _cooldown) public { - vm.assume(_cooldown > 0); - vm.warp(1); - - queue.setCooldown(_cooldown); - - // this will trigger a 0,0 locked balance - uint256 tokenId = 420; - - vm.prank(address(escrow)); - queue.queueExit(tokenId, address(this)); - - assertFalse(queue.canExit(tokenId)); - - vm.warp(queue.nextExitDate()); - assertFalse(queue.canExit(tokenId)); - - vm.warp(queue.nextExitDate() + 1); - assertTrue(queue.canExit(tokenId)); - } - - // test that changing the cooldown doesn't affect the current ticket holders - function testChangingCooldownDoesntAffectCurrentHolders() public { - // set the lock to start at 1 week - escrow.setMockLockedBalance(100e18, 1 weeks); - - // warp to first week - this is the min lock period as the lock starts week aligned - vm.warp(1 weeks); - - // set a cooldown to 3 days - queue.setCooldown(3 days); - - // warp to almost the end of the upcoming week - this means we wont snap to the next week - // but we can actually test the cooldown - vm.warp(1 weeks + 6 days); - - // queue a ticket - vm.prank(address(escrow)); - queue.queueExit(1, address(this)); - - // check the ticket - Ticket memory ticket = queue.queue(1); - // ticket should be in 3 days from now - assertEq(ticket.exitDate, 2 weeks + 2 days); - - // change the cooldown to 1 day - queue.setCooldown(1 weeks + 1 days); - - // warp to total of 2 days in the future - vm.warp(block.timestamp + 2 days); - - // should still not be able to exit - assertFalse(queue.canExit(1)); - - // warp to 3 days - vm.warp(block.timestamp + 1 days); - - // should not be able to exit - assertFalse(queue.canExit(1)); - - // warp to 3d + 1 - vm.warp(block.timestamp + 1); - - assertTrue(queue.canExit(1)); - - // change the cooldown to 5 days - queue.setCooldown(5 days); - - // should still be able to exit - assertTrue(queue.canExit(1)); - } - - // test can exit and this resets the ticket - function testFuzz_canExitAndResetsTicket(address _holder) public { - vm.assume(_holder != address(0)); - vm.warp(1 weeks); - - uint time = block.timestamp; - // set the lock to start at 1 week - escrow.setMockLockedBalance(100e18, 1 weeks); - - // warp to almost the end of the upcoming week - this means we wont snap to the next week - // but we can actually test the cooldown - vm.warp(2 weeks - 1); - - queue.setCooldown(100); - - uint256 _tokenId = 420; - - vm.prank(address(escrow)); - queue.queueExit(_tokenId, _holder); - - vm.warp(2 weeks + 99); - - vm.expectRevert(CannotExit.selector); - vm.prank(address(escrow)); - queue.exit(_tokenId); - - vm.warp(2 weeks + 100); - - vm.expectEmit(true, false, false, true); - emit Exit(_tokenId, 0); - vm.prank(address(escrow)); - queue.exit(_tokenId); - - assertEq(queue.ticketHolder(_tokenId), address(0)); - assertEq(queue.queue(_tokenId).exitDate, 0); - - vm.expectRevert(CannotExit.selector); - vm.prank(address(escrow)); - queue.exit(_tokenId); - } - - function testUUPSUpgrade() public { - address newImpl = address(new ExitQueue()); - queue.upgradeTo(newImpl); - assertEq(queue.implementation(), newImpl); - - bytes memory err = _authErr(address(1), address(queue), queue.QUEUE_ADMIN_ROLE()); - vm.prank(address(1)); - vm.expectRevert(err); - queue.upgradeTo(newImpl); - } -} diff --git a/test/escrow/queue/ExitQueueBase.sol b/test/escrow/queue/ExitQueueBase.sol deleted file mode 100644 index 6db8434..0000000 --- a/test/escrow/queue/ExitQueueBase.sol +++ /dev/null @@ -1,74 +0,0 @@ -pragma solidity ^0.8.17; - -import {TestHelpers} from "@helpers/TestHelpers.sol"; - -import {console2 as console} from "forge-std/console2.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; -import {DAO, createTestDAO} from "@mocks/MockDAO.sol"; -import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; -import {IExitQueue, ExitQueue} from "@escrow/ExitQueue.sol"; -import {IExitQueueErrorsAndEvents} from "@escrow-interfaces/IExitQueue.sol"; -import {IVotingEscrowEventsStorageErrorsEvents} from "@escrow-interfaces/IVotingEscrowIncreasing.sol"; -import {MockERC20} from "@mocks/MockERC20.sol"; - -contract MockEscrow { - struct LockedBalance { - uint256 amount; - uint256 start; - } - - address public token; - - LockedBalance public lockedBalance = LockedBalance(100e18, 0); - - function setMockLockedBalance(uint256 _amount, uint256 _start) public { - lockedBalance = LockedBalance(_amount, _start); - } - - constructor(address _token) { - token = _token; - } - - function locked(uint tokenid) external view returns (LockedBalance memory) { - if (tokenid == 1) return lockedBalance; - else return LockedBalance(0, 0); - } -} - -contract ExitQueueBase is TestHelpers, IExitQueueErrorsAndEvents { - using ProxyLib for address; - - ExitQueue queue; - MockERC20 token; - MockEscrow escrow; - - function _deployExitQueue( - address _escrow, - uint48 _cooldown, - address _dao, - uint256 _feePercent, - address _clock, - uint48 _minLock - ) public returns (ExitQueue) { - ExitQueue impl = new ExitQueue(); - - bytes memory initCalldata = abi.encodeCall( - ExitQueue.initialize, - (_escrow, _cooldown, _dao, _feePercent, _clock, _minLock) - ); - return ExitQueue(address(impl).deployUUPSProxy(initCalldata)); - } - - function setUp() public virtual override { - super.setUp(); - token = new MockERC20(); - escrow = new MockEscrow(address(token)); - queue = _deployExitQueue(address(escrow), 0, address(dao), 0, address(clock), 1); - dao.grant({ - _who: address(this), - _where: address(queue), - _permissionId: queue.QUEUE_ADMIN_ROLE() - }); - } -} diff --git a/test/escrow/queue/ExitQueueFeeWithdrawals.t.sol b/test/escrow/queue/ExitQueueFeeWithdrawals.t.sol deleted file mode 100644 index a622c66..0000000 --- a/test/escrow/queue/ExitQueueFeeWithdrawals.t.sol +++ /dev/null @@ -1,115 +0,0 @@ -pragma solidity ^0.8.17; - -import {console2 as console} from "forge-std/console2.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; -import {DAO, createTestDAO} from "@mocks/MockDAO.sol"; -import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; -import {IExitQueue, ExitQueue} from "@escrow/ExitQueue.sol"; -import {ExitQueueBase} from "./ExitQueueBase.sol"; - -contract TestExitQueueWithdrawals is ExitQueueBase { - function setUp() public override { - super.setUp(); - - dao.grant({ - _who: address(this), - _where: address(queue), - _permissionId: queue.QUEUE_ADMIN_ROLE() - }); - - dao.grant({ - _who: address(this), - _where: address(queue), - _permissionId: queue.WITHDRAW_ROLE() - }); - } - - // vary the fee percent with a fixed locked amount to check it calculates correctly - function testFuzz_feeCalculatesCorretly(uint16 _fee) public { - if (_fee > 10_000) { - _fee = 10_000; - } - queue.setFeePercent(_fee); - - uint256 expectedFee = (100e18 * uint(_fee)) / 10_000; - - assertEq(queue.calculateFee(1), expectedFee); - } - - // zero lock balance reverts - function testZeroLockedBalanceReverts() public { - queue.setFeePercent(1); // you need this or it'll early return - vm.expectRevert(NoLockBalance.selector); - queue.calculateFee(0); - } - - // cant set fee percent too high - function testFeeTooHighReverts() public { - vm.expectRevert(abi.encodeWithSelector(FeeTooHigh.selector, 10_000)); - queue.setFeePercent(10_001); - } - - // allow only the withdrawer to withdraw - function testOnlyWithdrawerCanWithdraw(address _notWithdrawer) public { - vm.assume(_notWithdrawer != address(this)); - bytes memory data = abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(queue), - _notWithdrawer, - queue.WITHDRAW_ROLE() - ); - vm.expectRevert(data); - vm.prank(_notWithdrawer); - queue.withdraw(0); - } - - // withdraw the erc20 - function testWithdraw() public { - token.mint(address(queue), 100e18); - - queue.withdraw(90e18); - - assertEq(token.balanceOf(address(queue)), 10e18); - assertEq(token.balanceOf(address(this)), 90e18); - } - - /// @dev using 32 bit integers to avoid overflow - function testFuzz_CannotQueueWithIfBeforeMinLock(uint32 _minLock, uint32 _lockStart) public { - // create a lock at a random time - vm.assume(_minLock > 0); - vm.warp(_lockStart); - escrow.setMockLockedBalance(100e18, _lockStart); - - // set the min lock to another random time - queue.setMinLock(_minLock); - - uint256 minLockThreshold = uint256(_minLock) + uint256(_lockStart); - - assertEq(queue.timeToMinLock(1), minLockThreshold); - - vm.startPrank(address(escrow)); - { - if (minLockThreshold > 0) { - // warp to one second before the min lock + start - vm.warp(minLockThreshold - 1); - - bytes memory err = abi.encodeWithSelector( - MinLockNotReached.selector, - 1, - _minLock, - minLockThreshold - ); - // expect revert - vm.expectRevert(err); - queue.queueExit(1, address(this)); - } - - // warp to the min lock + start - expect success - vm.warp(minLockThreshold); - queue.queueExit(1, address(this)); - } - vm.stopPrank(); - } -} diff --git a/test/fork/e2eV2.t.sol b/test/fork/e2eV2.t.sol deleted file mode 100644 index cf92ea5..0000000 --- a/test/fork/e2eV2.t.sol +++ /dev/null @@ -1,1452 +0,0 @@ -pragma solidity ^0.8.17; - -import {Test} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; -import {UUPSUpgradeable as UUPS} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; - -import "../helpers/OSxHelpers.sol"; - -import {Clock} from "@clock/Clock.sol"; -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; -import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; -import {VotingEscrow, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; - -import {GaugesDaoFactory, GaugePluginSet, Deployment} from "src/factory/GaugesDaoFactory.sol"; -import {Deploy, DeploymentParameters} from "script/Deploy.s.sol"; - -interface IERC20Mint is IERC20 { - function mint(address _to, uint256 _amount) external; -} - -contract GhettoMultisig { - function approveCallerToSpendTokenWithID(address _token, uint256 _id) external { - _token.call(abi.encodeWithSignature("approve(address,uint256)", msg.sender, _id)); - } -} - -contract MultisigReceiver is GhettoMultisig { - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) external pure returns (bytes4) { - return this.onERC721Received.selector; - } -} - -/** - * This is an enhanced e2e test that aims to do the following: - * 1. Use factory contract to deploy identically to production - * 2. Setup a test harness for connecting to either fork or local node - * 3. A more robust suite of lifecylce tests for multiple users entering and exiting - * 4. A more robust suite for admininstration of the contracts - * 5. Ability to connect to an existing deployment and test on the real network - */ -contract TestE2EV2 is Test, IWithdrawalQueueErrors, IGaugeVote, IEscrowCurveTokenStorage { - error VotingInactive(); - error OnlyEscrow(); - error GaugeDoesNotExist(address _pool); - error AmountTooSmall(); - error NotApprovedOrOwner(); - error NoVotingPower(); - error NotWhitelisted(); - error NothingToSweep(); - error MinLockNotReached(uint256 tokenId, uint48 minLock, uint48 earliestExitDate); - - uint constant MONTH = 2592000; - - GaugesDaoFactory factory; - - address gauge0 = address(0xc0ffee); - address gauge1 = address(0x1bad1dea); - - // consistent distributor in fork tests - address distributor = address(0x1337); - - // although these exist on the factory a bit easier to access here - // these only reference the FIRST set of contracts, if deploying multiple - // fetch from the factory - SimpleGaugeVoter voter; - QuadraticIncreasingEscrow curve; - ExitQueue queue; - VotingEscrow escrow; - Clock clock; - Lock lock; - Multisig multisig; - DAO dao; - IERC20Mint token; - - MultisigReceiver jordisMultisig; - - address[] signers; - - enum TestMode { - /// @dev Not yet supported - Local, - /// @dev use the factory to deploy the contracts - ForkDeploy, - /// @dev do not deploy the contracts, use the existing ones - ForkExisting - } - - /*/////////////////////////////////////////////////////////////// - Setup - /////////////////////////////////////////////balanceCarlos////////////////*/ - - /// The test here will run in 2 modes: - /// 1. Local Mode (Not yet supported): we deploy the OSx contracts locally using mocks to expedite testing - /// 2. Fork Mode: Deploy (Supported): we pass in the real OSx contracts and deploy via the factory - /// 3. Fork Mode: Existing (Supported): we don't deploy via the factory, we use the existing contract for everything - function setUp() public { - // deploy the deploy script - Deploy deploy = new Deploy(); - - // fetch the deployment parameters - DeploymentParameters memory deploymentParameters = deploy.getDeploymentParameters( - vm.envBool("DEPLOY_AS_PRODUCTION") - ); - - signers = deploy.readMultisigMembers(); - - // any env modifications you need to make to the deployment parameters - // can be done here - if (_getTestMode() == TestMode.Local) { - revert("Local mode not supported yet"); - // setup OSx mocks - // write the addresses - } - - // deploy the contracts via the factory - if (_getTestMode() == TestMode.ForkDeploy) { - // random ens domain - deploymentParameters.voterEnsSubdomain = _hToS( - keccak256(abi.encodePacked("gauges", block.timestamp)) - ); - - // deploy the factory - factory = new GaugesDaoFactory(deploymentParameters); - - // execute the deployment - doing at setup caches it - factory.deployOnce(); - } - // connect to the existing factory to fetch the contract addresses - else if (_getTestMode() == TestMode.ForkExisting) { - address factoryAddress = vm.envAddress("FACTORY"); - if (factoryAddress == address(0)) { - revert("Factory address not set"); - } - factory = GaugesDaoFactory(factoryAddress); - } - - // set our contracts - Deployment memory deployment = factory.getDeployment(); - - // if deploying multiple tokens, you can adjust the index here - GaugePluginSet memory pluginSet = deployment.gaugeVoterPluginSets[0]; - - voter = SimpleGaugeVoter(pluginSet.plugin); - curve = QuadraticIncreasingEscrow(pluginSet.curve); - queue = ExitQueue(pluginSet.exitQueue); - escrow = VotingEscrow(pluginSet.votingEscrow); - clock = Clock(pluginSet.clock); - lock = Lock(pluginSet.nftLock); - multisig = Multisig(deployment.multisigPlugin); - dao = DAO(deployment.dao); - token = IERC20Mint(escrow.token()); - - require(_resolveMintTokens(), "Failed to mint tokens"); - - // increment the block by 1 to ensure we have a new block - // A new multisig requires this after changing settings - - vm.roll(block.number + 1); - } - - /*/////////////////////////////////////////////////////////////// - Admin Tests - //////////////////////////////////////////////////////////////*/ - - /// we have 2 sets of tests here: - /// 1. Administrator tests: these tests the long term administration of the contract - /// and validate that the multisig is able to adjust all the parameters and upgrade the contracts - /// also test critical reverts and unhappy paths - - /// 2. User/Lifecycle tests: these tests ensure that users can enter, vote and exit. - /// We test with a handful of live users to ensure that the system is robust - /// 2a. ensure that both parallel deploys work - /// also test critical reverts and unhappy paths - - // All contracts are UUPS the multisig must be able to upgrade them freely - // We use a simple placeholder contract to test this - function testAdministratorsCanUpgradeAllTheContracts() public { - // deploy the upgraded contract - Upgraded upgraded = new Upgraded(); - - address[] memory protocolContracts = new address[](6); - protocolContracts[0] = address(voter); - protocolContracts[1] = address(curve); - protocolContracts[2] = address(queue); - protocolContracts[3] = address(escrow); - protocolContracts[4] = address(clock); - protocolContracts[5] = address(lock); - - for (uint256 i = 0; i < protocolContracts.length; i++) { - // build the proposal - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0] = IDAO.Action({ - to: protocolContracts[i], - value: 0, - data: abi.encodeCall(UUPS(protocolContracts[i]).upgradeTo, address(upgraded)) - }); - - // build the proposal - uint256 proposalId = _buildMsigProposal(actions); - - // sign and execute - _signExecuteMultisigProposal(proposalId); - } - - // run a manual confirmation against the ABIs to see the upgrade has happened - vm.expectRevert("Non upgradable"); - voter.upgradeTo(address(this)); - - vm.expectRevert("Non upgradable"); - curve.upgradeTo(address(this)); - - vm.expectRevert("Non upgradable"); - queue.upgradeTo(address(this)); - - vm.expectRevert("Non upgradable"); - escrow.upgradeTo(address(this)); - - vm.expectRevert("Non upgradable"); - clock.upgradeTo(address(this)); - - vm.expectRevert("Non upgradable"); - lock.upgradeTo(address(this)); - } - - // test we can pause and unpause - // this applies to the escrow contract and also to the voting contract - function testPauseAndUnPause() public { - // voting - unpause first as deployed paused - { - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0] = IDAO.Action({ - to: address(voter), - value: 0, - data: abi.encodeCall(voter.unpause, ()) - }); - - _buildSignProposal(actions); - - // check - assertEq(voter.paused(), false); - } - - // repause - { - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0] = IDAO.Action({ - to: address(voter), - value: 0, - data: abi.encodeCall(voter.pause, ()) - }); - - _buildSignProposal(actions); - - // check - assertEq(voter.paused(), true); - } - - // escrow - // pause - { - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0] = IDAO.Action({ - to: address(escrow), - value: 0, - data: abi.encodeCall(escrow.pause, ()) - }); - - _buildSignProposal(actions); - - // check - assertEq(escrow.paused(), true); - } - - // the paused escrow should prevent editing locks - vm.expectRevert("Pausable: paused"); - escrow.createLock(100 ether); - - vm.expectRevert("Pausable: paused"); - escrow.createLockFor(100 ether, address(this)); - - vm.expectRevert("Pausable: paused"); - escrow.withdraw(0); - - vm.expectRevert("Pausable: paused"); - escrow.beginWithdrawal(0); - - vm.expectRevert("Pausable: paused"); - escrow.resetVotesAndBeginWithdrawal(0); - - // unpause - { - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0] = IDAO.Action({ - to: address(escrow), - value: 0, - data: abi.encodeCall(escrow.unpause, ()) - }); - - _buildSignProposal(actions); - - // check - assertEq(escrow.paused(), false); - } - } - - // test the DAO setup - it should be root on itself and the factory should not be root - // the multisig should have execute on the DAO - function testDAOSetup() public view { - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(dao), - _permissionId: dao.ROOT_PERMISSION_ID(), - _data: bytes("") - }) - ); - - assertFalse( - dao.isGranted({ - _who: address(factory), - _where: address(dao), - _permissionId: dao.ROOT_PERMISSION_ID(), - _data: bytes("") - }) - ); - - assertTrue( - dao.hasPermission({ - _who: address(multisig), - _where: address(dao), - _permissionId: dao.EXECUTE_PERMISSION_ID(), - _data: bytes("") - }) - ); - - // all the multisig signers should be on the multisig - for (uint i = 0; i < signers.length; i++) { - assertTrue(multisig.isMember(signers[i])); - } - - // check the Dao has the critical roles in all the contracts - - // escrow - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(escrow), - _permissionId: escrow.ESCROW_ADMIN_ROLE(), - _data: bytes("") - }), - "DAO should have escrow admin role" - ); - - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(escrow), - _permissionId: escrow.PAUSER_ROLE(), - _data: bytes("") - }), - "DAO should have escrow pauser role" - ); - - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(escrow), - _permissionId: escrow.SWEEPER_ROLE(), - _data: bytes("") - }), - "DAO should have escrow sweeper role" - ); - - // voter - - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(voter), - _permissionId: voter.GAUGE_ADMIN_ROLE(), - _data: bytes("") - }), - "DAO should have voter gauge admin role" - ); - - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(voter), - _permissionId: voter.UPGRADE_PLUGIN_PERMISSION_ID(), - _data: bytes("") - }), - "DAO should have voter upgrade plugin role" - ); - - // lock - - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(lock), - _permissionId: lock.LOCK_ADMIN_ROLE(), - _data: bytes("") - }), - "DAO should have lock admin role" - ); - - // queue - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(queue), - _permissionId: queue.QUEUE_ADMIN_ROLE(), - _data: bytes("") - }), - "DAO should have queue admin role" - ); - - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(queue), - _permissionId: queue.WITHDRAW_ROLE(), - _data: bytes("") - }), - "DAO should have queue withdraw role" - ); - - // clock - - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(clock), - _permissionId: clock.CLOCK_ADMIN_ROLE(), - _data: bytes("") - }), - "DAO should have clock admin role" - ); - - // curve - assertTrue( - dao.isGranted({ - _who: address(dao), - _where: address(curve), - _permissionId: curve.CURVE_ADMIN_ROLE(), - _data: bytes("") - }), - "DAO should have curve admin role" - ); - } - - /*/////////////////////////////////////////////////////////////// - User/Lifecycle Tests - //////////////////////////////////////////////////////////////*/ - - /// here we walkthrough a user journey with 3 users - /// carlos will have 2 locks, holding both at the same time - /// javi will have 1 lock minted at the same time as user 1 holds both - /// carlos will mint his lock for him - /// jordi will have 1 lock minted after user 1 has exited one of their locks - /// jordi will have a smart contract wallet - /// we will have them create the lock, vote across a couple epochs, and then exit - /// we will also have them attempt to circumvent the system and fail - /// finally we will define one attacker who will attempt to attack the system and fail - - // 3 caballeros - address carlos = address(0xca7105); - address javi = address(0x7af1); - address jordi = address(0x707d1); - - uint balanceCarlos = 1000 ether; - uint balanceJavi = 0 ether; - uint balanceJordi = 1_234 ether; - - uint depositCarlos0 = 250 ether; - uint depositCarlos1 = 500 ether; - uint depositCarlosJavi = 250 ether; - - // 1 attacker - address jordan = address(0x707da); - - uint epochStartTime; - - // warp relative to the start of the test - function goToEpochStartPlus(uint _time) public { - vm.warp(epochStartTime + _time); - } - - function testLifeCycle() public { - // we are in the voting period based on the pinned block so let's wait till the next epoch - uint nextEpoch = clock.epochStartTs(); - vm.warp(nextEpoch); - epochStartTime = block.timestamp; - - // set up labels - { - vm.label(carlos, "Carlos"); - vm.label(javi, "Javi"); - vm.label(jordi, "Jordi"); - vm.label(jordan, "Jordan"); - } - - // first we give the guys each some tokens of the underlying - { - vm.startPrank(distributor); - { - token.transfer(carlos, balanceCarlos); - token.transfer(jordi, balanceJordi); - } - vm.stopPrank(); - } - - // carlos goes first and makes the first deposit, it's at the start of the - // week, so we would expect him to be warm after 1 week - // we wait a couple of days and he makes a deposit for javi - // we expect his warmup to carryover also to the next week - // we expect both of their locks to start accruing voting power on the same day - { - goToEpochStartPlus(1 days); - - vm.startPrank(carlos); - { - token.approve(address(escrow), balanceCarlos); - - // we also check he can't create too small a lock - vm.expectRevert(AmountTooSmall.selector); - escrow.createLock(100 ether - 1); - - escrow.createLock(depositCarlos0); - - goToEpochStartPlus(6 days); - - escrow.createLockFor(depositCarlosJavi, javi); - } - vm.stopPrank(); - - // check carlos has token 1, javi has token 2 - assertEq(lock.ownerOf(1), carlos, "Carlos should own token 1"); - assertEq(lock.ownerOf(2), javi, "Javi should own token 2"); - - // check the token points written - TokenPoint memory tp1_1 = curve.tokenPointHistory(1, 1); - TokenPoint memory tp2_1 = curve.tokenPointHistory(2, 1); - - assertEq(tp1_1.bias, depositCarlos0, "Carlos point 1 should have the correct bias"); - assertEq(tp2_1.bias, depositCarlosJavi, "Javi point should have the correct bias"); - - assertEq( - tp1_1.checkpointTs, - epochStartTime + clock.checkpointInterval(), - "carlos point should have the correct checkpoint" - ); - assertEq( - tp2_1.checkpointTs, - epochStartTime + clock.checkpointInterval(), - "Javi point should have the correct checkpoint" - ); - - assertEq( - tp1_1.writtenTs, - epochStartTime + 1 days, - "Carlos point should have the correct written timestamp" - ); - assertEq( - tp2_1.writtenTs, - epochStartTime + 6 days, - "Javi point should have the correct written timestamp" - ); - - // check the contract has the correct total - assertEq( - escrow.totalLocked(), - depositCarlos0 + depositCarlosJavi, - "Total locked should be the sum of the two deposits" - ); - - // checked the locked balances and the token points - assertEq( - escrow.locked(1).amount, - depositCarlos0, - "Carlos should have the correct amount locked" - ); - assertEq( - escrow.locked(2).amount, - depositCarlosJavi, - "Javi should have the correct amount locked" - ); - - // start date in the future - carlos will not be warm as his lock is not active yet - assertFalse(curve.isWarm(1), "Carlos should not be warm"); - assertFalse(curve.isWarm(2), "Javi should not be warm"); - - assertEq(escrow.votingPower(1), 0, "Carlos should have no voting power"); - assertEq(escrow.votingPower(2), 0, "Javi should have no voting power"); - - assertEq( - escrow.locked(1).start, - escrow.locked(2).start, - "Both locks should start at the same time" - ); - assertEq( - escrow.locked(1).start, - epochStartTime + clock.checkpointInterval(), - "Both locks should start at the next checkpoint" - ); - - // fast forward to the checkpoint interval carlos is warm and has voting power, javi is not - goToEpochStartPlus(clock.checkpointInterval()); - - assertEq( - escrow.votingPower(1), - curve.getBias(0, depositCarlos0), - "Carlos should not yet have voting power" - ); - assertTrue(curve.isWarm(1), "Carlos should not be warm"); - - assertEq(escrow.votingPower(2), 0, "Javi should not have the correct voting power"); - assertFalse(curve.isWarm(2), "Javi should not be warm"); - } - - // carlos can't even begin an exit because of the min lock - { - vm.startPrank(carlos); - { - lock.approve(address(escrow), 1); - - TokenPoint memory tp1_1 = curve.tokenPointHistory(1, 1); - - uint expectedMinLock = tp1_1.checkpointTs + MONTH; - - vm.expectRevert( - abi.encodeWithSelector(MinLockNotReached.selector, 1, MONTH, expectedMinLock) - ); - escrow.beginWithdrawal(1); - } - vm.stopPrank(); - } - - // we fast forward 4 weeks and check the expected balances - { - goToEpochStartPlus(4 weeks); - - // Generalising this is a bit hard... - // we could check a < x < b, but checking x exactly is tedious - } - - // we have carlos make a second deposit and validate that his total voting power is initially unchanged - { - vm.startPrank(carlos); - { - escrow.createLock(depositCarlos1); - } - vm.stopPrank(); - - // check the token points written - TokenPoint memory tp1_2 = curve.tokenPointHistory(3, 1); - - assertEq(tp1_2.bias, depositCarlos1, "Carlos point 2 should have the correct bias"); - assertEq( - tp1_2.checkpointTs, - epochStartTime + 4 weeks + clock.checkpointInterval(), - "Carlos point should have the correct checkpoint" - ); - assertEq( - tp1_2.writtenTs, - epochStartTime + 4 weeks, - "Carlos point should have the correct written timestamp" - ); - - // check the voting power is unchanged (my boi aint warm) - assertEq( - escrow.votingPower(3), - 0, - "Carlos should have no voting power on the second lock" - ); - assertFalse(curve.isWarm(3), "Carlos should not be warm on the second lock"); - - // check the total voting power on the escrow - assertEq( - escrow.totalLocked(), - depositCarlos0 + depositCarlos1 + depositCarlosJavi, - "Total locked should be the sum of the two deposits" - ); - - // calculate elapsed time since we made the first lock - uint timeElapsedSinceFirstLock = block.timestamp - - curve.tokenPointHistory(1, 1).checkpointTs; - - assertEq( - escrow.votingPowerForAccount(carlos), - curve.getBias(timeElapsedSinceFirstLock, depositCarlos0), - "Carlos should only have the first lock active" - ); - } - // we then fast forward 1 week and check that his voting power has increased as expected with the new lock - { - goToEpochStartPlus(5 weeks); - - // calculate elapsed time since we made the first lock - uint timeElapsedSinceFirstLock = block.timestamp - - curve.tokenPointHistory(1, 1).checkpointTs; - - // elased time is zero so should be exactly equal to the bias - assertEq( - escrow.votingPowerForAccount(carlos), - curve.getBias(timeElapsedSinceFirstLock, depositCarlos0) + depositCarlos1, - "Carlos should have extra voting power" - ); - } - - // jordan tries to enter the queue with one of their locks - { - vm.startPrank(jordan); - { - bytes memory erc721ownererr = "ERC721: caller is not token owner or approved"; - for (uint i = 1; i <= 3; i++) { - vm.expectRevert(OnlyEscrow.selector); - queue.queueExit(i, jordan); - - // lingering permissions from carlos' approval - if (i == 1) { - vm.expectRevert(bytes("ERC721: transfer from incorrect owner")); - } else { - vm.expectRevert(erc721ownererr); - } - - escrow.beginWithdrawal(i); - } - } - vm.stopPrank(); - } - - // the guys try to vote but it's paused - { - assertFalse(clock.votingActive(), "Voting should not be active"); - - address[3] memory stakers = [carlos, javi, jordi]; - GaugeVote[] memory votes = new GaugeVote[](0); - for (uint i = 0; i < 3; i++) { - address staker = stakers[i]; - vm.startPrank(staker); - { - vm.expectRevert("Pausable: paused"); - voter.vote(1, votes); - } - vm.stopPrank(); - } - - // same issue when unpaused, votes aren't active - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0] = IDAO.Action({ - to: address(voter), - value: 0, - data: abi.encodeCall(voter.unpause, ()) - }); - _buildSignProposal(actions); - - for (uint i = 0; i < 3; i++) { - address staker = stakers[i]; - vm.startPrank(staker); - { - vm.expectRevert(VotingInactive.selector); - voter.vote(1, votes); - } - vm.stopPrank(); - } - - // move to the next voting window, should not be active at the start of the voting window - goToEpochStartPlus(6 weeks); - - assertFalse(clock.votingActive(), "Voting should not be active"); - - // go one hour further - 1, still no - - goToEpochStartPlus(6 weeks + 1 hours - 1); - - assertFalse(clock.votingActive(), "Voting should not be active"); - - // go one SECOND further, now it should be active - - goToEpochStartPlus(6 weeks + 1 hours); - - assertTrue(clock.votingActive(), "Voting should be active"); - } - - // the guys vote after a gauge is created and the voting is active, they split votes between the gauges - { - // can't frontrun the voting for a non existent gauge - GaugeVote[] memory incorrectVotes = new GaugeVote[](1); - incorrectVotes[0] = GaugeVote({gauge: address(123), weight: 1}); - - vm.startPrank(carlos); - { - vm.expectRevert(abi.encodeWithSelector(GaugeDoesNotExist.selector, address(123))); - voter.vote(1, incorrectVotes); - } - vm.stopPrank(); - - // create the gauge - { - string memory metadataURI0 = "ipfs://gauge0"; - string memory metadataURI1 = "ipfs://gauge1"; - IDAO.Action[] memory actions = new IDAO.Action[](2); - actions[0] = IDAO.Action({ - to: address(voter), - value: 0, - data: abi.encodeWithSelector(voter.createGauge.selector, gauge0, metadataURI0) - }); - actions[1] = IDAO.Action({ - to: address(voter), - value: 0, - data: abi.encodeWithSelector(voter.createGauge.selector, gauge1, metadataURI1) - }); - - _buildSignProposal(actions); - - // gauges should exist and be active - assertTrue(voter.isActive(gauge0), "Gauge 0 should be active"); - assertTrue(voter.isActive(gauge1), "Gauge 1 should be active"); - - // check the metadata - assertEq( - voter.getGauge(gauge0).metadataURI, - metadataURI0, - "Gauge 0 should have the correct metadata" - ); - assertEq( - voter.getGauge(gauge1).metadataURI, - metadataURI1, - "Gauge 1 should have the correct metadata" - ); - } - - // jordan tries voting for someone else and fails - { - GaugeVote[] memory votes = new GaugeVote[](2); - votes[0] = GaugeVote({gauge: gauge0, weight: 1}); - votes[1] = GaugeVote({gauge: gauge1, weight: 1}); - - vm.startPrank(jordan); - { - vm.expectRevert(NotApprovedOrOwner.selector); - voter.vote(1, votes); - - // what about a non-existent id - vm.expectRevert("ERC721: invalid token ID"); - voter.vote(123, votes); - } - vm.stopPrank(); - } - - // the boys vote: carlos votes with multiple and jord with a single - { - GaugeVote[] memory votes = new GaugeVote[](2); - - // carlos is 50 50 - votes[0] = GaugeVote({gauge: gauge0, weight: 1}); - votes[1] = GaugeVote({gauge: gauge1, weight: 1}); - uint[] memory ids = new uint[](2); - ids[0] = 1; - ids[1] = 3; - - vm.startPrank(carlos); - { - voter.voteMultiple(ids, votes); - } - vm.stopPrank(); - - // javi votes for the one gauge - votes = new GaugeVote[](1); - votes[0] = GaugeVote({gauge: gauge0, weight: 1}); - - vm.startPrank(javi); - { - voter.vote(2, votes); - } - vm.stopPrank(); - - // check the votes - we should have all of javi's votes (id 2) for gauge 0 - // carlos' votes should be split between the two gauges - // in total the second gauge should have 50% of the votes of carlos' votes - // and the first 100% of javi's votes + 50% of carlos' votes - assertEq( - voter.votes(1, gauge0), - escrow.votingPower(1) / 2, - "Carlos 1 g 0 should have the correct votes" - ); - assertEq( - voter.votes(1, gauge1), - escrow.votingPower(1) / 2, - "Carlos 1 g 1 should have the correct votes" - ); - assertEq( - voter.votes(2, gauge0), - escrow.votingPower(2), - "Javi should have the correct votes" - ); - assertEq( - voter.votes(3, gauge0), - escrow.votingPower(3) / 2, - "Carlos 3 g 0 should have the correct votes" - ); - assertEq( - voter.votes(3, gauge1), - escrow.votingPower(3) / 2, - "Carlos 3 g 1 should have the correct votes" - ); - - // check the gauge votes - assertEq( - voter.gaugeVotes(gauge0), - escrow.votingPower(2) + escrow.votingPower(1) / 2 + escrow.votingPower(3) / 2 - ); - assertEq( - voter.gaugeVotes(gauge1), - escrow.votingPower(1) / 2 + escrow.votingPower(3) / 2 - ); - } - } - - // jordi create a deposit mid vote and tries to vote - he should have no voting power - { - vm.startPrank(jordi); - { - token.approve(address(escrow), balanceJordi); - - // bad contract first - GhettoMultisig badMultisig = new GhettoMultisig(); - - vm.expectRevert("ERC721: transfer to non ERC721Receiver implementer"); - escrow.createLockFor(balanceJordi, address(badMultisig)); - - // he fixes it - jordisMultisig = new MultisigReceiver(); - - escrow.createLockFor(balanceJordi, address(jordisMultisig)); - - // allow jordi to vote on behalf of his msig - jordisMultisig.approveCallerToSpendTokenWithID(address(lock), 4); - - GaugeVote[] memory votes = new GaugeVote[](2); - votes[0] = GaugeVote({gauge: gauge0, weight: 1}); - votes[1] = GaugeVote({gauge: gauge1, weight: 1}); - - vm.expectRevert(NoVotingPower.selector); - voter.vote(4, votes); - } - vm.stopPrank(); - } - - // javi updates his vote - { - GaugeVote[] memory votes = new GaugeVote[](1); - votes[0] = GaugeVote({gauge: gauge1, weight: 1}); - - vm.startPrank(javi); - { - voter.vote(2, votes); - } - vm.stopPrank(); - - // check the votes - we should have all of javi's votes (id 2) for gauge 1 - // carlos' votes should be split between the two gauges - // in total the second gauge should have 100% of Javi's votes + 50% of the votes of carlos' votes - // and the first 50% of carlos' votes - assertEq( - voter.votes(1, gauge0), - escrow.votingPower(1) / 2, - "Carlos 1 g 0 should have the correct votes" - ); - - assertEq( - voter.votes(1, gauge1), - escrow.votingPower(1) / 2, - "Carlos 1 g 1 should have the correct votes" - ); - - assertEq(voter.votes(2, gauge0), 0, "Javi should have the correct votes"); - - assertEq( - voter.votes(2, gauge1), - escrow.votingPower(2), - "Javi should have the correct votes" - ); - - assertEq( - voter.votes(3, gauge0), - escrow.votingPower(3) / 2, - "Carlos 3 g 0 should have the correct votes" - ); - - assertEq( - voter.votes(3, gauge1), - escrow.votingPower(3) / 2, - "Carlos 3 g 1 should have the correct votes" - ); - - // check the gauge votes - assertEq( - voter.gaugeVotes(gauge0), - escrow.votingPower(1) / 2 + escrow.votingPower(3) / 2 - ); - - assertEq( - voter.gaugeVotes(gauge1), - escrow.votingPower(2) + escrow.votingPower(1) / 2 + escrow.votingPower(3) / 2 - ); - } - - // at distribution we wait - // the guys try and exit but can't - { - // go to 1 hour - 1 second before vote closes - goToEpochStartPlus(7 weeks - 1 hours - 1); - - assertTrue(clock.votingActive(), "Voting should be active"); - - // closes the next second - goToEpochStartPlus(7 weeks - 1 hours); - - assertFalse(clock.votingActive(), "Voting should not be active"); - - // carlos tries to exit - vm.startPrank(carlos); - { - vm.expectRevert(VotingInactive.selector); - escrow.resetVotesAndBeginWithdrawal(1); - - vm.expectRevert(CannotExit.selector); - escrow.beginWithdrawal(1); - } - vm.stopPrank(); - } - - // we wait till voting is over and they begin the exit - carlos does anyhow - // we check he can't exit early and someone can't exit for him - { - goToEpochStartPlus(8 weeks + 1 hours); - - vm.startPrank(carlos); - { - lock.approve(address(escrow), 1); - escrow.resetVotesAndBeginWithdrawal(1); - } - vm.stopPrank(); - - // carlos doesnt have the nft - its in the queue but he has a ticket - assertEq(lock.ownerOf(1), address(escrow), "Carlos should not own the nft"); - assertEq(queue.queue(1).holder, carlos, "Carlos should be in the queue"); - - // expected exit date is: - // now + cooldown given that it crosses the cp boundary - assertEq( - queue.queue(1).exitDate, - epochStartTime + 8 weeks + 1 hours + queue.cooldown(), - "Carlos should be able to exit at the next checkpoint" - ); - - // second user point wrttien - TokenPoint memory tp1_2 = curve.tokenPointHistory(1, 2); - - assertEq(tp1_2.bias, 0, "Carlos point 1_2 should have the correct bias"); - assertEq( - tp1_2.checkpointTs, - epochStartTime + 8 weeks + clock.checkpointInterval(), - "Carlos point should have the correct checkpoint" - ); - assertEq( - tp1_2.writtenTs, - epochStartTime + 8 weeks + 1 hours, - "Carlos point should have the correct written timestamp" - ); - - // he can't exit early - vm.startPrank(carlos); - { - vm.expectRevert(CannotExit.selector); - escrow.withdraw(1); - - // go to cooldown end - goToEpochStartPlus(8 weeks + 1 hours + MONTH); - - // can't exit yet - vm.expectRevert(CannotExit.selector); - escrow.withdraw(1); - - // + 1s he can (1738616401) - goToEpochStartPlus(8 weeks + 1 hours + MONTH + 1); - - escrow.withdraw(1); - } - vm.stopPrank(); - - // he should have his original amount back, minus any fees - assertEq( - token.balanceOf(carlos), - depositCarlos0 - (queue.feePercent() * depositCarlos0) / 10_000, - "Carlos should have the correct balance after exiting" - ); - - // check the total locked - assertEq( - escrow.totalLocked(), - depositCarlos1 + depositCarlosJavi + balanceJordi, - "Total locked should be the sum of the two deposits" - ); - - // there are no fees in our contract - assertEq(token.balanceOf(address(queue)), 0, "Queue should have no fees"); - } - - // governance changes some params: warmup is now one day, cooldown is a week - { - IDAO.Action[] memory actions = new IDAO.Action[](2); - actions[0] = IDAO.Action({ - to: address(curve), - value: 0, - data: abi.encodeWithSelector(curve.setWarmupPeriod.selector, 1 days) - }); - actions[1] = IDAO.Action({ - to: address(queue), - value: 0, - data: abi.encodeWithSelector(queue.setCooldown.selector, 1 weeks) - }); - - _buildSignProposal(actions); - - // check the new params - assertEq(curve.warmupPeriod(), 1 days, "Curve should have the correct warmup period"); - assertEq(queue.cooldown(), 1 weeks, "Queue should have the correct cooldown period"); - } - - // carlos creates a new lock 12 h the window opens, he should be warm tomorrow - { - goToEpochStartPlus(10 weeks - 12 hours); - - vm.startPrank(carlos); - { - token.approve(address(escrow), depositCarlos0); - escrow.createLock(depositCarlos0); - } - vm.stopPrank(); - - // nope - goToEpochStartPlus(10 weeks + 12 hours); - assertFalse(curve.isWarm(5), "Carlos should not be warm"); - - // +1s - goToEpochStartPlus(10 weeks + 12 hours + 1); - assertTrue(curve.isWarm(5), "Carlos should be warm"); - } - - // javi goes for an exit, he should be able to exit in a week - { - goToEpochStartPlus(10 weeks + 3 days); - - vm.startPrank(javi); - { - voter.reset(2); - lock.approve(address(escrow), 2); - escrow.beginWithdrawal(2); - } - vm.stopPrank(); - - // javi doesnt have the nft - its in the queue but he has a ticket - assertEq(lock.ownerOf(2), address(escrow), "Javi should not own the nft"); - assertEq(queue.queue(2).holder, javi, "Javi should be in the queue"); - - // exit date should be 1 week from now - assertEq( - queue.queue(2).exitDate, - epochStartTime + 11 weeks + 3 days, - "Javi should be able to exit in a week" - ); - - // go there + 1 and exit - goToEpochStartPlus(11 weeks + 3 days + 1); - - vm.startPrank(javi); - { - escrow.withdraw(2); - } - vm.stopPrank(); - - // total locked should be carlos' 2 deposits + jordis - assertEq( - escrow.totalLocked(), - depositCarlos0 + depositCarlos1 + balanceJordi, - "Total locked should be the sum of the deposits" - ); - } - - // jordi tries to send his nft to the lock and queue, and can't but he accidentally sends it to the escrow - { - vm.startPrank(jordi); - { - vm.expectRevert(NotWhitelisted.selector); - lock.transferFrom(jordi, address(queue), 4); - - vm.expectRevert(NotWhitelisted.selector); - lock.transferFrom(jordi, address(lock), 4); - - // he sends it to the escrow - lock.transferFrom(address(jordisMultisig), address(escrow), 4); - } - vm.stopPrank(); - - assertEq(lock.ownerOf(4), address(escrow), "Jordi should not own the nft"); - - // he can't get it back now :( - vm.startPrank(jordi); - { - vm.expectRevert("ERC721: caller is not token owner or approved"); - lock.transferFrom(address(escrow), jordi, 4); - } - vm.stopPrank(); - } - - // we recover it from him - { - IDAO.Action[] memory actions = new IDAO.Action[](3); - actions[0] = IDAO.Action({ - to: address(lock), - value: 0, - data: abi.encodeWithSelector(lock.setWhitelisted.selector, address(jordi), true) - }); - actions[1] = IDAO.Action({ - to: address(escrow), - value: 0, - data: abi.encodeWithSelector(escrow.sweepNFT.selector, 4, jordi) - }); - actions[2] = IDAO.Action({ - to: address(lock), - value: 0, - data: abi.encodeWithSelector(lock.setWhitelisted.selector, address(jordi), false) - }); - - _buildSignProposal(actions); - - // check he has it - assertEq(lock.ownerOf(4), jordi, "Jordi should own the nft"); - } - - // jordan convinces the dev team to give him sweeper access and tries to rug all the tokens, he cant - { - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0] = IDAO.Action({ - to: address(dao), - value: 0, - data: abi.encodeCall(dao.grant, (address(escrow), jordan, escrow.SWEEPER_ROLE())) - }); - _buildSignProposal(actions); - - vm.prank(distributor); - token.transfer(jordan, 100 ether); - - vm.startPrank(jordan); - { - vm.expectRevert(NothingToSweep.selector); - escrow.sweep(); - - // however he sends some of his own tokens and can get those out - token.transfer(address(escrow), 100 ether); - - assertEq(token.balanceOf(jordan), 0, "Jordan should have no tokens"); - - escrow.sweep(); - - assertEq(token.balanceOf(jordan), 100 ether, "Jordan should have his tokens"); - } - vm.stopPrank(); - } - - // we get all the guys to exit and unwind their positions - { - // warp to a voting window - must pass the min lock - goToEpochStartPlus(16 weeks + 2 hours); - - vm.startPrank(carlos); - { - lock.approve(address(escrow), 3); - lock.approve(address(escrow), 5); - - escrow.resetVotesAndBeginWithdrawal(3); - escrow.beginWithdrawal(5); - } - vm.stopPrank(); - - vm.startPrank(jordi); - { - lock.approve(address(escrow), 4); - escrow.beginWithdrawal(4); - } - vm.stopPrank(); - - // fast forward a month - goToEpochStartPlus(16 weeks + 2 hours + MONTH); - - // carlos exits - vm.startPrank(carlos); - { - escrow.withdraw(3); - escrow.withdraw(5); - } - vm.stopPrank(); - - // jordi exits - vm.startPrank(jordi); - { - escrow.withdraw(4); - } - vm.stopPrank(); - } - - // we check the end state of the contracts - { - // no votes - assertEq(voter.totalVotingPowerCast(), 0, "Voter should have no votes"); - assertEq(voter.gaugeVotes(gauge0), 0, "Gauge 0 should have no votes"); - assertEq(voter.gaugeVotes(gauge1), 0, "Gauge 1 should have no votes"); - - // no tokens - assertEq(token.balanceOf(address(escrow)), 0, "Escrow should have no tokens"); - assertEq(escrow.totalLocked(), 0, "Escrow should have no locked tokens"); - - // no locks - assertEq(lock.totalSupply(), 0, "Lock should have no tokens"); - } - } - - // run a quick sanity fork test with both real tokens - - // run a test with different parameters - - /*/////////////////////////////////////////////////////////////// - Utils - //////////////////////////////////////////////////////////////*/ - - function _getTestMode() internal view returns (TestMode) { - string memory mode = vm.envString("FORK_TEST_MODE"); - if (keccak256(abi.encodePacked(mode)) == keccak256(abi.encodePacked("fork-deploy"))) { - return TestMode.ForkDeploy; - } else if ( - keccak256(abi.encodePacked(mode)) == keccak256(abi.encodePacked("fork-existing")) - ) { - return TestMode.ForkExisting; - } else if (keccak256(abi.encodePacked(mode)) == keccak256(abi.encodePacked("local"))) { - revert("Local mode not yet supported"); - } else { - revert("Invalid test mode - valid options are fork-deploy, fork-existing, local"); - } - } - - function _hToS(bytes32 _hash) internal pure returns (string memory) { - bytes memory hexString = new bytes(64); - bytes memory alphabet = "0123456789abcdef"; - - for (uint256 i = 0; i < 32; i++) { - hexString[i * 2] = alphabet[uint8(_hash[i] >> 4)]; - hexString[1 + i * 2] = alphabet[uint8(_hash[i] & 0x0f)]; - } - - return string(hexString); - } - - function _buildMsigProposal( - IDAO.Action[] memory actions - ) internal returns (uint256 proposalId) { - // prank the first signer who will create stuff - vm.startPrank(signers[0]); - { - proposalId = multisig.createProposal({ - _metadata: "", - _actions: actions, - _allowFailureMap: 0, - _approveProposal: true, - _tryExecution: false, - _startDate: 0, - _endDate: uint64(block.timestamp) + 3 days - }); - } - vm.stopPrank(); - - return proposalId; - } - - function _signExecuteMultisigProposal(uint256 _proposalId) internal { - // load all the proposers into memory other than the first - - if (signers.length > 1) { - // have them sign - for (uint256 i = 1; i < signers.length; i++) { - vm.startPrank(signers[i]); - { - multisig.approve(_proposalId, false); - } - vm.stopPrank(); - } - } - - // prank the first signer who will create stuff - vm.startPrank(signers[0]); - { - multisig.execute(_proposalId); - } - vm.stopPrank(); - } - - function _buildSignProposal( - IDAO.Action[] memory actions - ) internal returns (uint256 proposalId) { - proposalId = _buildMsigProposal(actions); - _signExecuteMultisigProposal(proposalId); - return proposalId; - } - - /// depending on the network, we need different approaches to mint tokens - function _resolveMintTokens() internal returns (bool) { - // if deploying a mock, mint will be open - try token.mint(address(distributor), 3_000 ether) { - return true; - } catch {} - - // next we just try a good old fashioned find a whale and rug them in the test - address whale = vm.envAddress("TOKEN_TEST_WHALE"); - if (whale == address(0)) { - return false; - } - - vm.prank(whale); - token.transfer(address(distributor), 3_000 ether); - return true; - } -} - -contract Upgraded is UUPS { - function _authorizeUpgrade(address) internal pure override { - revert("Non upgradable"); - } -} diff --git a/test/helpers/OSxHelpers.sol b/test/helpers/OSxHelpers.sol deleted file mode 100644 index f067cf1..0000000 --- a/test/helpers/OSxHelpers.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.8; - -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; -import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; -import {MockDAOFactory, PluginSetupRef} from "@mocks/osx/MockDAOFactory.sol"; -import {MockPluginSetupProcessor} from "@mocks/osx/MockPSP.sol"; - -function bytes32ToAddress(bytes32 _bytes32) pure returns (address) { - return address(uint160(uint256(_bytes32))); -} - -/// @dev call the PSP with an action wrapped in grant/revoke root permissions -function wrapGrantRevokeRoot(DAO _dao, address _psp, IDAO.Action memory _action) view returns (IDAO.Action[] memory) { - IDAO.Action[] memory actions = new IDAO.Action[](3); - actions[0] = IDAO.Action({ - to: address(_dao), - value: 0, - data: abi.encodeCall(_dao.grant, (address(_dao), _psp, _dao.ROOT_PERMISSION_ID())) - }); - - actions[1] = _action; - - actions[2] = IDAO.Action({ - to: address(_dao), - value: 0, - data: abi.encodeCall(_dao.revoke, (address(_dao), _psp, _dao.ROOT_PERMISSION_ID())) - }); - - return actions; -} - -/// @dev call the PSP with actions wrapped in grant/revoke root permissions -function wrapGrantRevokeRoot( - DAO _dao, - address _psp, - IDAO.Action[] memory _actions -) view returns (IDAO.Action[] memory) { - uint8 len = uint8(_actions.length); - IDAO.Action[] memory actions = new IDAO.Action[](len + 2); - actions[0] = IDAO.Action({ - to: address(_dao), - value: 0, - data: abi.encodeCall(_dao.grant, (address(_dao), _psp, _dao.ROOT_PERMISSION_ID())) - }); - - for (uint8 i = 0; i < len; i++) { - actions[i + 1] = _actions[i]; - } - - actions[len + 1] = IDAO.Action({ - to: address(_dao), - value: 0, - data: abi.encodeCall(_dao.revoke, (address(_dao), _psp, _dao.ROOT_PERMISSION_ID())) - }); - - return actions; -} - -// eth address derivation using RLP encoding -// use this if you can't get the address directly and don't have access to CREATE2 -function computeAddress(address target, uint8 nonce) pure returns (address) { - bytes memory data; - if (nonce == 0) { - data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), target, bytes1(0x80)); - } else if (nonce <= 0x7f) { - data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), target, uint8(nonce)); - } else if (nonce <= 0xff) { - data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), target, bytes1(0x81), uint8(nonce)); - } else if (nonce <= 0xffff) { - data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), target, bytes1(0x82), uint16(nonce)); - } else if (nonce <= 0xffffff) { - data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), target, bytes1(0x83), uint24(nonce)); - } else { - data = abi.encodePacked(bytes1(0xda), bytes1(0x94), target, bytes1(0x84), uint32(nonce)); - } - - return bytes32ToAddress(keccak256(data)); -} - -function _mockDAOSettings() pure returns (MockDAOFactory.DAOSettings memory) { - return - MockDAOFactory.DAOSettings({ - trustedForwarder: address(0), - daoURI: "test", - subdomain: "test", - metadata: new bytes(0) - }); -} - -// all this data is unused in the mock -function _mockPluginSetupRef() pure returns (PluginSetupRef memory) { - return - PluginSetupRef({pluginSetupRepo: PluginRepo(address(0)), versionTag: PluginRepo.Tag({release: 1, build: 0})}); -} - -function _mockPrepareInstallationParams( - bytes memory data -) pure returns (MockPluginSetupProcessor.PrepareInstallationParams memory) { - return MockPluginSetupProcessor.PrepareInstallationParams(_mockPluginSetupRef(), data); -} - -function _mockApplyInstallationParams( - address plugin, - PermissionLib.MultiTargetPermission[] memory permissions -) pure returns (MockPluginSetupProcessor.ApplyInstallationParams memory) { - return - MockPluginSetupProcessor.ApplyInstallationParams( - _mockPluginSetupRef(), - plugin, - permissions, - bytes32("helpersHash") - ); -} - -/// we don't use most of the plugin settings in the mock so just ignore it -function _mockPluginSettings(bytes memory data) pure returns (MockDAOFactory.PluginSettings[] memory) { - MockDAOFactory.PluginSettings[] memory settings = new MockDAOFactory.PluginSettings[](1); - settings[0] = MockDAOFactory.PluginSettings({pluginSetupRef: _mockPluginSetupRef(), data: data}); - return settings; -} - -function _mockPrepareUninstallationParams( - IPluginSetup.SetupPayload memory payload -) pure returns (MockPluginSetupProcessor.PrepareUninstallationParams memory) { - return MockPluginSetupProcessor.PrepareUninstallationParams(_mockPluginSetupRef(), payload); -} - -function _mockApplyUninstallationParams( - address plugin, - PermissionLib.MultiTargetPermission[] memory permissions -) pure returns (MockPluginSetupProcessor.ApplyUninstallationParams memory) { - return MockPluginSetupProcessor.ApplyUninstallationParams(plugin, _mockPluginSetupRef(), permissions); -} diff --git a/test/helpers/TestHelpers.sol b/test/helpers/TestHelpers.sol deleted file mode 100644 index 4d2996f..0000000 --- a/test/helpers/TestHelpers.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.17; - -import "forge-std/Test.sol"; - -import {Clock} from "@clock/Clock.sol"; -import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; -import {DAO} from "@mocks/MockDAO.sol"; - -import {createTestDAO} from "@mocks/MockDAO.sol"; - -import {ProxyLib} from "@libs/ProxyLib.sol"; - -contract TestHelpers is Test { - using ProxyLib for address; - - DAO dao; - Clock clock; - - address constant OSX_ANY_ADDR = address(type(uint160).max); - - bytes ownableError = "Ownable: caller is not the owner"; - bytes initializableError = "Initializable: contract is already initialized"; - - function setUp() public virtual { - dao = createTestDAO(address(this)); - clock = _deployClock(address(dao)); - } - - function _deployClock(address _dao) internal returns (Clock) { - address impl = address(new Clock()); - bytes memory initCalldata = abi.encodeWithSelector(Clock.initialize.selector, _dao); - return Clock(impl.deployUUPSProxy(initCalldata)); - } - - function _authErr( - address _caller, - address _contract, - bytes32 _perm - ) internal view returns (bytes memory) { - return - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - _contract, - _caller, - _perm - ); - } -} diff --git a/test/integration/GaugesDaoFactory.sol b/test/integration/GaugesDaoFactory.sol deleted file mode 100644 index 328ba72..0000000 --- a/test/integration/GaugesDaoFactory.sol +++ /dev/null @@ -1,1328 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import "forge-std/Test.sol"; -import {GaugesDaoFactory, Deployment, DeploymentParameters, TokenParameters} from "../../src/factory/GaugesDaoFactory.sol"; -import {MockPluginSetupProcessor} from "@mocks/osx/MockPSP.sol"; -import {MockPluginSetupProcessorMulti} from "@mocks/osx/MockPSPMulti.sol"; -import {MockPluginRepoRegistry} from "@mocks/osx/MockPluginRepoRegistry.sol"; -import {MockDAOFactory} from "@mocks/osx/MockDAOFactory.sol"; -import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; -import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; -import {PluginRepoRegistry} from "@aragon/osx/framework/plugin/repo/PluginRepoRegistry.sol"; -import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; -import {MultisigSetup as MultisigPluginSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol"; -import {SimpleGaugeVoterSetup, VotingEscrow, Clock, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter} from "../../src/voting/SimpleGaugeVoterSetup.sol"; - -contract GaugesDaoFactoryTest is Test { - function test_ShouldStoreTheSettings_1() public { - address[] memory multisigMembers = new address[](13); - for (uint256 i = 0; i < 13; i++) { - multisigMembers[i] = address(uint160(i + 5)); - } - - SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( - address(new SimpleGaugeVoter()), - address(new QuadraticIncreasingEscrow()), - address(new ExitQueue()), - address(new VotingEscrow()), - address(new Clock()), - address(new Lock()) - ); - - MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); - PluginRepoFactory pRefoFactory = new PluginRepoFactory( - PluginRepoRegistry(address(pRepoRegistry)) - ); - MockPluginSetupProcessorMulti psp = new MockPluginSetupProcessorMulti(new address[](0)); - MockDAOFactory daoFactory = new MockDAOFactory(MockPluginSetupProcessor(address(psp))); - - TokenParameters[] memory tokenParameters = new TokenParameters[](2); - tokenParameters[0] = TokenParameters({ - token: address(111), - veTokenName: "Name 1", - veTokenSymbol: "TK1" - }); - tokenParameters[1] = TokenParameters({ - token: address(222), - veTokenName: "Name 2", - veTokenSymbol: "TK2" - }); - - DeploymentParameters memory creationParams = DeploymentParameters({ - // Multisig settings - minApprovals: 2, - multisigMembers: multisigMembers, - // Gauge Voter - tokenParameters: tokenParameters, - feePercent: 50, // 0.5% - warmupPeriod: 1234, - cooldownPeriod: 2345, - minLockDuration: 3456, - minDeposit: 1, - votingPaused: false, - // Standard multisig repo - multisigPluginRepo: PluginRepo(address(5555)), - multisigPluginRelease: 1, - multisigPluginBuild: 2, - // Voter plugin setup and ENS - voterPluginSetup: gaugeVoterPluginSetup, - voterEnsSubdomain: "gauge-ens-subdomain", - // OSx addresses - osxDaoFactory: address(daoFactory), - pluginSetupProcessor: PluginSetupProcessor(address(psp)), - pluginRepoFactory: pRefoFactory - }); - - GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); - - // Check - DeploymentParameters memory actualParams = factory.getDeploymentParameters(); - assertEq(actualParams.minApprovals, creationParams.minApprovals, "Incorrect minApprovals"); - assertEq( - actualParams.multisigMembers.length, - creationParams.multisigMembers.length, - "Incorrect multisigMembers.length" - ); - for (uint256 i = 0; i < 13; i++) { - assertEq(multisigMembers[i], address(uint160(i + 5)), "Incorrect member address"); - } - - assertEq( - actualParams.tokenParameters.length, - creationParams.tokenParameters.length, - "Incorrect tokenParameters.length" - ); - assertEq( - actualParams.tokenParameters[0].token, - creationParams.tokenParameters[0].token, - "Incorrect tokenParameters[0].token" - ); - assertEq( - actualParams.tokenParameters[0].veTokenName, - creationParams.tokenParameters[0].veTokenName, - "Incorrect tokenParameters[0].veTokenName" - ); - assertEq( - actualParams.tokenParameters[0].veTokenSymbol, - creationParams.tokenParameters[0].veTokenSymbol, - "Incorrect tokenParameters[0].veTokenSymbol" - ); - assertEq( - actualParams.tokenParameters[1].token, - creationParams.tokenParameters[1].token, - "Incorrect tokenParameters[1].token" - ); - assertEq( - actualParams.tokenParameters[1].veTokenName, - creationParams.tokenParameters[1].veTokenName, - "Incorrect tokenParameters[1].veTokenName" - ); - assertEq( - actualParams.tokenParameters[1].veTokenSymbol, - creationParams.tokenParameters[1].veTokenSymbol, - "Incorrect tokenParameters[1].veTokenSymbol" - ); - - assertEq(actualParams.feePercent, creationParams.feePercent, "Incorrect feePercent"); - assertEq(actualParams.warmupPeriod, creationParams.warmupPeriod, "Incorrect warmupPeriod"); - assertEq( - actualParams.cooldownPeriod, - creationParams.cooldownPeriod, - "Incorrect cooldownPeriod" - ); - assertEq( - actualParams.minLockDuration, - creationParams.minLockDuration, - "Incorrect minLockDuration" - ); - assertEq(actualParams.votingPaused, creationParams.votingPaused, "Incorrect votingPaused"); - - assertEq( - address(actualParams.multisigPluginRepo), - address(creationParams.multisigPluginRepo), - "Incorrect multisigPluginRepo" - ); - assertEq( - actualParams.multisigPluginRelease, - creationParams.multisigPluginRelease, - "Incorrect multisigPluginRelease" - ); - assertEq( - actualParams.multisigPluginBuild, - creationParams.multisigPluginBuild, - "Incorrect multisigPluginBuild" - ); - assertEq( - address(actualParams.voterPluginSetup), - address(creationParams.voterPluginSetup), - "Incorrect voterPluginSetup" - ); - assertEq( - actualParams.voterEnsSubdomain, - creationParams.voterEnsSubdomain, - "Incorrect voterEnsSubdomain" - ); - - assertEq( - address(actualParams.osxDaoFactory), - address(creationParams.osxDaoFactory), - "Incorrect osxDaoFactory" - ); - assertEq( - address(actualParams.pluginSetupProcessor), - address(creationParams.pluginSetupProcessor), - "Incorrect pluginSetupProcessor" - ); - assertEq( - address(actualParams.pluginRepoFactory), - address(creationParams.pluginRepoFactory), - "Incorrect pluginRepoFactory" - ); - } - - function test_ShouldStoreTheSettings_2() public { - address[] memory multisigMembers = new address[](13); - for (uint256 i = 0; i < 13; i++) { - multisigMembers[i] = address(uint160(i + 10)); - } - - SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( - address(new SimpleGaugeVoter()), - address(new QuadraticIncreasingEscrow()), - address(new ExitQueue()), - address(new VotingEscrow()), - address(new Clock()), - address(new Lock()) - ); - - MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); - PluginRepoFactory pRefoFactory = new PluginRepoFactory( - PluginRepoRegistry(address(pRepoRegistry)) - ); - MockPluginSetupProcessorMulti psp = new MockPluginSetupProcessorMulti(new address[](0)); - MockDAOFactory daoFactory = new MockDAOFactory(MockPluginSetupProcessor(address(psp))); - - TokenParameters[] memory tokenParameters = new TokenParameters[](2); - tokenParameters[0] = TokenParameters({ - token: address(333), - veTokenName: "Name 3", - veTokenSymbol: "TK3" - }); - tokenParameters[1] = TokenParameters({ - token: address(444), - veTokenName: "Name 4", - veTokenSymbol: "TK4" - }); - - DeploymentParameters memory creationParams = DeploymentParameters({ - // Multisig settings - minApprovals: 3, - multisigMembers: multisigMembers, - // Gauge Voter - tokenParameters: tokenParameters, - feePercent: 100, // 100/10k = 1% - warmupPeriod: 7654, - cooldownPeriod: 6543, - minLockDuration: 5432, - minDeposit: 1 ether, - votingPaused: true, - // Standard multisig repo - multisigPluginRepo: PluginRepo(address(3333)), - multisigPluginRelease: 2, - multisigPluginBuild: 10, - // Voter plugin setup and ENS - voterPluginSetup: gaugeVoterPluginSetup, - voterEnsSubdomain: "gauge-ens-subdomain-bis", - // OSx addresses - osxDaoFactory: address(daoFactory), - pluginSetupProcessor: PluginSetupProcessor(address(psp)), - pluginRepoFactory: pRefoFactory - }); - - GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); - - // Check - DeploymentParameters memory actualParams = factory.getDeploymentParameters(); - assertEq(actualParams.minApprovals, creationParams.minApprovals, "Incorrect minApprovals"); - assertEq( - actualParams.multisigMembers.length, - creationParams.multisigMembers.length, - "Incorrect multisigMembers.length" - ); - for (uint256 i = 0; i < 13; i++) { - assertEq(multisigMembers[i], address(uint160(i + 10)), "Incorrect member address"); - } - - assertEq( - actualParams.tokenParameters.length, - creationParams.tokenParameters.length, - "Incorrect tokenParameters.length" - ); - assertEq( - actualParams.tokenParameters[0].token, - creationParams.tokenParameters[0].token, - "Incorrect tokenParameters[0].token" - ); - assertEq( - actualParams.tokenParameters[0].veTokenName, - creationParams.tokenParameters[0].veTokenName, - "Incorrect tokenParameters[0].veTokenName" - ); - assertEq( - actualParams.tokenParameters[0].veTokenSymbol, - creationParams.tokenParameters[0].veTokenSymbol, - "Incorrect tokenParameters[0].veTokenSymbol" - ); - assertEq( - actualParams.tokenParameters[1].token, - creationParams.tokenParameters[1].token, - "Incorrect tokenParameters[1].token" - ); - assertEq( - actualParams.tokenParameters[1].veTokenName, - creationParams.tokenParameters[1].veTokenName, - "Incorrect tokenParameters[1].veTokenName" - ); - assertEq( - actualParams.tokenParameters[1].veTokenSymbol, - creationParams.tokenParameters[1].veTokenSymbol, - "Incorrect tokenParameters[1].veTokenSymbol" - ); - - assertEq(actualParams.feePercent, creationParams.feePercent, "Incorrect feePercent"); - assertEq(actualParams.warmupPeriod, creationParams.warmupPeriod, "Incorrect warmupPeriod"); - assertEq( - actualParams.cooldownPeriod, - creationParams.cooldownPeriod, - "Incorrect cooldownPeriod" - ); - assertEq( - actualParams.minLockDuration, - creationParams.minLockDuration, - "Incorrect minLockDuration" - ); - assertEq(actualParams.votingPaused, creationParams.votingPaused, "Incorrect votingPaused"); - - assertEq( - address(actualParams.multisigPluginRepo), - address(creationParams.multisigPluginRepo), - "Incorrect multisigPluginRepo" - ); - assertEq( - actualParams.multisigPluginRelease, - creationParams.multisigPluginRelease, - "Incorrect multisigPluginRelease" - ); - assertEq( - actualParams.multisigPluginBuild, - creationParams.multisigPluginBuild, - "Incorrect multisigPluginBuild" - ); - assertEq( - address(actualParams.voterPluginSetup), - address(creationParams.voterPluginSetup), - "Incorrect voterPluginSetup" - ); - assertEq( - actualParams.voterEnsSubdomain, - creationParams.voterEnsSubdomain, - "Incorrect voterEnsSubdomain" - ); - - assertEq( - address(actualParams.osxDaoFactory), - address(creationParams.osxDaoFactory), - "Incorrect osxDaoFactory" - ); - assertEq( - address(actualParams.pluginSetupProcessor), - address(creationParams.pluginSetupProcessor), - "Incorrect pluginSetupProcessor" - ); - assertEq( - address(actualParams.pluginRepoFactory), - address(creationParams.pluginRepoFactory), - "Incorrect pluginRepoFactory" - ); - } - - function test_StandardDeployment_1() public { - address[] memory multisigMembers = new address[](13); - for (uint256 i = 0; i < 13; i++) { - multisigMembers[i] = address(uint160(i + 5)); - } - - PluginRepoFactory pRefoFactory = new PluginRepoFactory( - PluginRepoRegistry(address(new MockPluginRepoRegistry())) - ); - - // Publish repo - MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); - PluginRepo multisigPluginRepo = PluginRepoFactory(pRefoFactory) - .createPluginRepoWithFirstVersion( - "multisig-subdomain", - address(multisigPluginSetup), - address(this), - " ", - " " - ); - - SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( - address(new SimpleGaugeVoter()), - address(new QuadraticIncreasingEscrow()), - address(new ExitQueue()), - address(new VotingEscrow()), - address(new Clock()), - address(new Lock()) - ); - - TokenParameters[] memory tokenParameters = new TokenParameters[](2); - tokenParameters[0] = TokenParameters({ - token: address(deployMockERC20("T1", "T1", 18)), - veTokenName: "Name 1", - veTokenSymbol: "TK1" - }); - tokenParameters[1] = TokenParameters({ - token: address(deployMockERC20("T2", "T2", 18)), - veTokenName: "Name 2", - veTokenSymbol: "TK2" - }); - - // PSP with voter plugin setup and multisig - MockPluginSetupProcessorMulti psp; - { - address[] memory pluginSetups = new address[](3); - pluginSetups[0] = address(gaugeVoterPluginSetup); // Token 1 - pluginSetups[1] = address(gaugeVoterPluginSetup); // Token 2 - pluginSetups[2] = address(multisigPluginSetup); - - psp = new MockPluginSetupProcessorMulti(pluginSetups); - } - MockDAOFactory daoFactory = new MockDAOFactory(MockPluginSetupProcessor(address(psp))); - - DeploymentParameters memory creationParams = DeploymentParameters({ - // Multisig settings - minApprovals: 2, - multisigMembers: multisigMembers, - // Gauge Voter - tokenParameters: tokenParameters, - feePercent: 500, - warmupPeriod: 1234, - cooldownPeriod: 2345, - minLockDuration: 3456, - minDeposit: 1, - votingPaused: false, - // Standard multisig repo - multisigPluginRepo: multisigPluginRepo, - multisigPluginRelease: 1, - multisigPluginBuild: 2, - // Voter plugin setup and ENS - voterPluginSetup: gaugeVoterPluginSetup, - voterEnsSubdomain: "gauge-ens-subdomain", - // OSx addresses - osxDaoFactory: address(daoFactory), - pluginSetupProcessor: PluginSetupProcessor(address(psp)), - pluginRepoFactory: pRefoFactory - }); - - GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); - - factory.deployOnce(); - Deployment memory deployment = factory.getDeployment(); - - vm.roll(block.number + 1); // mint one block - - // DAO checks - - assertNotEq(address(deployment.dao), address(0), "Empty DAO field"); - assertEq(deployment.dao.daoURI(), "", "DAO URI should be empty"); - assertEq( - address(deployment.dao.signatureValidator()), - address(0), - "signatureValidator should be empty" - ); - assertEq( - address(deployment.dao.getTrustedForwarder()), - address(0), - "trustedForwarder should be empty" - ); - assertEq( - deployment.dao.hasPermission( - address(deployment.dao), - address(deployment.dao), - deployment.dao.ROOT_PERMISSION_ID(), - bytes("") - ), - true, - "The DAO should be ROOT on itself" - ); - assertEq( - deployment.dao.hasPermission( - address(deployment.dao), - address(deployment.dao), - deployment.dao.UPGRADE_DAO_PERMISSION_ID(), - bytes("") - ), - true, - "The DAO should have UPGRADE_DAO_PERMISSION on itself" - ); - assertEq( - deployment.dao.hasPermission( - address(deployment.dao), - address(deployment.dao), - deployment.dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID(), - bytes("") - ), - true, - "The DAO should have REGISTER_STANDARD_CALLBACK_PERMISSION_ID on itself" - ); - - // Multisig plugin - - assertNotEq(address(deployment.multisigPlugin), address(0), "Empty multisig field"); - assertEq( - deployment.multisigPlugin.lastMultisigSettingsChange(), - block.number - 1, - "Invalid lastMultisigSettingsChange" - ); - assertEq(deployment.multisigPlugin.proposalCount(), 0, "Invalid proposal count"); - assertEq(deployment.multisigPlugin.addresslistLength(), 13, "Invalid addresslistLength"); - for (uint256 i = 0; i < 13; i++) { - assertEq( - deployment.multisigPlugin.isMember(multisigMembers[i]), - true, - "Should be a member" - ); - } - for (uint256 i = 14; i < 50; i++) { - assertEq( - deployment.multisigPlugin.isMember(address(uint160(i + 5))), - false, - "Should not be a member" - ); - } - { - (bool onlyListed, uint16 minApprovals) = deployment.multisigPlugin.multisigSettings(); - - assertEq(onlyListed, true, "Invalid onlyListed"); - assertEq(minApprovals, 2, "Invalid minApprovals"); - } - - // Gauge voter plugin - - assertEq( - deployment.gaugeVoterPluginSets.length, - 2, - "Incorrect gaugeVoterPluginSets length" - ); - // 0 - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].plugin), - address(0), - "Empty plugin address" - ); - assertEq(deployment.gaugeVoterPluginSets[0].plugin.paused(), false, "Should not be paused"); - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].curve), - address(0), - "Empty curve address" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].curve.warmupPeriod(), - 1234, - "Incorrect warmupPeriod" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].exitQueue), - address(0), - "Empty exitQueue address" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].exitQueue.feePercent(), - 500, - "Incorrect feePercent" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].exitQueue.cooldown(), - 2345, - "Incorrect cooldown" - ); - - assertEq(deployment.gaugeVoterPluginSets[0].exitQueue.minLock(), 3456, "Incorrect minLock"); - - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.minDeposit(), - 1, - "Incorrect minDeposit" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].votingEscrow), - address(0), - "Empty votingEscrow address" - ); - - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.token(), - tokenParameters[0].token, - "Incorrect token contract" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.voter(), - address(deployment.gaugeVoterPluginSets[0].plugin), - "Incorrect voter" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.curve(), - address(deployment.gaugeVoterPluginSets[0].curve), - "Incorrect curve" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.queue(), - address(deployment.gaugeVoterPluginSets[0].exitQueue), - "Incorrect queue" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.clock(), - address(deployment.gaugeVoterPluginSets[0].clock), - "Incorrect clock" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.lockNFT(), - address(deployment.gaugeVoterPluginSets[0].nftLock), - "Incorrect lockNFT" - ); - - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].clock), - address(0), - "Empty clock address" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].nftLock), - address(0), - "Empty nftLock address" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].nftLock.name(), - tokenParameters[0].veTokenName, - "Incorrect veTokenName" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].nftLock.symbol(), - tokenParameters[0].veTokenSymbol, - "Incorrect veTokenSymbol" - ); - // 1 - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].plugin), - address(0), - "Empty plugin address" - ); - assertEq(deployment.gaugeVoterPluginSets[1].plugin.paused(), false, "Should not be paused"); - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].curve), - address(0), - "Empty curve address" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].curve.warmupPeriod(), - 1234, - "Incorrect warmupPeriod" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].exitQueue), - address(0), - "Empty exitQueue address" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].exitQueue.feePercent(), - 500, - "Incorrect feePercent" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].exitQueue.cooldown(), - 2345, - "Incorrect cooldown" - ); - assertEq(deployment.gaugeVoterPluginSets[1].exitQueue.minLock(), 3456, "Incorrect minLock"); - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].votingEscrow), - address(0), - "Empty votingEscrow address" - ); - - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.token(), - tokenParameters[1].token, - "Incorrect token contract" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.voter(), - address(deployment.gaugeVoterPluginSets[1].plugin), - "Incorrect voter" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.curve(), - address(deployment.gaugeVoterPluginSets[1].curve), - "Incorrect curve" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.queue(), - address(deployment.gaugeVoterPluginSets[1].exitQueue), - "Incorrect queue" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.clock(), - address(deployment.gaugeVoterPluginSets[1].clock), - "Incorrect clock" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.lockNFT(), - address(deployment.gaugeVoterPluginSets[1].nftLock), - "Incorrect lockNFT" - ); - - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].clock), - address(0), - "Empty clock address" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].nftLock), - address(0), - "Empty nftLock address" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].nftLock.name(), - tokenParameters[1].veTokenName, - "Incorrect veTokenName" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].nftLock.symbol(), - tokenParameters[1].veTokenSymbol, - "Incorrect veTokenSymbol" - ); - - // PLUGIN REPO's - - PluginRepo.Version memory version; - - // Multisig code - version = multisigPluginRepo.getLatestVersion(1); - assertEq( - address(multisigPluginSetup.implementation()), - address(deployment.multisigPlugin.implementation()), - "Invalid multisigPluginSetup" - ); - - // Gauge voter plugin - assertNotEq( - address(deployment.gaugeVoterPluginRepo), - address(0), - "Empty gaugeVoterPluginRepo field" - ); - assertEq(deployment.gaugeVoterPluginRepo.latestRelease(), 1, "Invalid latestRelease"); - assertEq(deployment.gaugeVoterPluginRepo.buildCount(1), 1, "Invalid buildCount"); - version = deployment.gaugeVoterPluginRepo.getLatestVersion(1); - assertEq( - address(version.pluginSetup), - address(gaugeVoterPluginSetup), - "Invalid gaugeVoterPluginSetup" - ); - } - - function test_StandardDeployment_2() public { - address[] memory multisigMembers = new address[](13); - for (uint256 i = 0; i < 13; i++) { - multisigMembers[i] = address(uint160(i + 10)); - } - - PluginRepoFactory pRefoFactory = new PluginRepoFactory( - PluginRepoRegistry(address(new MockPluginRepoRegistry())) - ); - - // Publish repo - MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); - PluginRepo multisigPluginRepo = PluginRepoFactory(pRefoFactory) - .createPluginRepoWithFirstVersion( - "multisig-2-subdomain", - address(multisigPluginSetup), - address(this), - " ", - " " - ); - - SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( - address(new SimpleGaugeVoter()), - address(new QuadraticIncreasingEscrow()), - address(new ExitQueue()), - address(new VotingEscrow()), - address(new Clock()), - address(new Lock()) - ); - - TokenParameters[] memory tokenParameters = new TokenParameters[](3); - tokenParameters[0] = TokenParameters({ - token: address(deployMockERC20("T3", "T3", 18)), - veTokenName: "Name 3", - veTokenSymbol: "TK3" - }); - tokenParameters[1] = TokenParameters({ - token: address(deployMockERC20("T4", "T4", 18)), - veTokenName: "Name 4", - veTokenSymbol: "TK4" - }); - tokenParameters[2] = TokenParameters({ - token: address(deployMockERC20("T5", "T5", 18)), - veTokenName: "Name 5", - veTokenSymbol: "TK5" - }); - - // PSP with voter plugin setup and multisig - MockPluginSetupProcessorMulti psp; - { - address[] memory pluginSetups = new address[](4); - pluginSetups[0] = address(gaugeVoterPluginSetup); // Token 1 - pluginSetups[1] = address(gaugeVoterPluginSetup); // Token 2 - pluginSetups[2] = address(gaugeVoterPluginSetup); // Token 3 - pluginSetups[3] = address(multisigPluginSetup); - - psp = new MockPluginSetupProcessorMulti(pluginSetups); - } - MockDAOFactory daoFactory = new MockDAOFactory(MockPluginSetupProcessor(address(psp))); - - DeploymentParameters memory creationParams = DeploymentParameters({ - // Multisig settings - minApprovals: 5, - multisigMembers: multisigMembers, - // Gauge Voter - tokenParameters: tokenParameters, - feePercent: 20, // 20/10k = 0.2% - warmupPeriod: 5678, - cooldownPeriod: 6789, - minLockDuration: 7890, - minDeposit: 0.1 ether, - votingPaused: true, - // Standard multisig repo - multisigPluginRepo: multisigPluginRepo, - multisigPluginRelease: 1, - multisigPluginBuild: 2, - // Voter plugin setup and ENS - voterPluginSetup: gaugeVoterPluginSetup, - voterEnsSubdomain: "gauge-ens-subdomain", - // OSx addresses - osxDaoFactory: address(daoFactory), - pluginSetupProcessor: PluginSetupProcessor(address(psp)), - pluginRepoFactory: pRefoFactory - }); - - GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); - - factory.deployOnce(); - Deployment memory deployment = factory.getDeployment(); - - vm.roll(block.number + 1); // mint one block - - // DAO checks - - assertNotEq(address(deployment.dao), address(0), "Empty DAO field"); - assertEq(deployment.dao.daoURI(), "", "DAO URI should be empty"); - assertEq( - address(deployment.dao.signatureValidator()), - address(0), - "signatureValidator should be empty" - ); - assertEq( - address(deployment.dao.getTrustedForwarder()), - address(0), - "trustedForwarder should be empty" - ); - assertEq( - deployment.dao.hasPermission( - address(deployment.dao), - address(deployment.dao), - deployment.dao.ROOT_PERMISSION_ID(), - bytes("") - ), - true, - "The DAO should be ROOT on itself" - ); - assertEq( - deployment.dao.hasPermission( - address(deployment.dao), - address(deployment.dao), - deployment.dao.UPGRADE_DAO_PERMISSION_ID(), - bytes("") - ), - true, - "The DAO should have UPGRADE_DAO_PERMISSION on itself" - ); - assertEq( - deployment.dao.hasPermission( - address(deployment.dao), - address(deployment.dao), - deployment.dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID(), - bytes("") - ), - true, - "The DAO should have REGISTER_STANDARD_CALLBACK_PERMISSION_ID on itself" - ); - - // Multisig plugin - - assertNotEq(address(deployment.multisigPlugin), address(0), "Empty multisig field"); - assertEq( - deployment.multisigPlugin.lastMultisigSettingsChange(), - block.number - 1, - "Invalid lastMultisigSettingsChange" - ); - assertEq(deployment.multisigPlugin.proposalCount(), 0, "Invalid proposal count"); - assertEq(deployment.multisigPlugin.addresslistLength(), 13, "Invalid addresslistLength"); - for (uint256 i = 0; i < 13; i++) { - assertEq( - deployment.multisigPlugin.isMember(multisigMembers[i]), - true, - "Should be a member" - ); - } - for (uint256 i = 14; i < 50; i++) { - assertEq( - deployment.multisigPlugin.isMember(address(uint160(i + 10))), - false, - "Should not be a member" - ); - } - { - (bool onlyListed, uint16 minApprovals) = deployment.multisigPlugin.multisigSettings(); - - assertEq(onlyListed, true, "Invalid onlyListed"); - assertEq(minApprovals, 5, "Invalid minApprovals"); - } - - // Gauge voter plugin - - assertEq( - deployment.gaugeVoterPluginSets.length, - 3, - "Incorrect gaugeVoterPluginSets length" - ); - // 0 - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].plugin), - address(0), - "Empty plugin address" - ); - assertEq(deployment.gaugeVoterPluginSets[0].plugin.paused(), true, "Should be paused"); - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].curve), - address(0), - "Empty curve address" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].curve.warmupPeriod(), - 5678, - "Incorrect warmupPeriod" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].exitQueue), - address(0), - "Empty exitQueue address" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].exitQueue.feePercent(), - 20, - "Incorrect feePercent" - ); - - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.minDeposit(), - 0.1 ether, - "Incorrect minDeposit" - ); - - assertEq( - deployment.gaugeVoterPluginSets[0].exitQueue.cooldown(), - 6789, - "Incorrect cooldown" - ); - assertEq(deployment.gaugeVoterPluginSets[0].exitQueue.minLock(), 7890, "Incorrect minLock"); - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].votingEscrow), - address(0), - "Empty votingEscrow address" - ); - - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.token(), - tokenParameters[0].token, - "Incorrect token contract" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.voter(), - address(deployment.gaugeVoterPluginSets[0].plugin), - "Incorrect voter" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.curve(), - address(deployment.gaugeVoterPluginSets[0].curve), - "Incorrect curve" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.queue(), - address(deployment.gaugeVoterPluginSets[0].exitQueue), - "Incorrect queue" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.clock(), - address(deployment.gaugeVoterPluginSets[0].clock), - "Incorrect clock" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].votingEscrow.lockNFT(), - address(deployment.gaugeVoterPluginSets[0].nftLock), - "Incorrect lockNFT" - ); - - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].clock), - address(0), - "Empty clock address" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[0].nftLock), - address(0), - "Empty nftLock address" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].nftLock.name(), - tokenParameters[0].veTokenName, - "Incorrect veTokenName" - ); - assertEq( - deployment.gaugeVoterPluginSets[0].nftLock.symbol(), - tokenParameters[0].veTokenSymbol, - "Incorrect veTokenSymbol" - ); - // 1 - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].plugin), - address(0), - "Empty plugin address" - ); - assertEq(deployment.gaugeVoterPluginSets[1].plugin.paused(), true, "Should be paused"); - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].curve), - address(0), - "Empty curve address" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].curve.warmupPeriod(), - 5678, - "Incorrect warmupPeriod" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].exitQueue), - address(0), - "Empty exitQueue address" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].exitQueue.feePercent(), - 20, - "Incorrect feePercent" - ); - - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.minDeposit(), - 0.1 ether, - "Incorrect minDeposit" - ); - - assertEq( - deployment.gaugeVoterPluginSets[1].exitQueue.cooldown(), - 6789, - "Incorrect cooldown" - ); - assertEq(deployment.gaugeVoterPluginSets[1].exitQueue.minLock(), 7890, "Incorrect minLock"); - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].votingEscrow), - address(0), - "Empty votingEscrow address" - ); - - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.token(), - tokenParameters[1].token, - "Incorrect token contract" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.voter(), - address(deployment.gaugeVoterPluginSets[1].plugin), - "Incorrect voter" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.curve(), - address(deployment.gaugeVoterPluginSets[1].curve), - "Incorrect curve" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.queue(), - address(deployment.gaugeVoterPluginSets[1].exitQueue), - "Incorrect queue" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.clock(), - address(deployment.gaugeVoterPluginSets[1].clock), - "Incorrect clock" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].votingEscrow.lockNFT(), - address(deployment.gaugeVoterPluginSets[1].nftLock), - "Incorrect lockNFT" - ); - - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].clock), - address(0), - "Empty clock address" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].nftLock), - address(0), - "Empty nftLock address" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].nftLock.name(), - tokenParameters[1].veTokenName, - "Incorrect veTokenName" - ); - assertEq( - deployment.gaugeVoterPluginSets[1].nftLock.symbol(), - tokenParameters[1].veTokenSymbol, - "Incorrect veTokenSymbol" - ); - // 2 - assertNotEq( - address(deployment.gaugeVoterPluginSets[2].plugin), - address(0), - "Empty plugin address" - ); - assertEq(deployment.gaugeVoterPluginSets[2].plugin.paused(), true, "Should be paused"); - assertNotEq( - address(deployment.gaugeVoterPluginSets[2].curve), - address(0), - "Empty curve address" - ); - assertEq( - deployment.gaugeVoterPluginSets[2].curve.warmupPeriod(), - 5678, - "Incorrect warmupPeriod" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[2].exitQueue), - address(0), - "Empty exitQueue address" - ); - assertEq( - deployment.gaugeVoterPluginSets[2].exitQueue.feePercent(), - 20, - "Incorrect feePercent" - ); - assertEq( - deployment.gaugeVoterPluginSets[2].votingEscrow.minDeposit(), - 0.1 ether, - "Incorrect minDeposit" - ); - - assertEq( - deployment.gaugeVoterPluginSets[2].exitQueue.cooldown(), - 6789, - "Incorrect cooldown" - ); - assertEq(deployment.gaugeVoterPluginSets[2].exitQueue.minLock(), 7890, "Incorrect minLock"); - assertNotEq( - address(deployment.gaugeVoterPluginSets[2].votingEscrow), - address(0), - "Empty votingEscrow address" - ); - - assertEq( - deployment.gaugeVoterPluginSets[2].votingEscrow.token(), - tokenParameters[2].token, - "Incorrect token contract" - ); - assertEq( - deployment.gaugeVoterPluginSets[2].votingEscrow.voter(), - address(deployment.gaugeVoterPluginSets[2].plugin), - "Incorrect voter" - ); - assertEq( - deployment.gaugeVoterPluginSets[2].votingEscrow.curve(), - address(deployment.gaugeVoterPluginSets[2].curve), - "Incorrect curve" - ); - assertEq( - deployment.gaugeVoterPluginSets[2].votingEscrow.queue(), - address(deployment.gaugeVoterPluginSets[2].exitQueue), - "Incorrect queue" - ); - assertEq( - deployment.gaugeVoterPluginSets[2].votingEscrow.clock(), - address(deployment.gaugeVoterPluginSets[2].clock), - "Incorrect clock" - ); - assertEq( - deployment.gaugeVoterPluginSets[2].votingEscrow.lockNFT(), - address(deployment.gaugeVoterPluginSets[2].nftLock), - "Incorrect lockNFT" - ); - - assertNotEq( - address(deployment.gaugeVoterPluginSets[2].clock), - address(0), - "Empty clock address" - ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[2].nftLock), - address(0), - "Empty nftLock address" - ); - assertEq( - deployment.gaugeVoterPluginSets[2].nftLock.name(), - tokenParameters[2].veTokenName, - "Incorrect veTokenName" - ); - assertEq( - deployment.gaugeVoterPluginSets[2].nftLock.symbol(), - tokenParameters[2].veTokenSymbol, - "Incorrect veTokenSymbol" - ); - - // PLUGIN REPO's - - PluginRepo.Version memory version; - - // Multisig code - version = multisigPluginRepo.getLatestVersion(1); - assertEq( - address(multisigPluginSetup.implementation()), - address(deployment.multisigPlugin.implementation()), - "Invalid multisigPluginSetup" - ); - - // Gauge voter plugin - assertNotEq( - address(deployment.gaugeVoterPluginRepo), - address(0), - "Empty gaugeVoterPluginRepo field" - ); - assertEq(deployment.gaugeVoterPluginRepo.latestRelease(), 1, "Invalid latestRelease"); - assertEq(deployment.gaugeVoterPluginRepo.buildCount(1), 1, "Invalid buildCount"); - version = deployment.gaugeVoterPluginRepo.getLatestVersion(1); - assertEq( - address(version.pluginSetup), - address(gaugeVoterPluginSetup), - "Invalid gaugeVoterPluginSetup" - ); - } - - function test_MultipleDeploysDoNothing() public { - address[] memory multisigMembers = new address[](13); - for (uint256 i = 0; i < 13; i++) { - multisigMembers[i] = address(uint160(i + 10)); - } - - PluginRepoFactory pRefoFactory = new PluginRepoFactory( - PluginRepoRegistry(address(new MockPluginRepoRegistry())) - ); - - // Publish repo - MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); - PluginRepo multisigPluginRepo = PluginRepoFactory(pRefoFactory) - .createPluginRepoWithFirstVersion( - "multisig-2-subdomain", - address(multisigPluginSetup), - address(this), - " ", - " " - ); - - SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( - address(new SimpleGaugeVoter()), - address(new QuadraticIncreasingEscrow()), - address(new ExitQueue()), - address(new VotingEscrow()), - address(new Clock()), - address(new Lock()) - ); - - TokenParameters[] memory tokenParameters = new TokenParameters[](3); - tokenParameters[0] = TokenParameters({ - token: address(deployMockERC20("T3", "T3", 18)), - veTokenName: "Name 3", - veTokenSymbol: "TK3" - }); - tokenParameters[1] = TokenParameters({ - token: address(deployMockERC20("T4", "T4", 18)), - veTokenName: "Name 4", - veTokenSymbol: "TK4" - }); - tokenParameters[2] = TokenParameters({ - token: address(deployMockERC20("T5", "T5", 18)), - veTokenName: "Name 5", - veTokenSymbol: "TK5" - }); - - // PSP with voter plugin setup and multisig - MockPluginSetupProcessorMulti psp; - { - address[] memory pluginSetups = new address[](4); - pluginSetups[0] = address(gaugeVoterPluginSetup); // Token 1 - pluginSetups[1] = address(gaugeVoterPluginSetup); // Token 2 - pluginSetups[2] = address(gaugeVoterPluginSetup); // Token 3 - pluginSetups[3] = address(multisigPluginSetup); - - psp = new MockPluginSetupProcessorMulti(pluginSetups); - } - MockDAOFactory daoFactory = new MockDAOFactory(MockPluginSetupProcessor(address(psp))); - - DeploymentParameters memory creationParams = DeploymentParameters({ - // Multisig settings - minApprovals: 5, - multisigMembers: multisigMembers, - // Gauge Voter - tokenParameters: tokenParameters, - feePercent: 500, // 500/10k = 5% - warmupPeriod: 1234, - cooldownPeriod: 2345, - minLockDuration: 3456, - minDeposit: 10 ether, - votingPaused: false, - // Standard multisig repo - multisigPluginRepo: multisigPluginRepo, - multisigPluginRelease: 1, - multisigPluginBuild: 2, - // Voter plugin setup and ENS - voterPluginSetup: gaugeVoterPluginSetup, - voterEnsSubdomain: "gauge-ens-subdomain", - // OSx addresses - osxDaoFactory: address(daoFactory), - pluginSetupProcessor: PluginSetupProcessor(address(psp)), - pluginRepoFactory: pRefoFactory - }); - - GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); - - // ok - factory.deployOnce(); - - vm.expectRevert(abi.encodeWithSelector(GaugesDaoFactory.AlreadyDeployed.selector)); - factory.deployOnce(); - - vm.expectRevert(abi.encodeWithSelector(GaugesDaoFactory.AlreadyDeployed.selector)); - factory.deployOnce(); - } -} diff --git a/test/libs/SignedFixedPointMathLib.t.sol b/test/libs/SignedFixedPointMathLib.t.sol deleted file mode 100644 index 3316474..0000000 --- a/test/libs/SignedFixedPointMathLib.t.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.0; - -import {Test, console2 as console} from "forge-std/Test.sol"; // Assuming you're using Foundry for testing -import "@libs/SignedFixedPointMathLib.sol"; - -contract SignedFixedPointMathTest is Test { - using SignedFixedPointMath for int256; - - function testToFP() public { - int256 eth = 1 ether; // 1 ETH = 1e18 - assertEq(SignedFixedPointMath.toFP(1), 1e18); - assertEq(SignedFixedPointMath.toFP(eth), eth * 1e18); // 1 ETH scaled up to 1e36 - - int256 quarterEth = eth / 4; // 0.25 ETH - assertEq(SignedFixedPointMath.toFP(quarterEth), (1e18 / 4) * 1e18); // Scaled down to 0.25 - } - - function testFromFP() public { - int256 scaledEth = 1e18; // 1 in FP format - assertEq(SignedFixedPointMath.fromFP(scaledEth), 1); // Should return 1 - - int256 scaledQuarter = 100e18 / 4; // 0.25 in FP format - assertEq(SignedFixedPointMath.fromFP(scaledQuarter), 25); // Should return 0.25 ETH - } - - function testMul() public { - int256 eth = 100 ether; - int256 quarterEth = 1 ether / 4; - - int256 result = eth.mul(quarterEth); - assertEq(SignedFixedPointMath.fromFP(result), 25); // 1 * 0.25 = 0.25 ETH - } - - function testDiv() public { - int256 eth = 1 ether; - int256 quarterEth = eth / 4; - - int256 result = SignedFixedPointMath.div(eth.toFP(), quarterEth.toFP()); - assertEq(SignedFixedPointMath.fromFP(result), 4); // 1 ETH / 0.25 ETH = 4 - } - - function testAdd() public { - int256 eth = 1 ether; - int256 halfEth = eth / 2; - - int256 result = SignedFixedPointMath.add(eth.toFP(), halfEth.toFP()); - assertEq(SignedFixedPointMath.fromFP(result), 1.5 ether); // 1 + 0.5 = 1.5 ETH - } - - function testSub() public { - int256 eth = 1 ether; - int256 halfEth = eth / 2; - - int256 result = SignedFixedPointMath.sub(eth.toFP(), halfEth.toFP()); - assertEq(SignedFixedPointMath.fromFP(result), 0.5 ether); // 1 - 0.5 = 0.5 ETH - } - - function testPow() public { - int256 base = 2; - int256 exp = 3; - - int256 result = SignedFixedPointMath.pow(base.toFP(), exp.toFP()); - assertApproxEqAbs(result, (8 ether), 20); - } - - function testPowZero() public { - int256 base = 0; - int256 exp = 3; - - int256 result = SignedFixedPointMath.pow(base.toFP(), exp.toFP()); - assertEq(result, 0); - } - - function testPowNegativeReverts() public { - int256 base = -1; - int256 exp = 3; - - vm.expectRevert(NegativeBase.selector); - SignedFixedPointMath.pow(base.toFP(), exp.toFP()); - } - - function testComparison() public { - int256 eth = 1 ether; - int256 halfEth = eth / 2; - - assertTrue(SignedFixedPointMath.lt(halfEth.toFP(), eth.toFP())); - assertTrue(SignedFixedPointMath.gt(eth.toFP(), halfEth.toFP())); - } -} diff --git a/test/mocks/MockDAO.sol b/test/mocks/MockDAO.sol deleted file mode 100644 index 6728075..0000000 --- a/test/mocks/MockDAO.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.17; - -import "forge-std/console2.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; - -/// mocks IDAO for the governanceERC20 auth modifier -contract MockDAOSimplePermission { - function hasPermission(address, address, bytes32, bytes calldata) public pure returns (bool) { - // always pass - return true; - } -} - -contract MockDAORevertFallback { - function hasPermission(address, address, bytes32, bytes calldata) public pure returns (bool) { - // always pass - return true; - } - - fallback() external { - revert("MockDAORevertFallback"); - } - - receive() external payable { - revert("MockDAORevertFallback"); - } - - function gimme() public payable { - // do nothing but accept ether - } -} - -/// @notice creates an actual DAO behind a basic proxy for testing -/// @param _initialOwner The initial owner of the DAO having the `ROOT_PERMISSION_ID` permission. -function createTestDAO(address _initialOwner) returns (DAO) { - DAO _dao = DAO(payable(new ERC1967Proxy(address(new DAO()), bytes("")))); - string memory _daoURI = "ipfs://"; - _dao.initialize({ - _metadata: bytes(""), - _initialOwner: _initialOwner, - _trustedForwarder: address(0), - daoURI_: _daoURI - }); - return _dao; -} - -function createTestDAORevertFallback() returns (MockDAORevertFallback) { - return new MockDAORevertFallback(); -} diff --git a/test/mocks/MockERC20.sol b/test/mocks/MockERC20.sol deleted file mode 100644 index c050fc6..0000000 --- a/test/mocks/MockERC20.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; - -contract MockERC20 is ERC20("MockERC20", "MERC") { - function mint(address to, uint256 amount) external { - _mint(to, amount); - } -} - -contract MockERC20Votes is ERC20Votes { - constructor() ERC20("votes", "V") ERC20Permit("votes") {} - - function mint(address to, uint256 amount) external { - _mint(to, amount); - } -} diff --git a/test/mocks/osx/MockDAOFactory.sol b/test/mocks/osx/MockDAOFactory.sol deleted file mode 100644 index 9287032..0000000 --- a/test/mocks/osx/MockDAOFactory.sol +++ /dev/null @@ -1,205 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {IProtocolVersion} from "@aragon/osx/utils/protocol/IProtocolVersion.sol"; -import {ProtocolVersion} from "@aragon/osx/utils/protocol/ProtocolVersion.sol"; -import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; -import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; -import {ProxyLib} from "@libs/ProxyLib.sol"; - -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -// import {PluginRepo} from "../plugin/repo/PluginRepo.sol"; -// import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; -import {hashHelpers, PluginSetupRef} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol"; - -import {MockPluginSetupProcessor as PluginSetupProcessor} from "./MockPSP.sol"; - -// import {DAORegistry} from "./DAORegistry.sol"; - -/// @title MockDAOFactory -/// @dev spoof of DAOFactory with minimal dependencies -/// Best to use with MockPSP: this allows setting of a single setup contract manually to avoid -/// all the other logic, however means you cannot easily deploy the DAO with multiple plugins -contract MockDAOFactory { - using ProxyLib for address; - - /// @notice The DAO base contract, to be used for creating new `DAO`s via `createERC1967Proxy` function. - address public immutable daoBase; - - /// @notice The DAO registry listing the `DAO` contracts created via this contract. - // DAORegistry public immutable daoRegistry; - - /// @notice The plugin setup processor for installing plugins on the newly created `DAO`s. - PluginSetupProcessor public immutable pluginSetupProcessor; - - /// @notice The container for the DAO settings to be set during the DAO initialization. - /// @param trustedForwarder The address of the trusted forwarder required for meta transactions. - /// @param daoURI The DAO uri used with [EIP-4824](https://eips.ethereum.org/EIPS/eip-4824). - /// @param subdomain The ENS subdomain to be registered for the DAO contract. - /// @param metadata The metadata of the DAO. - struct DAOSettings { - address trustedForwarder; - string daoURI; - string subdomain; - bytes metadata; - } - - /// @notice The container with the information required to install a plugin on the DAO. - /// @param pluginSetupRef The `PluginSetupRepo` address of the plugin and the version tag. - /// @param data The bytes-encoded data containing the input parameters for the installation as specified in the plugin's build metadata JSON file. - struct PluginSettings { - PluginSetupRef pluginSetupRef; - bytes data; - } - - /// @notice Thrown if `PluginSettings` array is empty, and no plugin is provided. - error NoPluginProvided(); - - /// @notice lifted from dao registry - event DAORegistered(address indexed dao, address indexed creator, string subdomain); - - /// @notice The constructor setting the registry and plugin setup processor and creating the base contracts for the factory. - // / @param _registry The DAO registry to register the DAO by its name. - /// @param _pluginSetupProcessor The address of PluginSetupProcessor. - constructor(PluginSetupProcessor _pluginSetupProcessor) { - pluginSetupProcessor = _pluginSetupProcessor; - - daoBase = address(new DAO()); - } - - // /// @notice Checks if this or the parent contract supports an interface by its ID. - // /// @param _interfaceId The ID of the interface. - // /// @return Returns `true` if the interface is supported. - // function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { - // return - // _interfaceId == type(IProtocolVersion).interfaceId || - // super.supportsInterface(_interfaceId); - // } - - /// @notice Creates a new DAO, registers it on the DAO registry, and installs a list of plugins via the plugin setup processor. - /// @param _daoSettings The DAO settings to be set during the DAO initialization. - /// @param _pluginSettings The array containing references to plugins and their settings to be installed after the DAO has been created. - function createDao( - DAOSettings calldata _daoSettings, - PluginSettings[] calldata _pluginSettings - ) external returns (DAO createdDao) { - // Check if no plugin is provided. - if (_pluginSettings.length == 0) { - revert NoPluginProvided(); - } - - // Create DAO. - createdDao = _createDAO(_daoSettings); - - // MOCK: SKIP - // // Register DAO. - // daoRegistry.register(createdDao, msg.sender, _daoSettings.subdomain); - emit DAORegistered(address(createdDao), msg.sender, _daoSettings.subdomain); - - // Get Permission IDs - bytes32 rootPermissionID = createdDao.ROOT_PERMISSION_ID(); - bytes32 applyInstallationPermissionID = pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID(); - - // Grant the temporary permissions. - // Grant Temporarly `ROOT_PERMISSION` to `pluginSetupProcessor`. - createdDao.grant(address(createdDao), address(pluginSetupProcessor), rootPermissionID); - - // Grant Temporarly `APPLY_INSTALLATION_PERMISSION` on `pluginSetupProcessor` to this `DAOFactory`. - createdDao.grant(address(pluginSetupProcessor), address(this), applyInstallationPermissionID); - - // Install plugins on the newly created DAO. - for (uint256 i; i < _pluginSettings.length; ++i) { - // Prepare plugin. - (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = pluginSetupProcessor - .prepareInstallation( - address(createdDao), - PluginSetupProcessor.PrepareInstallationParams( - _pluginSettings[i].pluginSetupRef, - _pluginSettings[i].data - ) - ); - - // Apply plugin. - pluginSetupProcessor.applyInstallation( - address(createdDao), - PluginSetupProcessor.ApplyInstallationParams( - _pluginSettings[i].pluginSetupRef, - plugin, - preparedSetupData.permissions, - hashHelpers(preparedSetupData.helpers) - ) - ); - } - - // Set the rest of DAO's permissions. - _setDAOPermissions(createdDao); - - // Revoke the temporarly granted permissions. - // Revoke Temporarly `ROOT_PERMISSION` from `pluginSetupProcessor`. - createdDao.revoke(address(createdDao), address(pluginSetupProcessor), rootPermissionID); - - // Revoke `APPLY_INSTALLATION_PERMISSION` on `pluginSetupProcessor` from this `DAOFactory` . - createdDao.revoke(address(pluginSetupProcessor), address(this), applyInstallationPermissionID); - - // Revoke Temporarly `ROOT_PERMISSION_ID` from `pluginSetupProcessor` that implicitly granted to this `DaoFactory` - // at the create dao step `address(this)` being the initial owner of the new created DAO. - createdDao.revoke(address(createdDao), address(this), rootPermissionID); - } - - /// @notice Deploys a new DAO `ERC1967` proxy, and initialize it with this contract as the intial owner. - /// @param _daoSettings The trusted forwarder, name and metadata hash of the DAO it creates. - function _createDAO(DAOSettings calldata _daoSettings) internal returns (DAO dao) { - // Create a DAO proxy and initialize it with the DAOFactory (`address(this)`) as the initial owner. - // As a result, the DAOFactory has `ROOT_PERMISSION_`ID` permission on the DAO. - dao = DAO( - payable( - daoBase.deployUUPSProxy( - abi.encodeCall( - DAO.initialize, - (_daoSettings.metadata, address(this), _daoSettings.trustedForwarder, _daoSettings.daoURI) - ) - ) - ) - ); - } - - /// @notice Sets the required permissions for the new DAO. - /// @param _dao The DAO instance just created. - function _setDAOPermissions(DAO _dao) internal { - // set permissionIds on the dao itself. - PermissionLib.SingleTargetPermission[] memory items = new PermissionLib.SingleTargetPermission[](5); - - // Grant DAO all the permissions required - items[0] = PermissionLib.SingleTargetPermission( - PermissionLib.Operation.Grant, - address(_dao), - _dao.ROOT_PERMISSION_ID() - ); - items[1] = PermissionLib.SingleTargetPermission( - PermissionLib.Operation.Grant, - address(_dao), - _dao.UPGRADE_DAO_PERMISSION_ID() - ); - items[2] = PermissionLib.SingleTargetPermission( - PermissionLib.Operation.Grant, - address(_dao), - _dao.SET_TRUSTED_FORWARDER_PERMISSION_ID() - ); - items[3] = PermissionLib.SingleTargetPermission( - PermissionLib.Operation.Grant, - address(_dao), - _dao.SET_METADATA_PERMISSION_ID() - ); - items[4] = PermissionLib.SingleTargetPermission( - PermissionLib.Operation.Grant, - address(_dao), - _dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID() - ); - - _dao.applySingleTargetPermissions(address(_dao), items); - } -} diff --git a/test/mocks/osx/MockPSP.sol b/test/mocks/osx/MockPSP.sol deleted file mode 100644 index 619c192..0000000 --- a/test/mocks/osx/MockPSP.sol +++ /dev/null @@ -1,748 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.17; - -import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; - -import {DAO, IDAO} from "@aragon/osx/core/dao/DAO.sol"; -import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; -import {PluginUUPSUpgradeable} from "@aragon/osx/core/plugin/PluginUUPSUpgradeable.sol"; -import {IPlugin} from "@aragon/osx/core/plugin/IPlugin.sol"; - -// import {PluginRepoRegistry} from "@aragon/osx/framework/plugin/repo/PluginRepoRegistry.sol"; -import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; - -import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; -import {PluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol"; -import {PluginSetupRef, hashHelpers, hashPermissions, _getPreparedSetupId, _getAppliedSetupId, _getPluginInstallationId, PreparationType} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol"; - -/// @title PluginSetupProcessor -/// @author Aragon Association - 2022-2023 -/// @notice This contract processes the preparation and application of plugin setups (installation, update, uninstallation) on behalf of a requesting DAO. -/// @dev This contract is temporarily granted the `ROOT_PERMISSION_ID` permission on the applying DAO and therefore is highly security critical. -contract MockPluginSetupProcessor { - using ERC165Checker for address; - - /// @notice The ID of the permission required to call the `applyInstallation` function. - bytes32 public constant APPLY_INSTALLATION_PERMISSION_ID = - keccak256("APPLY_INSTALLATION_PERMISSION"); - - /// @notice The ID of the permission required to call the `applyUpdate` function. - bytes32 public constant APPLY_UPDATE_PERMISSION_ID = keccak256("APPLY_UPDATE_PERMISSION"); - - /// @notice The ID of the permission required to call the `applyUninstallation` function. - bytes32 public constant APPLY_UNINSTALLATION_PERMISSION_ID = - keccak256("APPLY_UNINSTALLATION_PERMISSION"); - - /// @notice The hash obtained from the bytes-encoded empty array to be used for UI updates being required to submit an empty permission array. - /// @dev The hash is computed via `keccak256(abi.encode([]))`. - bytes32 private constant EMPTY_ARRAY_HASH = - 0x569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd; - - /// @notice The hash obtained from the bytes-encoded zero value. - /// @dev The hash is computed via `keccak256(abi.encode(0))`. - bytes32 private constant ZERO_BYTES_HASH = - 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; - - /// @notice A struct containing information related to plugin setups that have been applied. - /// @param blockNumber The block number at which the `applyInstallation`, `applyUpdate` or `applyUninstallation` was executed. - /// @param currentAppliedSetupId The current setup id that plugin holds. Needed to confirm that `prepareUpdate` or `prepareUninstallation` happens for the plugin's current/valid dependencies. - /// @param preparedSetupIdToBlockNumber The mapping between prepared setup IDs and block numbers at which `prepareInstallation`, `prepareUpdate` or `prepareUninstallation` was executed. - struct PluginState { - uint256 blockNumber; - bytes32 currentAppliedSetupId; - mapping(bytes32 => uint256) preparedSetupIdToBlockNumber; - } - - /// @notice A mapping between the plugin installation ID (obtained from the DAO and plugin address) and the plugin state information. - /// @dev This variable is public on purpose to allow future versions to access and migrate the storage. - mapping(bytes32 => PluginState) public states; - - /// @notice The struct containing the parameters for the `prepareInstallation` function. - /// @param pluginSetupRef The reference to the plugin setup to be used for the installation. - /// @param data The bytes-encoded data containing the input parameters for the installation preparation as specified in the corresponding ABI on the version's metadata. - struct PrepareInstallationParams { - PluginSetupRef pluginSetupRef; - bytes data; - } - - /// @notice The struct containing the parameters for the `applyInstallation` function. - /// @param pluginSetupRef The reference to the plugin setup used for the installation. - /// @param plugin The address of the plugin contract to be installed. - /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the DAO. - /// @param helpersHash The hash of helpers that were deployed in `prepareInstallation`. This helps to derive the setup ID. - struct ApplyInstallationParams { - PluginSetupRef pluginSetupRef; - address plugin; - PermissionLib.MultiTargetPermission[] permissions; - bytes32 helpersHash; - } - - /// @notice The struct containing the parameters for the `prepareUpdate` function. - /// @param currentVersionTag The tag of the current plugin version to update from. - /// @param newVersionTag The tag of the new plugin version to update to. - /// @param pluginSetupRepo The plugin setup repository address on which the plugin exists. - /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. - /// This includes the bytes-encoded data containing the input parameters for the update preparation as specified in the corresponding ABI on the version's metadata. - struct PrepareUpdateParams { - PluginRepo.Tag currentVersionTag; - PluginRepo.Tag newVersionTag; - PluginRepo pluginSetupRepo; - IPluginSetup.SetupPayload setupPayload; - } - - /// @notice The struct containing the parameters for the `applyUpdate` function. - /// @param plugin The address of the plugin contract to be updated. - /// @param pluginSetupRef The reference to the plugin setup used for the update. - /// @param initData The encoded data (function selector and arguments) to be provided to `upgradeToAndCall`. - /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the DAO. - /// @param helpersHash The hash of helpers that were deployed in `prepareUpdate`. This helps to derive the setup ID. - struct ApplyUpdateParams { - address plugin; - PluginSetupRef pluginSetupRef; - bytes initData; - PermissionLib.MultiTargetPermission[] permissions; - bytes32 helpersHash; - } - - /// @notice The struct containing the parameters for the `prepareUninstallation` function. - /// @param pluginSetupRef The reference to the plugin setup to be used for the uninstallation. - /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. - /// This includes the bytes-encoded data containing the input parameters for the uninstallation preparation as specified in the corresponding ABI on the version's metadata. - struct PrepareUninstallationParams { - PluginSetupRef pluginSetupRef; - IPluginSetup.SetupPayload setupPayload; - } - - /// @notice The struct containing the parameters for the `applyInstallation` function. - /// @param plugin The address of the plugin contract to be uninstalled. - /// @param pluginSetupRef The reference to the plugin setup used for the uninstallation. - /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcess. - struct ApplyUninstallationParams { - address plugin; - PluginSetupRef pluginSetupRef; - PermissionLib.MultiTargetPermission[] permissions; - } - - /// @notice The plugin repo registry listing the `PluginRepo` contracts versioning the `PluginSetup` contracts. - // PluginRepoRegistry public repoRegistry; - - /// @notice Thrown if a setup is unauthorized and cannot be applied because of a missing permission of the associated DAO. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param caller The address (EOA or contract) that requested the application of a setup on the associated DAO. - /// @param permissionId The permission identifier. - /// @dev This is thrown if the `APPLY_INSTALLATION_PERMISSION_ID`, `APPLY_UPDATE_PERMISSION_ID`, or APPLY_UNINSTALLATION_PERMISSION_ID is missing. - error SetupApplicationUnauthorized(address dao, address caller, bytes32 permissionId); - - /// @notice Thrown if a plugin is not upgradeable. - /// @param plugin The address of the plugin contract. - error PluginNonupgradeable(address plugin); - - /// @notice Thrown if the upgrade of an `UUPSUpgradeable` proxy contract (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)) failed. - /// @param proxy The address of the proxy. - /// @param implementation The address of the implementation contract. - /// @param initData The initialization data to be passed to the upgradeable plugin contract via `upgradeToAndCall`. - error PluginProxyUpgradeFailed(address proxy, address implementation, bytes initData); - - /// @notice Thrown if a contract does not support the `IPlugin` interface. - /// @param plugin The address of the contract. - error IPluginNotSupported(address plugin); - - /// @notice Thrown if a plugin repository does not exist on the plugin repo registry. - error PluginRepoNonexistent(); - - /// @notice Thrown if a plugin setup was already prepared indicated by the prepared setup ID. - /// @param preparedSetupId The prepared setup ID. - error SetupAlreadyPrepared(bytes32 preparedSetupId); - - /// @notice Thrown if a prepared setup ID is not eligible to be applied. This can happen if another setup has been already applied or if the setup wasn't prepared in the first place. - /// @param preparedSetupId The prepared setup ID. - error SetupNotApplicable(bytes32 preparedSetupId); - - /// @notice Thrown if the update version is invalid. - /// @param currentVersionTag The tag of the current version to update from. - /// @param newVersionTag The tag of the new version to update to. - error InvalidUpdateVersion(PluginRepo.Tag currentVersionTag, PluginRepo.Tag newVersionTag); - - /// @notice Thrown if plugin is already installed and one tries to prepare or apply install on it. - error PluginAlreadyInstalled(); - - /// @notice Thrown if the applied setup ID resulting from the supplied setup payload does not match with the current applied setup ID. - /// @param currentAppliedSetupId The current applied setup ID with which the data in the supplied payload must match. - /// @param appliedSetupId The applied setup ID obtained from the data in the supplied setup payload. - error InvalidAppliedSetupId(bytes32 currentAppliedSetupId, bytes32 appliedSetupId); - - /// @notice Emitted with a prepared plugin installation to store data relevant for the application step. - /// @param sender The sender that prepared the plugin installation. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param preparedSetupId The prepared setup ID obtained from the supplied data. - /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. - /// @param versionTag The version tag of the plugin setup of the prepared installation. - /// @param data The bytes-encoded data containing the input parameters for the preparation as specified in the corresponding ABI on the version's metadata. - /// @param plugin The address of the plugin contract. - /// @param preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. - event InstallationPrepared( - address indexed sender, - address indexed dao, - bytes32 preparedSetupId, - PluginRepo indexed pluginSetupRepo, - PluginRepo.Tag versionTag, - bytes data, - address plugin, - IPluginSetup.PreparedSetupData preparedSetupData - ); - - /// @notice Emitted after a plugin installation was applied. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param plugin The address of the plugin contract. - /// @param preparedSetupId The prepared setup ID. - /// @param appliedSetupId The applied setup ID. - event InstallationApplied( - address indexed dao, - address indexed plugin, - bytes32 preparedSetupId, - bytes32 appliedSetupId - ); - - /// @notice Emitted with a prepared plugin update to store data relevant for the application step. - /// @param sender The sender that prepared the plugin update. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param preparedSetupId The prepared setup ID. - /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. - /// @param versionTag The version tag of the plugin setup of the prepared update. - /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. - /// @param preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. - /// @param initData The initialization data to be passed to the upgradeable plugin contract. - event UpdatePrepared( - address indexed sender, - address indexed dao, - bytes32 preparedSetupId, - PluginRepo indexed pluginSetupRepo, - PluginRepo.Tag versionTag, - IPluginSetup.SetupPayload setupPayload, - IPluginSetup.PreparedSetupData preparedSetupData, - bytes initData - ); - - /// @notice Emitted after a plugin update was applied. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param plugin The address of the plugin contract. - /// @param preparedSetupId The prepared setup ID. - /// @param appliedSetupId The applied setup ID. - event UpdateApplied( - address indexed dao, - address indexed plugin, - bytes32 preparedSetupId, - bytes32 appliedSetupId - ); - - /// @notice Emitted with a prepared plugin uninstallation to store data relevant for the application step. - /// @param sender The sender that prepared the plugin uninstallation. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param preparedSetupId The prepared setup ID. - /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. - /// @param versionTag The version tag of the plugin to used for install preparation. - /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. - /// @param permissions The list of multi-targeted permission operations to be applied to the installing DAO. - event UninstallationPrepared( - address indexed sender, - address indexed dao, - bytes32 preparedSetupId, - PluginRepo indexed pluginSetupRepo, - PluginRepo.Tag versionTag, - IPluginSetup.SetupPayload setupPayload, - PermissionLib.MultiTargetPermission[] permissions - ); - - /// @notice Emitted after a plugin installation was applied. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param plugin The address of the plugin contract. - /// @param preparedSetupId The prepared setup ID. - event UninstallationApplied( - address indexed dao, - address indexed plugin, - bytes32 preparedSetupId - ); - - /// @notice A modifier to check if a caller has the permission to apply a prepared setup. - /// @param _dao The address of the DAO. - /// @param _permissionId The permission identifier. - modifier canApply(address _dao, bytes32 _permissionId) { - _canApply(_dao, _permissionId); - _; - } - - /// @notice Constructs the plugin setup processor by setting the associated plugin repo registry. - // / @param _repoRegistry The plugin repo registry contract. - constructor(address _setup) { - queueSetup(_setup); - // repoRegistry = _repoRegistry; - } - - address[] setups; - - function queueSetup(address _setup) public { - setups.push(_setup); - } - - function popSetup() internal returns (address) { - require(setups.length > 0, "No setups queued"); - address _setup = setups[setups.length - 1]; - setups.pop(); - return _setup; - } - - /// @notice Prepares the installation of a plugin. - /// @param _dao The address of the installing DAO. - /// @param _params The struct containing the parameters for the `prepareInstallation` function. - /// @return plugin The prepared plugin contract address. - /// @return preparedSetupData The data struct containing the array of helper contracts and permissions that the setup has prepared. - function prepareInstallation( - address _dao, - PrepareInstallationParams calldata _params - ) external returns (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) { - // PluginRepo pluginSetupRepo = _params.pluginSetupRef.pluginSetupRepo; - - // // Check that the plugin repository exists on the plugin repo registry. - // if (!repoRegistry.entries(address(pluginSetupRepo))) { - // revert PluginRepoNonexistent(); - // } - - // // reverts if not found - // PluginRepo.Version memory version = pluginSetupRepo.getVersion( - // _params.pluginSetupRef.versionTag - // ); - - // Prepare the installation - (plugin, preparedSetupData) = PluginSetup(popSetup()).prepareInstallation( - _dao, - _params.data - ); - - // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, plugin); - - // bytes32 preparedSetupId = _getPreparedSetupId( - // _params.pluginSetupRef, - // hashPermissions(preparedSetupData.permissions), - // hashHelpers(preparedSetupData.helpers), - // bytes(""), - // PreparationType.Installation - // ); - - // PluginState storage pluginState = states[pluginInstallationId]; - - // // Check if this plugin is already installed. - // if (pluginState.currentAppliedSetupId != bytes32(0)) { - // revert PluginAlreadyInstalled(); - // } - - // // Check if this setup has already been prepared before and is pending. - // if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { - // revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); - // } - - // pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; - - // emit InstallationPrepared({ - // sender: msg.sender, - // dao: _dao, - // preparedSetupId: preparedSetupId, - // pluginSetupRepo: pluginSetupRepo, - // versionTag: _params.pluginSetupRef.versionTag, - // data: _params.data, - // plugin: plugin, - // preparedSetupData: preparedSetupData - // }); - - return (plugin, preparedSetupData); - } - - /// @notice Applies the permissions of a prepared installation to a DAO. - /// @param _dao The address of the installing DAO. - /// @param _params The struct containing the parameters for the `applyInstallation` function. - function applyInstallation( - address _dao, - ApplyInstallationParams calldata _params - ) external canApply(_dao, APPLY_INSTALLATION_PERMISSION_ID) { - // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); - - // PluginState storage pluginState = states[pluginInstallationId]; - - // bytes32 preparedSetupId = _getPreparedSetupId( - // _params.pluginSetupRef, - // hashPermissions(_params.permissions), - // _params.helpersHash, - // bytes(""), - // PreparationType.Installation - // ); - - // // Check if this plugin is already installed. - // if (pluginState.currentAppliedSetupId != bytes32(0)) { - // revert PluginAlreadyInstalled(); - // } - - // validatePreparedSetupId(pluginInstallationId, preparedSetupId); - - // bytes32 appliedSetupId = _getAppliedSetupId(_params.pluginSetupRef, _params.helpersHash); - - // pluginState.currentAppliedSetupId = appliedSetupId; - // pluginState.blockNumber = block.number; - - // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the installing DAO. - if (_params.permissions.length > 0) { - DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); - } - - // emit InstallationApplied({ - // dao: _dao, - // plugin: _params.plugin, - // preparedSetupId: preparedSetupId, - // appliedSetupId: appliedSetupId - // }); - } - - /// @notice Prepares the update of an UUPS upgradeable plugin. - /// @param _dao The address of the DAO For which preparation of update happens. - /// @param _params The struct containing the parameters for the `prepareUpdate` function. - /// @return initData The initialization data to be passed to upgradeable contracts when the update is applied - /// @return preparedSetupData The data struct containing the array of helper contracts and permissions that the setup has prepared. - /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the update is prepared for. - function prepareUpdate( - address _dao, - PrepareUpdateParams calldata _params - ) - external - returns (bytes memory initData, IPluginSetup.PreparedSetupData memory preparedSetupData) - { - if ( - _params.currentVersionTag.release != _params.newVersionTag.release || - _params.currentVersionTag.build >= _params.newVersionTag.build - ) { - revert InvalidUpdateVersion({ - currentVersionTag: _params.currentVersionTag, - newVersionTag: _params.newVersionTag - }); - } - - bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.setupPayload.plugin); - - PluginState storage pluginState = states[pluginInstallationId]; - - bytes32 currentHelpersHash = hashHelpers(_params.setupPayload.currentHelpers); - - bytes32 appliedSetupId = _getAppliedSetupId( - PluginSetupRef(_params.currentVersionTag, _params.pluginSetupRepo), - currentHelpersHash - ); - - // The following check implicitly confirms that plugin is currently installed. - // Otherwise, `currentAppliedSetupId` would not be set. - if (pluginState.currentAppliedSetupId != appliedSetupId) { - revert InvalidAppliedSetupId({ - currentAppliedSetupId: pluginState.currentAppliedSetupId, - appliedSetupId: appliedSetupId - }); - } - - PluginRepo.Version memory currentVersion = _params.pluginSetupRepo.getVersion( - _params.currentVersionTag - ); - - PluginRepo.Version memory newVersion = _params.pluginSetupRepo.getVersion( - _params.newVersionTag - ); - - bytes32 preparedSetupId; - - // If the current and new plugin setup are identical, this is an UI update. - // In this case, the permission hash is set to the empty array hash and the `prepareUpdate` call is skipped to avoid side effects. - if (currentVersion.pluginSetup == newVersion.pluginSetup) { - preparedSetupId = _getPreparedSetupId( - PluginSetupRef(_params.newVersionTag, _params.pluginSetupRepo), - EMPTY_ARRAY_HASH, - currentHelpersHash, - bytes(""), - PreparationType.Update - ); - - // Because UI updates do not change the plugin functionality, the array of helpers - // associated with this plugin version `preparedSetupData.helpers` and being returned must - // equal `_params.setupPayload.currentHelpers` returned by the previous setup step (installation or update ) - // that this update is transitioning from. - preparedSetupData.helpers = _params.setupPayload.currentHelpers; - } else { - // Check that plugin is `PluginUUPSUpgradable`. - if (!_params.setupPayload.plugin.supportsInterface(type(IPlugin).interfaceId)) { - revert IPluginNotSupported({plugin: _params.setupPayload.plugin}); - } - if (IPlugin(_params.setupPayload.plugin).pluginType() != IPlugin.PluginType.UUPS) { - revert PluginNonupgradeable({plugin: _params.setupPayload.plugin}); - } - - // Prepare the update. - (initData, preparedSetupData) = PluginSetup(newVersion.pluginSetup).prepareUpdate( - _dao, - _params.currentVersionTag.build, - _params.setupPayload - ); - - preparedSetupId = _getPreparedSetupId( - PluginSetupRef(_params.newVersionTag, _params.pluginSetupRepo), - hashPermissions(preparedSetupData.permissions), - hashHelpers(preparedSetupData.helpers), - initData, - PreparationType.Update - ); - } - - // Check if this setup has already been prepared before and is pending. - if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { - revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); - } - - pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; - - // Avoid stack too deep. - emitPrepareUpdateEvent(_dao, preparedSetupId, _params, preparedSetupData, initData); - - return (initData, preparedSetupData); - } - - /// @notice Applies the permissions of a prepared update of an UUPS upgradeable proxy contract to a DAO. - /// @param _dao The address of the updating DAO. - /// @param _params The struct containing the parameters for the `applyInstallation` function. - function applyUpdate( - address _dao, - ApplyUpdateParams calldata _params - ) external canApply(_dao, APPLY_UPDATE_PERMISSION_ID) { - bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); - - PluginState storage pluginState = states[pluginInstallationId]; - - bytes32 preparedSetupId = _getPreparedSetupId( - _params.pluginSetupRef, - hashPermissions(_params.permissions), - _params.helpersHash, - _params.initData, - PreparationType.Update - ); - - validatePreparedSetupId(pluginInstallationId, preparedSetupId); - - bytes32 appliedSetupId = _getAppliedSetupId(_params.pluginSetupRef, _params.helpersHash); - - pluginState.blockNumber = block.number; - pluginState.currentAppliedSetupId = appliedSetupId; - - PluginRepo.Version memory version = _params.pluginSetupRef.pluginSetupRepo.getVersion( - _params.pluginSetupRef.versionTag - ); - - address currentImplementation = PluginUUPSUpgradeable(_params.plugin).implementation(); - address newImplementation = PluginSetup(version.pluginSetup).implementation(); - - if (currentImplementation != newImplementation) { - _upgradeProxy(_params.plugin, newImplementation, _params.initData); - } - - // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the updating DAO. - if (_params.permissions.length > 0) { - DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); - } - - emit UpdateApplied({ - dao: _dao, - plugin: _params.plugin, - preparedSetupId: preparedSetupId, - appliedSetupId: appliedSetupId - }); - } - - /// @notice Prepares the uninstallation of a plugin. - /// @param _dao The address of the uninstalling DAO. - /// @param _params The struct containing the parameters for the `prepareUninstallation` function. - /// @return permissions The list of multi-targeted permission operations to be applied to the uninstalling DAO. - /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the uninstallation was prepared for. - function prepareUninstallation( - address _dao, - PrepareUninstallationParams calldata _params - ) external returns (PermissionLib.MultiTargetPermission[] memory permissions) { - // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.setupPayload.plugin); - - // PluginState storage pluginState = states[pluginInstallationId]; - - // bytes32 appliedSetupId = _getAppliedSetupId( - // _params.pluginSetupRef, - // hashHelpers(_params.setupPayload.currentHelpers) - // ); - - // if (pluginState.currentAppliedSetupId != appliedSetupId) { - // revert InvalidAppliedSetupId({ - // currentAppliedSetupId: pluginState.currentAppliedSetupId, - // appliedSetupId: appliedSetupId - // }); - // } - - // PluginRepo.Version memory version = _params.pluginSetupRef.pluginSetupRepo.getVersion( - // _params.pluginSetupRef.versionTag - // ); - - permissions = PluginSetup(popSetup()).prepareUninstallation(_dao, _params.setupPayload); - - // bytes32 preparedSetupId = _getPreparedSetupId( - // _params.pluginSetupRef, - // hashPermissions(permissions), - // ZERO_BYTES_HASH, - // bytes(""), - // PreparationType.Uninstallation - // ); - - // // Check if this setup has already been prepared before and is pending. - // if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { - // revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); - // } - - // pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; - - // emit UninstallationPrepared({ - // sender: msg.sender, - // dao: _dao, - // preparedSetupId: preparedSetupId, - // pluginSetupRepo: _params.pluginSetupRef.pluginSetupRepo, - // versionTag: _params.pluginSetupRef.versionTag, - // setupPayload: _params.setupPayload, - // permissions: permissions - // }); - } - - /// @notice Applies the permissions of a prepared uninstallation to a DAO. - /// @param _dao The address of the DAO. - /// @param _dao The address of the uninstalling DAO. - /// @param _params The struct containing the parameters for the `applyUninstallation` function. - /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the uninstallation was prepared for. - function applyUninstallation( - address _dao, - ApplyUninstallationParams calldata _params - ) external canApply(_dao, APPLY_UNINSTALLATION_PERMISSION_ID) { - // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); - - // PluginState storage pluginState = states[pluginInstallationId]; - - // bytes32 preparedSetupId = _getPreparedSetupId( - // _params.pluginSetupRef, - // hashPermissions(_params.permissions), - // ZERO_BYTES_HASH, - // bytes(""), - // PreparationType.Uninstallation - // ); - - // validatePreparedSetupId(pluginInstallationId, preparedSetupId); - - // // Since the plugin is uninstalled, only the current block number must be updated. - // pluginState.blockNumber = block.number; - // pluginState.currentAppliedSetupId = bytes32(0); - - // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the uninstalling DAO. - if (_params.permissions.length > 0) { - DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); - } - - // emit UninstallationApplied({ - // dao: _dao, - // plugin: _params.plugin, - // preparedSetupId: preparedSetupId - // }); - } - - /// @notice Validates that a setup ID can be applied for `applyInstallation`, `applyUpdate`, or `applyUninstallation`. - /// @param pluginInstallationId The plugin installation ID obtained from the hash of `abi.encode(daoAddress, pluginAddress)`. - /// @param preparedSetupId The prepared setup ID to be validated. - /// @dev If the block number stored in `states[pluginInstallationId].blockNumber` exceeds the one stored in `pluginState.preparedSetupIdToBlockNumber[preparedSetupId]`, the prepared setup with `preparedSetupId` is outdated and not applicable anymore. - function validatePreparedSetupId( - bytes32 pluginInstallationId, - bytes32 preparedSetupId - ) public view { - PluginState storage pluginState = states[pluginInstallationId]; - if (pluginState.blockNumber >= pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { - revert SetupNotApplicable({preparedSetupId: preparedSetupId}); - } - } - - /// @notice Upgrades a UUPS upgradeable proxy contract (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). - /// @param _proxy The address of the proxy. - /// @param _implementation The address of the implementation contract. - /// @param _initData The initialization data to be passed to the upgradeable plugin contract via `upgradeToAndCall`. - function _upgradeProxy( - address _proxy, - address _implementation, - bytes memory _initData - ) private { - if (_initData.length > 0) { - try - PluginUUPSUpgradeable(_proxy).upgradeToAndCall(_implementation, _initData) - {} catch Error(string memory reason) { - revert(reason); - } catch (bytes memory /*lowLevelData*/) { - revert PluginProxyUpgradeFailed({ - proxy: _proxy, - implementation: _implementation, - initData: _initData - }); - } - } else { - try PluginUUPSUpgradeable(_proxy).upgradeTo(_implementation) {} catch Error( - string memory reason - ) { - revert(reason); - } catch (bytes memory /*lowLevelData*/) { - revert PluginProxyUpgradeFailed({ - proxy: _proxy, - implementation: _implementation, - initData: _initData - }); - } - } - } - - /// @notice Checks if a caller can apply a setup. The caller can be either the DAO to which the plugin setup is applied to or another account to which the DAO has granted the respective permission. - /// @param _dao The address of the applying DAO. - /// @param _permissionId The permission ID. - function _canApply(address _dao, bytes32 _permissionId) private view { - if ( - msg.sender != _dao && - !DAO(payable(_dao)).hasPermission(address(this), msg.sender, _permissionId, bytes("")) - ) { - revert SetupApplicationUnauthorized({ - dao: _dao, - caller: msg.sender, - permissionId: _permissionId - }); - } - } - - /// @notice A helper to emit the `UpdatePrepared` event from the supplied, structured data. - /// @param _dao The address of the updating DAO. - /// @param _preparedSetupId The prepared setup ID. - /// @param _params The struct containing the parameters for the `prepareUpdate` function. - /// @param _preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. - /// @param _initData The initialization data to be passed to upgradeable contracts when the update is applied - /// @dev This functions exists to avoid stack-too-deep errors. - function emitPrepareUpdateEvent( - address _dao, - bytes32 _preparedSetupId, - PrepareUpdateParams calldata _params, - IPluginSetup.PreparedSetupData memory _preparedSetupData, - bytes memory _initData - ) private { - emit UpdatePrepared({ - sender: msg.sender, - dao: _dao, - preparedSetupId: _preparedSetupId, - pluginSetupRepo: _params.pluginSetupRepo, - versionTag: _params.newVersionTag, - setupPayload: _params.setupPayload, - preparedSetupData: _preparedSetupData, - initData: _initData - }); - } -} diff --git a/test/mocks/osx/MockPSPMulti.sol b/test/mocks/osx/MockPSPMulti.sol deleted file mode 100644 index fab1480..0000000 --- a/test/mocks/osx/MockPSPMulti.sol +++ /dev/null @@ -1,744 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.17; - -import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import {DAO, IDAO} from "@aragon/osx/core/dao/DAO.sol"; -import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; -import {PluginUUPSUpgradeable} from "@aragon/osx/core/plugin/PluginUUPSUpgradeable.sol"; -import {IPlugin} from "@aragon/osx/core/plugin/IPlugin.sol"; -import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; -import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; -import {PluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol"; -import {PluginSetupRef, hashHelpers, hashPermissions, _getPreparedSetupId, _getAppliedSetupId, _getPluginInstallationId, PreparationType} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol"; - -/// @title PluginSetupProcessor -/// @author Aragon Association - 2022-2023 -/// @notice This contract processes the preparation and application of plugin setups (installation, update, uninstallation) on behalf of a requesting DAO. -/// @dev This contract is temporarily granted the `ROOT_PERMISSION_ID` permission on the applying DAO and therefore is highly security critical. -contract MockPluginSetupProcessorMulti { - using ERC165Checker for address; - - /// @notice The ID of the permission required to call the `applyInstallation` function. - bytes32 public constant APPLY_INSTALLATION_PERMISSION_ID = - keccak256("APPLY_INSTALLATION_PERMISSION"); - - /// @notice The ID of the permission required to call the `applyUpdate` function. - bytes32 public constant APPLY_UPDATE_PERMISSION_ID = keccak256("APPLY_UPDATE_PERMISSION"); - - /// @notice The ID of the permission required to call the `applyUninstallation` function. - bytes32 public constant APPLY_UNINSTALLATION_PERMISSION_ID = - keccak256("APPLY_UNINSTALLATION_PERMISSION"); - - /// @notice The hash obtained from the bytes-encoded empty array to be used for UI updates being required to submit an empty permission array. - /// @dev The hash is computed via `keccak256(abi.encode([]))`. - bytes32 private constant EMPTY_ARRAY_HASH = - 0x569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd; - - /// @notice The hash obtained from the bytes-encoded zero value. - /// @dev The hash is computed via `keccak256(abi.encode(0))`. - bytes32 private constant ZERO_BYTES_HASH = - 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; - - /// @notice A struct containing information related to plugin setups that have been applied. - /// @param blockNumber The block number at which the `applyInstallation`, `applyUpdate` or `applyUninstallation` was executed. - /// @param currentAppliedSetupId The current setup id that plugin holds. Needed to confirm that `prepareUpdate` or `prepareUninstallation` happens for the plugin's current/valid dependencies. - /// @param preparedSetupIdToBlockNumber The mapping between prepared setup IDs and block numbers at which `prepareInstallation`, `prepareUpdate` or `prepareUninstallation` was executed. - struct PluginState { - uint256 blockNumber; - bytes32 currentAppliedSetupId; - mapping(bytes32 => uint256) preparedSetupIdToBlockNumber; - } - - /// @notice A mapping between the plugin installation ID (obtained from the DAO and plugin address) and the plugin state information. - /// @dev This variable is public on purpose to allow future versions to access and migrate the storage. - mapping(bytes32 => PluginState) public states; - - /// @notice The struct containing the parameters for the `prepareInstallation` function. - /// @param pluginSetupRef The reference to the plugin setup to be used for the installation. - /// @param data The bytes-encoded data containing the input parameters for the installation preparation as specified in the corresponding ABI on the version's metadata. - struct PrepareInstallationParams { - PluginSetupRef pluginSetupRef; - bytes data; - } - - /// @notice The struct containing the parameters for the `applyInstallation` function. - /// @param pluginSetupRef The reference to the plugin setup used for the installation. - /// @param plugin The address of the plugin contract to be installed. - /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the DAO. - /// @param helpersHash The hash of helpers that were deployed in `prepareInstallation`. This helps to derive the setup ID. - struct ApplyInstallationParams { - PluginSetupRef pluginSetupRef; - address plugin; - PermissionLib.MultiTargetPermission[] permissions; - bytes32 helpersHash; - } - - /// @notice The struct containing the parameters for the `prepareUpdate` function. - /// @param currentVersionTag The tag of the current plugin version to update from. - /// @param newVersionTag The tag of the new plugin version to update to. - /// @param pluginSetupRepo The plugin setup repository address on which the plugin exists. - /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. - /// This includes the bytes-encoded data containing the input parameters for the update preparation as specified in the corresponding ABI on the version's metadata. - struct PrepareUpdateParams { - PluginRepo.Tag currentVersionTag; - PluginRepo.Tag newVersionTag; - PluginRepo pluginSetupRepo; - IPluginSetup.SetupPayload setupPayload; - } - - /// @notice The struct containing the parameters for the `applyUpdate` function. - /// @param plugin The address of the plugin contract to be updated. - /// @param pluginSetupRef The reference to the plugin setup used for the update. - /// @param initData The encoded data (function selector and arguments) to be provided to `upgradeToAndCall`. - /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the DAO. - /// @param helpersHash The hash of helpers that were deployed in `prepareUpdate`. This helps to derive the setup ID. - struct ApplyUpdateParams { - address plugin; - PluginSetupRef pluginSetupRef; - bytes initData; - PermissionLib.MultiTargetPermission[] permissions; - bytes32 helpersHash; - } - - /// @notice The struct containing the parameters for the `prepareUninstallation` function. - /// @param pluginSetupRef The reference to the plugin setup to be used for the uninstallation. - /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. - /// This includes the bytes-encoded data containing the input parameters for the uninstallation preparation as specified in the corresponding ABI on the version's metadata. - struct PrepareUninstallationParams { - PluginSetupRef pluginSetupRef; - IPluginSetup.SetupPayload setupPayload; - } - - /// @notice The struct containing the parameters for the `applyInstallation` function. - /// @param plugin The address of the plugin contract to be uninstalled. - /// @param pluginSetupRef The reference to the plugin setup used for the uninstallation. - /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcess. - struct ApplyUninstallationParams { - address plugin; - PluginSetupRef pluginSetupRef; - PermissionLib.MultiTargetPermission[] permissions; - } - - /// @notice The plugin repo registry listing the `PluginRepo` contracts versioning the `PluginSetup` contracts. - // PluginRepoRegistry public repoRegistry; - - /// @notice Thrown if a setup is unauthorized and cannot be applied because of a missing permission of the associated DAO. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param caller The address (EOA or contract) that requested the application of a setup on the associated DAO. - /// @param permissionId The permission identifier. - /// @dev This is thrown if the `APPLY_INSTALLATION_PERMISSION_ID`, `APPLY_UPDATE_PERMISSION_ID`, or APPLY_UNINSTALLATION_PERMISSION_ID is missing. - error SetupApplicationUnauthorized(address dao, address caller, bytes32 permissionId); - - /// @notice Thrown if a plugin is not upgradeable. - /// @param plugin The address of the plugin contract. - error PluginNonupgradeable(address plugin); - - /// @notice Thrown if the upgrade of an `UUPSUpgradeable` proxy contract (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)) failed. - /// @param proxy The address of the proxy. - /// @param implementation The address of the implementation contract. - /// @param initData The initialization data to be passed to the upgradeable plugin contract via `upgradeToAndCall`. - error PluginProxyUpgradeFailed(address proxy, address implementation, bytes initData); - - /// @notice Thrown if a contract does not support the `IPlugin` interface. - /// @param plugin The address of the contract. - error IPluginNotSupported(address plugin); - - /// @notice Thrown if a plugin repository does not exist on the plugin repo registry. - error PluginRepoNonexistent(); - - /// @notice Thrown if a plugin setup was already prepared indicated by the prepared setup ID. - /// @param preparedSetupId The prepared setup ID. - error SetupAlreadyPrepared(bytes32 preparedSetupId); - - /// @notice Thrown if a prepared setup ID is not eligible to be applied. This can happen if another setup has been already applied or if the setup wasn't prepared in the first place. - /// @param preparedSetupId The prepared setup ID. - error SetupNotApplicable(bytes32 preparedSetupId); - - /// @notice Thrown if the update version is invalid. - /// @param currentVersionTag The tag of the current version to update from. - /// @param newVersionTag The tag of the new version to update to. - error InvalidUpdateVersion(PluginRepo.Tag currentVersionTag, PluginRepo.Tag newVersionTag); - - /// @notice Thrown if plugin is already installed and one tries to prepare or apply install on it. - error PluginAlreadyInstalled(); - - /// @notice Thrown if the applied setup ID resulting from the supplied setup payload does not match with the current applied setup ID. - /// @param currentAppliedSetupId The current applied setup ID with which the data in the supplied payload must match. - /// @param appliedSetupId The applied setup ID obtained from the data in the supplied setup payload. - error InvalidAppliedSetupId(bytes32 currentAppliedSetupId, bytes32 appliedSetupId); - - /// @notice Emitted with a prepared plugin installation to store data relevant for the application step. - /// @param sender The sender that prepared the plugin installation. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param preparedSetupId The prepared setup ID obtained from the supplied data. - /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. - /// @param versionTag The version tag of the plugin setup of the prepared installation. - /// @param data The bytes-encoded data containing the input parameters for the preparation as specified in the corresponding ABI on the version's metadata. - /// @param plugin The address of the plugin contract. - /// @param preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. - event InstallationPrepared( - address indexed sender, - address indexed dao, - bytes32 preparedSetupId, - PluginRepo indexed pluginSetupRepo, - PluginRepo.Tag versionTag, - bytes data, - address plugin, - IPluginSetup.PreparedSetupData preparedSetupData - ); - - /// @notice Emitted after a plugin installation was applied. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param plugin The address of the plugin contract. - /// @param preparedSetupId The prepared setup ID. - /// @param appliedSetupId The applied setup ID. - event InstallationApplied( - address indexed dao, - address indexed plugin, - bytes32 preparedSetupId, - bytes32 appliedSetupId - ); - - /// @notice Emitted with a prepared plugin update to store data relevant for the application step. - /// @param sender The sender that prepared the plugin update. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param preparedSetupId The prepared setup ID. - /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. - /// @param versionTag The version tag of the plugin setup of the prepared update. - /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. - /// @param preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. - /// @param initData The initialization data to be passed to the upgradeable plugin contract. - event UpdatePrepared( - address indexed sender, - address indexed dao, - bytes32 preparedSetupId, - PluginRepo indexed pluginSetupRepo, - PluginRepo.Tag versionTag, - IPluginSetup.SetupPayload setupPayload, - IPluginSetup.PreparedSetupData preparedSetupData, - bytes initData - ); - - /// @notice Emitted after a plugin update was applied. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param plugin The address of the plugin contract. - /// @param preparedSetupId The prepared setup ID. - /// @param appliedSetupId The applied setup ID. - event UpdateApplied( - address indexed dao, - address indexed plugin, - bytes32 preparedSetupId, - bytes32 appliedSetupId - ); - - /// @notice Emitted with a prepared plugin uninstallation to store data relevant for the application step. - /// @param sender The sender that prepared the plugin uninstallation. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param preparedSetupId The prepared setup ID. - /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. - /// @param versionTag The version tag of the plugin to used for install preparation. - /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. - /// @param permissions The list of multi-targeted permission operations to be applied to the installing DAO. - event UninstallationPrepared( - address indexed sender, - address indexed dao, - bytes32 preparedSetupId, - PluginRepo indexed pluginSetupRepo, - PluginRepo.Tag versionTag, - IPluginSetup.SetupPayload setupPayload, - PermissionLib.MultiTargetPermission[] permissions - ); - - /// @notice Emitted after a plugin installation was applied. - /// @param dao The address of the DAO to which the plugin belongs. - /// @param plugin The address of the plugin contract. - /// @param preparedSetupId The prepared setup ID. - event UninstallationApplied( - address indexed dao, - address indexed plugin, - bytes32 preparedSetupId - ); - - /// @notice A modifier to check if a caller has the permission to apply a prepared setup. - /// @param _dao The address of the DAO. - /// @param _permissionId The permission identifier. - modifier canApply(address _dao, bytes32 _permissionId) { - _canApply(_dao, _permissionId); - _; - } - - /// @notice Constructs the plugin setup processor by setting the associated plugin repo registry. - // / @param _repoRegistry The plugin repo registry contract. - constructor(address[] memory _setups) { - for (uint256 i = 0; i < _setups.length; i++) { - queueSetup(_setups[i]); - } - // repoRegistry = _repoRegistry; - } - - address[] setups; - - function queueSetup(address _setup) public { - setups.push(_setup); - } - - function popSetup() internal returns (address) { - require(setups.length > 0, "No setups queued"); - address _setup = setups[setups.length - 1]; - setups.pop(); - return _setup; - } - - /// @notice Prepares the installation of a plugin. - /// @param _dao The address of the installing DAO. - /// @param _params The struct containing the parameters for the `prepareInstallation` function. - /// @return plugin The prepared plugin contract address. - /// @return preparedSetupData The data struct containing the array of helper contracts and permissions that the setup has prepared. - function prepareInstallation( - address _dao, - PrepareInstallationParams calldata _params - ) external returns (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) { - // PluginRepo pluginSetupRepo = _params.pluginSetupRef.pluginSetupRepo; - - // // Check that the plugin repository exists on the plugin repo registry. - // if (!repoRegistry.entries(address(pluginSetupRepo))) { - // revert PluginRepoNonexistent(); - // } - - // // reverts if not found - // PluginRepo.Version memory version = pluginSetupRepo.getVersion( - // _params.pluginSetupRef.versionTag - // ); - - // Prepare the installation - address setup = popSetup(); - (plugin, preparedSetupData) = PluginSetup(setup).prepareInstallation(_dao, _params.data); - - // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, plugin); - - // bytes32 preparedSetupId = _getPreparedSetupId( - // _params.pluginSetupRef, - // hashPermissions(preparedSetupData.permissions), - // hashHelpers(preparedSetupData.helpers), - // bytes(""), - // PreparationType.Installation - // ); - - // PluginState storage pluginState = states[pluginInstallationId]; - - // // Check if this plugin is already installed. - // if (pluginState.currentAppliedSetupId != bytes32(0)) { - // revert PluginAlreadyInstalled(); - // } - - // // Check if this setup has already been prepared before and is pending. - // if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { - // revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); - // } - - // pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; - - // emit InstallationPrepared({ - // sender: msg.sender, - // dao: _dao, - // preparedSetupId: preparedSetupId, - // pluginSetupRepo: pluginSetupRepo, - // versionTag: _params.pluginSetupRef.versionTag, - // data: _params.data, - // plugin: plugin, - // preparedSetupData: preparedSetupData - // }); - - return (plugin, preparedSetupData); - } - - /// @notice Applies the permissions of a prepared installation to a DAO. - /// @param _dao The address of the installing DAO. - /// @param _params The struct containing the parameters for the `applyInstallation` function. - function applyInstallation( - address _dao, - ApplyInstallationParams calldata _params - ) external canApply(_dao, APPLY_INSTALLATION_PERMISSION_ID) { - // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); - - // PluginState storage pluginState = states[pluginInstallationId]; - - // bytes32 preparedSetupId = _getPreparedSetupId( - // _params.pluginSetupRef, - // hashPermissions(_params.permissions), - // _params.helpersHash, - // bytes(""), - // PreparationType.Installation - // ); - - // // Check if this plugin is already installed. - // if (pluginState.currentAppliedSetupId != bytes32(0)) { - // revert PluginAlreadyInstalled(); - // } - - // validatePreparedSetupId(pluginInstallationId, preparedSetupId); - - // bytes32 appliedSetupId = _getAppliedSetupId(_params.pluginSetupRef, _params.helpersHash); - - // pluginState.currentAppliedSetupId = appliedSetupId; - // pluginState.blockNumber = block.number; - - // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the installing DAO. - if (_params.permissions.length > 0) { - DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); - } - - // emit InstallationApplied({ - // dao: _dao, - // plugin: _params.plugin, - // preparedSetupId: preparedSetupId, - // appliedSetupId: appliedSetupId - // }); - } - - /// @notice Prepares the update of an UUPS upgradeable plugin. - /// @param _dao The address of the DAO For which preparation of update happens. - /// @param _params The struct containing the parameters for the `prepareUpdate` function. - /// @return initData The initialization data to be passed to upgradeable contracts when the update is applied - /// @return preparedSetupData The data struct containing the array of helper contracts and permissions that the setup has prepared. - /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the update is prepared for. - function prepareUpdate( - address _dao, - PrepareUpdateParams calldata _params - ) - external - returns (bytes memory initData, IPluginSetup.PreparedSetupData memory preparedSetupData) - { - if ( - _params.currentVersionTag.release != _params.newVersionTag.release || - _params.currentVersionTag.build >= _params.newVersionTag.build - ) { - revert InvalidUpdateVersion({ - currentVersionTag: _params.currentVersionTag, - newVersionTag: _params.newVersionTag - }); - } - - bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.setupPayload.plugin); - - PluginState storage pluginState = states[pluginInstallationId]; - - bytes32 currentHelpersHash = hashHelpers(_params.setupPayload.currentHelpers); - - bytes32 appliedSetupId = _getAppliedSetupId( - PluginSetupRef(_params.currentVersionTag, _params.pluginSetupRepo), - currentHelpersHash - ); - - // The following check implicitly confirms that plugin is currently installed. - // Otherwise, `currentAppliedSetupId` would not be set. - if (pluginState.currentAppliedSetupId != appliedSetupId) { - revert InvalidAppliedSetupId({ - currentAppliedSetupId: pluginState.currentAppliedSetupId, - appliedSetupId: appliedSetupId - }); - } - - PluginRepo.Version memory currentVersion = _params.pluginSetupRepo.getVersion( - _params.currentVersionTag - ); - - PluginRepo.Version memory newVersion = _params.pluginSetupRepo.getVersion( - _params.newVersionTag - ); - - bytes32 preparedSetupId; - - // If the current and new plugin setup are identical, this is an UI update. - // In this case, the permission hash is set to the empty array hash and the `prepareUpdate` call is skipped to avoid side effects. - if (currentVersion.pluginSetup == newVersion.pluginSetup) { - preparedSetupId = _getPreparedSetupId( - PluginSetupRef(_params.newVersionTag, _params.pluginSetupRepo), - EMPTY_ARRAY_HASH, - currentHelpersHash, - bytes(""), - PreparationType.Update - ); - - // Because UI updates do not change the plugin functionality, the array of helpers - // associated with this plugin version `preparedSetupData.helpers` and being returned must - // equal `_params.setupPayload.currentHelpers` returned by the previous setup step (installation or update ) - // that this update is transitioning from. - preparedSetupData.helpers = _params.setupPayload.currentHelpers; - } else { - // Check that plugin is `PluginUUPSUpgradable`. - if (!_params.setupPayload.plugin.supportsInterface(type(IPlugin).interfaceId)) { - revert IPluginNotSupported({plugin: _params.setupPayload.plugin}); - } - if (IPlugin(_params.setupPayload.plugin).pluginType() != IPlugin.PluginType.UUPS) { - revert PluginNonupgradeable({plugin: _params.setupPayload.plugin}); - } - - // Prepare the update. - (initData, preparedSetupData) = PluginSetup(newVersion.pluginSetup).prepareUpdate( - _dao, - _params.currentVersionTag.build, - _params.setupPayload - ); - - preparedSetupId = _getPreparedSetupId( - PluginSetupRef(_params.newVersionTag, _params.pluginSetupRepo), - hashPermissions(preparedSetupData.permissions), - hashHelpers(preparedSetupData.helpers), - initData, - PreparationType.Update - ); - } - - // Check if this setup has already been prepared before and is pending. - if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { - revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); - } - - pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; - - // Avoid stack too deep. - emitPrepareUpdateEvent(_dao, preparedSetupId, _params, preparedSetupData, initData); - - return (initData, preparedSetupData); - } - - /// @notice Applies the permissions of a prepared update of an UUPS upgradeable proxy contract to a DAO. - /// @param _dao The address of the updating DAO. - /// @param _params The struct containing the parameters for the `applyInstallation` function. - function applyUpdate( - address _dao, - ApplyUpdateParams calldata _params - ) external canApply(_dao, APPLY_UPDATE_PERMISSION_ID) { - bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); - - PluginState storage pluginState = states[pluginInstallationId]; - - bytes32 preparedSetupId = _getPreparedSetupId( - _params.pluginSetupRef, - hashPermissions(_params.permissions), - _params.helpersHash, - _params.initData, - PreparationType.Update - ); - - validatePreparedSetupId(pluginInstallationId, preparedSetupId); - - bytes32 appliedSetupId = _getAppliedSetupId(_params.pluginSetupRef, _params.helpersHash); - - pluginState.blockNumber = block.number; - pluginState.currentAppliedSetupId = appliedSetupId; - - PluginRepo.Version memory version = _params.pluginSetupRef.pluginSetupRepo.getVersion( - _params.pluginSetupRef.versionTag - ); - - address currentImplementation = PluginUUPSUpgradeable(_params.plugin).implementation(); - address newImplementation = PluginSetup(version.pluginSetup).implementation(); - - if (currentImplementation != newImplementation) { - _upgradeProxy(_params.plugin, newImplementation, _params.initData); - } - - // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the updating DAO. - if (_params.permissions.length > 0) { - DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); - } - - emit UpdateApplied({ - dao: _dao, - plugin: _params.plugin, - preparedSetupId: preparedSetupId, - appliedSetupId: appliedSetupId - }); - } - - /// @notice Prepares the uninstallation of a plugin. - /// @param _dao The address of the uninstalling DAO. - /// @param _params The struct containing the parameters for the `prepareUninstallation` function. - /// @return permissions The list of multi-targeted permission operations to be applied to the uninstalling DAO. - /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the uninstallation was prepared for. - function prepareUninstallation( - address _dao, - PrepareUninstallationParams calldata _params - ) external returns (PermissionLib.MultiTargetPermission[] memory permissions) { - // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.setupPayload.plugin); - - // PluginState storage pluginState = states[pluginInstallationId]; - - // bytes32 appliedSetupId = _getAppliedSetupId( - // _params.pluginSetupRef, - // hashHelpers(_params.setupPayload.currentHelpers) - // ); - - // if (pluginState.currentAppliedSetupId != appliedSetupId) { - // revert InvalidAppliedSetupId({ - // currentAppliedSetupId: pluginState.currentAppliedSetupId, - // appliedSetupId: appliedSetupId - // }); - // } - - // PluginRepo.Version memory version = _params.pluginSetupRef.pluginSetupRepo.getVersion( - // _params.pluginSetupRef.versionTag - // ); - - permissions = PluginSetup(popSetup()).prepareUninstallation(_dao, _params.setupPayload); - - // bytes32 preparedSetupId = _getPreparedSetupId( - // _params.pluginSetupRef, - // hashPermissions(permissions), - // ZERO_BYTES_HASH, - // bytes(""), - // PreparationType.Uninstallation - // ); - - // // Check if this setup has already been prepared before and is pending. - // if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { - // revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); - // } - - // pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; - - // emit UninstallationPrepared({ - // sender: msg.sender, - // dao: _dao, - // preparedSetupId: preparedSetupId, - // pluginSetupRepo: _params.pluginSetupRef.pluginSetupRepo, - // versionTag: _params.pluginSetupRef.versionTag, - // setupPayload: _params.setupPayload, - // permissions: permissions - // }); - } - - /// @notice Applies the permissions of a prepared uninstallation to a DAO. - /// @param _dao The address of the DAO. - /// @param _dao The address of the uninstalling DAO. - /// @param _params The struct containing the parameters for the `applyUninstallation` function. - /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the uninstallation was prepared for. - function applyUninstallation( - address _dao, - ApplyUninstallationParams calldata _params - ) external canApply(_dao, APPLY_UNINSTALLATION_PERMISSION_ID) { - // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); - - // PluginState storage pluginState = states[pluginInstallationId]; - - // bytes32 preparedSetupId = _getPreparedSetupId( - // _params.pluginSetupRef, - // hashPermissions(_params.permissions), - // ZERO_BYTES_HASH, - // bytes(""), - // PreparationType.Uninstallation - // ); - - // validatePreparedSetupId(pluginInstallationId, preparedSetupId); - - // // Since the plugin is uninstalled, only the current block number must be updated. - // pluginState.blockNumber = block.number; - // pluginState.currentAppliedSetupId = bytes32(0); - - // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the uninstalling DAO. - if (_params.permissions.length > 0) { - DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); - } - - // emit UninstallationApplied({ - // dao: _dao, - // plugin: _params.plugin, - // preparedSetupId: preparedSetupId - // }); - } - - /// @notice Validates that a setup ID can be applied for `applyInstallation`, `applyUpdate`, or `applyUninstallation`. - /// @param pluginInstallationId The plugin installation ID obtained from the hash of `abi.encode(daoAddress, pluginAddress)`. - /// @param preparedSetupId The prepared setup ID to be validated. - /// @dev If the block number stored in `states[pluginInstallationId].blockNumber` exceeds the one stored in `pluginState.preparedSetupIdToBlockNumber[preparedSetupId]`, the prepared setup with `preparedSetupId` is outdated and not applicable anymore. - function validatePreparedSetupId( - bytes32 pluginInstallationId, - bytes32 preparedSetupId - ) public view { - PluginState storage pluginState = states[pluginInstallationId]; - if (pluginState.blockNumber >= pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { - revert SetupNotApplicable({preparedSetupId: preparedSetupId}); - } - } - - /// @notice Upgrades a UUPS upgradeable proxy contract (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). - /// @param _proxy The address of the proxy. - /// @param _implementation The address of the implementation contract. - /// @param _initData The initialization data to be passed to the upgradeable plugin contract via `upgradeToAndCall`. - function _upgradeProxy( - address _proxy, - address _implementation, - bytes memory _initData - ) private { - if (_initData.length > 0) { - try - PluginUUPSUpgradeable(_proxy).upgradeToAndCall(_implementation, _initData) - {} catch Error(string memory reason) { - revert(reason); - } catch (bytes memory) /*lowLevelData*/ { - revert PluginProxyUpgradeFailed({ - proxy: _proxy, - implementation: _implementation, - initData: _initData - }); - } - } else { - try PluginUUPSUpgradeable(_proxy).upgradeTo(_implementation) {} catch Error( - string memory reason - ) { - revert(reason); - } catch (bytes memory) /*lowLevelData*/ { - revert PluginProxyUpgradeFailed({ - proxy: _proxy, - implementation: _implementation, - initData: _initData - }); - } - } - } - - /// @notice Checks if a caller can apply a setup. The caller can be either the DAO to which the plugin setup is applied to or another account to which the DAO has granted the respective permission. - /// @param _dao The address of the applying DAO. - /// @param _permissionId The permission ID. - function _canApply(address _dao, bytes32 _permissionId) private view { - if ( - msg.sender != _dao && - !DAO(payable(_dao)).hasPermission(address(this), msg.sender, _permissionId, bytes("")) - ) { - revert SetupApplicationUnauthorized({ - dao: _dao, - caller: msg.sender, - permissionId: _permissionId - }); - } - } - - /// @notice A helper to emit the `UpdatePrepared` event from the supplied, structured data. - /// @param _dao The address of the updating DAO. - /// @param _preparedSetupId The prepared setup ID. - /// @param _params The struct containing the parameters for the `prepareUpdate` function. - /// @param _preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. - /// @param _initData The initialization data to be passed to upgradeable contracts when the update is applied - /// @dev This functions exists to avoid stack-too-deep errors. - function emitPrepareUpdateEvent( - address _dao, - bytes32 _preparedSetupId, - PrepareUpdateParams calldata _params, - IPluginSetup.PreparedSetupData memory _preparedSetupData, - bytes memory _initData - ) private { - emit UpdatePrepared({ - sender: msg.sender, - dao: _dao, - preparedSetupId: _preparedSetupId, - pluginSetupRepo: _params.pluginSetupRepo, - versionTag: _params.newVersionTag, - setupPayload: _params.setupPayload, - preparedSetupData: _preparedSetupData, - initData: _initData - }); - } -} diff --git a/test/mocks/osx/MockPluginRepoRegistry.sol b/test/mocks/osx/MockPluginRepoRegistry.sol deleted file mode 100644 index 5fa8a89..0000000 --- a/test/mocks/osx/MockPluginRepoRegistry.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity 0.8.17; - -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {InterfaceBasedRegistry} from "@aragon/osx/test/utils/InterfaceBasedRegistryMock.sol"; -import {IPluginRepo} from "@aragon/osx/framework/plugin/repo/IPluginRepo.sol"; - -/// @title MockPluginRepoRegistry -/// @author Aragon Association - 2022-2023 -/// @notice This contract maintains an address-based registry of plugin repositories in the Aragon App DAO framework. -contract MockPluginRepoRegistry is InterfaceBasedRegistry { - /// @notice The ID of the permission required to call the `register` function. - // bytes32 public constant REGISTER_PLUGIN_REPO_PERMISSION_ID = keccak256("REGISTER_PLUGIN_REPO_PERMISSION"); - - /// @notice Emitted if a new plugin repository is registered. - /// @param subdomain The subdomain of the plugin repository. - /// @param pluginRepo The address of the plugin repository. - event PluginRepoRegistered(string subdomain, address pluginRepo); - - // /// @notice Thrown if the plugin subdomain doesn't match the regex `[0-9a-z\-]` - // error InvalidPluginSubdomain(string subdomain); - - // /// @notice Thrown if the plugin repository subdomain is empty. - // error EmptyPluginRepoSubdomain(); - - /// @dev Used to disallow initializing the implementation contract by an attacker for extra safety. - constructor() { - _disableInitializers(); - } - - /// @notice Initializes the contract by setting calling the `InterfaceBasedRegistry` base class initialize method. - /// @param _dao The address of the managing DAO. - function initialize(IDAO _dao) external initializer { - bytes4 pluginRepoInterfaceId = type(IPluginRepo).interfaceId; - __InterfaceBasedRegistry_init(_dao, pluginRepoInterfaceId); - - // subdomainRegistrar = _subdomainRegistrar; - } - - /// @notice Registers a plugin repository with a subdomain and address. - /// @param subdomain The subdomain of the PluginRepo. - /// @param pluginRepo The address of the PluginRepo contract. - function registerPluginRepo( - string calldata subdomain, - address pluginRepo - ) external // auth(REGISTER_PLUGIN_REPO_PERMISSION_ID) - { - // if (!(bytes(subdomain).length > 0)) { - // revert EmptyPluginRepoSubdomain(); - // } - // if (!isSubdomainValid(subdomain)) { - // revert InvalidPluginSubdomain({subdomain: subdomain}); - // } - // bytes32 labelhash = keccak256(bytes(subdomain)); - // subdomainRegistrar.registerSubnode(labelhash, pluginRepo); - // _register(pluginRepo); - // emit PluginRepoRegistered(subdomain, pluginRepo); - } - - /// @notice This empty reserved space is put in place to allow future versions to add new variables without shifting down storage in the inheritance chain (see [OpenZeppelin's guide about storage gaps](https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps)). - uint256[50] private __gap; -} diff --git a/test/mocks/osx/PlaceholderSetup.sol b/test/mocks/osx/PlaceholderSetup.sol deleted file mode 100644 index 8c786f8..0000000 --- a/test/mocks/osx/PlaceholderSetup.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.0; -import {PluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol"; - -contract PlaceholderSetup { - function prepareInstallation( - address, - bytes calldata - ) external returns (address plugin, PluginSetup.PreparedSetupData memory preparedSetupData) {} -} diff --git a/test/mocks/osx/PluginSetupProcessorHelpers.sol b/test/mocks/osx/PluginSetupProcessorHelpers.sol deleted file mode 100644 index 135059a..0000000 --- a/test/mocks/osx/PluginSetupProcessorHelpers.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity 0.8.17; - -import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; -import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; -import {PluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol"; - -/// @notice The struct containing a reference to a plugin setup by specifying the containing plugin repository and the associated version tag. -/// @param versionTag The tag associated with the plugin setup version. -/// @param pluginSetupRepo The plugin setup repository. -struct PluginSetupRef { - PluginRepo.Tag versionTag; - PluginRepo pluginSetupRepo; -} - -/// @notice The different types describing a prepared setup. -/// @param None The default indicating the lack of a preparation type. -/// @param Installation The prepared setup installs a new plugin. -/// @param Update The prepared setup updates an existing plugin. -/// @param Uninstallation The prepared setup uninstalls an existing plugin. -enum PreparationType { - None, - Installation, - Update, - Uninstallation -} - -/// @notice Returns an ID for plugin installation by hashing the DAO and plugin address. -/// @param _dao The address of the DAO conducting the setup. -/// @param _plugin The plugin address. -function _getPluginInstallationId(address _dao, address _plugin) pure returns (bytes32) { - return keccak256(abi.encode(_dao, _plugin)); -} - -/// @notice Returns an ID for prepared setup obtained from hashing characterizing elements. -/// @param _pluginSetupRef The reference of the plugin setup containing plugin setup repo and version tag. -/// @param _permissionsHash The hash of the permission operations requested by the setup. -/// @param _helpersHash The hash of the helper contract addresses. -/// @param _data The bytes-encoded initialize data for the upgrade that is returned by `prepareUpdate`. -/// @param _preparationType The type of preparation the plugin is currently undergoing. Without this, it is possible to call `applyUpdate` even after `applyInstallation` is called. -/// @return The prepared setup id. -function _getPreparedSetupId( - PluginSetupRef memory _pluginSetupRef, - bytes32 _permissionsHash, - bytes32 _helpersHash, - bytes memory _data, - PreparationType _preparationType -) pure returns (bytes32) { - return - keccak256( - abi.encode( - _pluginSetupRef.versionTag, - _pluginSetupRef.pluginSetupRepo, - _permissionsHash, - _helpersHash, - keccak256(_data), - _preparationType - ) - ); -} - -/// @notice Returns an identifier for applied installations. -/// @param _pluginSetupRef The reference of the plugin setup containing plugin setup repo and version tag. -/// @param _helpersHash The hash of the helper contract addresses. -/// @return The applied setup id. -function _getAppliedSetupId( - PluginSetupRef memory _pluginSetupRef, - bytes32 _helpersHash -) pure returns (bytes32) { - return - keccak256( - abi.encode(_pluginSetupRef.versionTag, _pluginSetupRef.pluginSetupRepo, _helpersHash) - ); -} - -/// @notice Returns a hash of an array of helper addresses (contracts or EOAs). -/// @param _helpers The array of helper addresses (contracts or EOAs) to be hashed. -function hashHelpers(address[] memory _helpers) pure returns (bytes32) { - return keccak256(abi.encode(_helpers)); -} - -/// @notice Returns a hash of an array of multi-targeted permission operations. -/// @param _permissions The array of of multi-targeted permission operations. -/// @return The hash of the array of permission operations. -function hashPermissions( - PermissionLib.MultiTargetPermission[] memory _permissions -) pure returns (bytes32) { - return keccak256(abi.encode(_permissions)); -} diff --git a/test/python/crosscheck.py b/test/python/crosscheck.py deleted file mode 100644 index a40b634..0000000 --- a/test/python/crosscheck.py +++ /dev/null @@ -1,57 +0,0 @@ -# time utils -HOUR = 60 * 60 -DAY = 24 * HOUR -WEEK = 7 * DAY - -# Variables -AMOUNT = 420.69 # example amount to deposit -PERIOD_LENGTH = 2 * WEEK # example period length in seconds (1 week) -WARMUP_PERIOD = 3 * DAY # warmup period in days -MAX_PERIODS = 52 # maximum periods - -QUADRATIC_COEFFICIENT = 0 -LINEAR_COEFFICIENT = 1 / 52 -CONSTANT = 1 - -# Scale amount -amount_scaled = AMOUNT * 1e18 - - -# Function to evaluate y -def evaluate_y(secondsElapsed, PeriodLength, amount_scaled): - x = secondsElapsed / PeriodLength - y = amount_scaled * ( - QUADRATIC_COEFFICIENT * (x**2) + LINEAR_COEFFICIENT * x + CONSTANT - ) - return y - - -def evaluate_y_v2(secondsElapsed, PeriodLength, amount_scaled): - x = secondsElapsed / PeriodLength - y = amount_scaled * ( - QUADRATIC_COEFFICIENT * (x * x) + LINEAR_COEFFICIENT * x + CONSTANT - ) - return y - - -# Time points to evaluate, using tuples with optional labels -time_points = [ - ("0", 0), - ("1 minute", 60), - ("1 hour", 60 * 60), - ("1 day", 60 * 60 * 24), - (f"WARMUP_PERIOD ({WARMUP_PERIOD//DAY} days)", WARMUP_PERIOD), - (f"WARMUP_PERIOD + 1s", (WARMUP_PERIOD) + 1), - ("1 week", 60 * 60 * 24 * 7), - (f"1 period ({PERIOD_LENGTH // (WEEK)} weeks)", PERIOD_LENGTH), - (f"10 periods (10 * PERIOD)", 10 * PERIOD_LENGTH), - (f"50% periods (26 * PERIOD)", 26 * PERIOD_LENGTH), - (f"35 periods (35 * PERIOD)", 35 * PERIOD_LENGTH), - (f"PERIOD_END (26 * PERIOD)", MAX_PERIODS * PERIOD_LENGTH), -] - -# Evaluate and print results -for label, t in time_points: - y_value = evaluate_y(t, PERIOD_LENGTH, amount_scaled) - # Avoid scientific notation by formatting with commas and align values vertically - print(f"{label:<30} Voting Power: {y_value:>20.0f}") diff --git a/test/python/epoch.py b/test/python/epoch.py deleted file mode 100644 index bc2da7a..0000000 --- a/test/python/epoch.py +++ /dev/null @@ -1,23 +0,0 @@ -starting_time = 100 -current_time = starting_time -interval = 10 - -for seconds in range(30): - current_time += 1 - elapsed_in_interval = current_time % interval - time_until_next_interval = interval - elapsed_in_interval - - if elapsed_in_interval == 0: - next_deposit = current_time - else: - next_deposit = current_time + time_until_next_interval - - print("Current time: ", current_time) - print("Elapsed in interval: ", elapsed_in_interval) - print("Time until next interval: ", time_until_next_interval) - print("Next deposit: ", next_deposit) - print("") - - - - diff --git a/test/voting/GaugeManage.t.sol b/test/voting/GaugeManage.t.sol deleted file mode 100644 index 16f2258..0000000 --- a/test/voting/GaugeManage.t.sol +++ /dev/null @@ -1,201 +0,0 @@ -pragma solidity ^0.8.17; - -import {Test} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -// aragon contracts -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {MockPluginSetupProcessor} from "@mocks/osx/MockPSP.sol"; -import {MockDAOFactory} from "@mocks/osx/MockDAOFactory.sol"; -import {MockERC20} from "@mocks/MockERC20.sol"; - -import "@helpers/OSxHelpers.sol"; - -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; -import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; -import {VotingEscrow, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; - -import {GaugeVotingBase} from "./GaugeVotingBase.sol"; - -contract TestGaugeManage is GaugeVotingBase { - function setUp() public override { - super.setUp(); - vm.warp(1); // avoids sentinel value for createdAt - } - - // only gauge admins - function testOnlyGaugeAdmins(address _notThis) public { - vm.assume(_notThis != address(this)); - vm.assume(_notThis != address(dao)); - - bytes memory err = _authErr({ - _caller: _notThis, - _contract: address(voter), - _perm: voter.GAUGE_ADMIN_ROLE() - }); - - vm.startPrank(_notThis); - { - // only gauge admins can create gauges - vm.expectRevert(err); - voter.createGauge(address(0), ""); - - // only gauge admins can deactivate gauges - vm.expectRevert(err); - voter.deactivateGauge(address(0)); - - // only gauge admins can activate gauges - vm.expectRevert(err); - voter.activateGauge(address(0)); - - // only gauge admins can update gauge metadata - vm.expectRevert(err); - voter.updateGaugeMetadata(address(0), ""); - - vm.expectRevert(err); - voter.pause(); - - vm.expectRevert(err); - voter.unpause(); - } - vm.stopPrank(); - } - - // emits the event and pushes the new gauge - function testFuzz_CreateGauge(address _gauge, string calldata metadata) public { - vm.assume(_gauge != address(0)); - vm.expectEmit(true, true, false, true); - emit GaugeCreated(_gauge, address(this), metadata); - voter.createGauge(_gauge, metadata); - - assertEq(voter.getAllGauges().length, 1); - assertEq(voter.isActive(_gauge), true); - } - - // can't create if exists alread - function testFuzz_CreateGauge_Exists(address _gauge, string calldata metadata) public { - vm.assume(_gauge != address(0)); - voter.createGauge(_gauge, metadata); - vm.expectRevert(GaugeExists.selector); - voter.createGauge(_gauge, metadata); - } - - // can't decativate a non-existent gauge - function testFuzz_DeactivateGauge(address _gauge) public { - vm.expectRevert(abi.encodeWithSelector(GaugeDoesNotExist.selector, _gauge)); - voter.deactivateGauge(_gauge); - } - - // can deactivate an existing gauge - function testFuzz_DeactivateGaugeExists(address _gauge, string calldata metadata) public { - vm.assume(_gauge != address(0)); - voter.createGauge(_gauge, metadata); - vm.expectEmit(true, false, false, true); - emit GaugeDeactivated(_gauge); - voter.deactivateGauge(_gauge); - - assertEq(voter.getAllGauges().length, 1); - assertEq(voter.isActive(_gauge), false); - } - - // can't deactivate an already deactivated gauge - function testFuzz_DeactivateGaugeAlreadyDeactivated( - address _gauge, - string calldata metadata - ) public { - vm.assume(_gauge != address(0)); - voter.createGauge(_gauge, metadata); - voter.deactivateGauge(_gauge); - vm.expectRevert(GaugeActivationUnchanged.selector); - voter.deactivateGauge(_gauge); - } - - // can't activate a non-existent gauge - function testFuzz_ActivateGauge(address _gauge) public { - vm.expectRevert(abi.encodeWithSelector(GaugeDoesNotExist.selector, _gauge)); - voter.activateGauge(_gauge); - } - - // can reactivate an existing gauge - function testFuzz_ActivateGaugeExists(address _gauge, string calldata metadata) public { - vm.assume(_gauge != address(0)); - voter.createGauge(_gauge, metadata); - voter.deactivateGauge(_gauge); - vm.expectEmit(true, false, false, true); - emit GaugeActivated(_gauge); - voter.activateGauge(_gauge); - - assertEq(voter.getAllGauges().length, 1); - assertEq(voter.isActive(_gauge), true); - } - - // can't activate an already activated gauge - function testFuzz_ActivateGaugeAlreadyActivated( - address _gauge, - string calldata metadata - ) public { - vm.assume(_gauge != address(0)); - voter.createGauge(_gauge, metadata); - vm.expectRevert(GaugeActivationUnchanged.selector); - voter.activateGauge(_gauge); - } - - // can't update metadata on a non-existent gauge - function testFuzz_UpdateGaugeMetadata(address _gauge, string calldata metadata) public { - vm.expectRevert(abi.encodeWithSelector(GaugeDoesNotExist.selector, _gauge)); - voter.updateGaugeMetadata(_gauge, metadata); - } - - function testCannotCreateZeroGauge() public { - vm.expectRevert(ZeroGauge.selector); - voter.createGauge(address(0), ""); - } - - // can update metadata on an existing gauge - function testFuzz_UpdateGaugeMetadataExists( - address _gauge, - string calldata metadata, - string calldata newMetadata - ) public { - vm.assume(_gauge != address(0)); - voter.createGauge(_gauge, metadata); - vm.expectEmit(true, false, false, true); - emit GaugeMetadataUpdated(_gauge, newMetadata); - voter.updateGaugeMetadata(_gauge, newMetadata); - } - - function testFuzz_canUpdateGaugeMetadata(address _gauge, string calldata metadata) public { - vm.assume(_gauge != address(0)); - voter.createGauge(_gauge, metadata); - - assertEq(voter.getGauge(_gauge).metadataURI, metadata); - - string memory newMetadata = "new metadata"; - voter.updateGaugeMetadata(_gauge, newMetadata); - - assertEq(voter.getGauge(_gauge).metadataURI, newMetadata); - } - - // can pause votes and resets - function testCanPauseVoteAndResets() public { - bytes memory err = "Pausable: paused"; - - voter.pause(); - - GaugeVote[] memory votes; - uint256[] memory tokenIds; - - vm.expectRevert(err); - voter.vote(0, votes); - - vm.expectRevert(err); - voter.voteMultiple(tokenIds, votes); - - vm.expectRevert(err); - voter.reset(0); - } -} diff --git a/test/voting/GaugeTime.t.sol b/test/voting/GaugeTime.t.sol deleted file mode 100644 index a9772ef..0000000 --- a/test/voting/GaugeTime.t.sol +++ /dev/null @@ -1,101 +0,0 @@ -pragma solidity ^0.8.17; - -import {Test} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -// aragon contracts -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {MockPluginSetupProcessor} from "@mocks/osx/MockPSP.sol"; -import {MockDAOFactory} from "@mocks/osx/MockDAOFactory.sol"; -import {MockERC20} from "@mocks/MockERC20.sol"; - -import "@helpers/OSxHelpers.sol"; - -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; -import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; -import {VotingEscrow, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; - -import {GaugeVotingBase} from "./GaugeVotingBase.sol"; - -contract TestGaugeTime is GaugeVotingBase { - function setUp() public override { - super.setUp(); - vm.warp(0); - } - - function nextDeposit() public view returns (uint256) { - return clock.epochNextCheckpointTs(); - } - - function testEpochTimess() public { - for (uint i = 0; i < 10; ++i) { - uint start = block.timestamp; - - // start - assertEq(clock.elapsedInEpoch(), 0); - assertEq(voter.epochId(), i); - assertEq(voter.epochStart(), block.timestamp); - assertEq(voter.epochVoteStart(), block.timestamp + 1 hours); - assertEq(voter.epochVoteEnd(), block.timestamp + 1 weeks - 1 hours); - assertEq(voter.votingActive(), false); - assertEq(nextDeposit(), block.timestamp + 1 weeks); - assertEq(clock.epochStartsIn(), 0); - - // +1hr: voting starts - vm.warp(start + 1 hours); - - assertEq(clock.elapsedInEpoch(), 1 hours); - assertEq(voter.epochId(), i); - assertEq(voter.epochStart(), block.timestamp + 2 weeks - 1 hours); - assertEq(voter.epochVoteStart(), block.timestamp); - assertEq(voter.epochVoteEnd(), block.timestamp + 1 weeks - 2 hours); - assertEq(voter.votingActive(), true); - assertEq(nextDeposit(), block.timestamp + 1 weeks - 1 hours); - assertEq(clock.epochStartsIn(), 2 weeks - 1 hours); - - // +1 week - 1 hours: voting ends - vm.warp(start + 1 weeks - 1 hours); - - assertEq(clock.elapsedInEpoch(), 1 weeks - 1 hours); - assertEq(voter.epochId(), i); - assertEq(voter.epochStart(), block.timestamp + 1 weeks + 1 hours); - assertEq(voter.epochVoteStart(), block.timestamp + 1 weeks + 2 hours); - assertEq(voter.epochVoteEnd(), block.timestamp); - assertEq(voter.votingActive(), false); - assertEq(nextDeposit(), block.timestamp + 1 hours); - assertEq(clock.epochStartsIn(), 1 weeks + 1 hours); - - // +1 week: next deposit opens - vm.warp(start + 1 weeks); - - assertEq(clock.elapsedInEpoch(), 1 weeks); - assertEq(voter.epochId(), i); - assertEq(voter.epochStart(), block.timestamp + 1 weeks); - assertEq(voter.epochVoteStart(), block.timestamp + 1 weeks + 1 hours); - assertEq(voter.epochVoteEnd(), block.timestamp); - assertEq(voter.votingActive(), false); - assertEq(nextDeposit(), block.timestamp + 1 weeks); - assertEq(clock.epochStartsIn(), 1 weeks); - - // whole of next week calculates correctly - - vm.warp(start + 1 weeks + 3 days); - - assertEq(clock.elapsedInEpoch(), 1 weeks + 3 days); - assertEq(voter.epochId(), i); - assertEq(voter.epochStart(), block.timestamp + 4 days); - assertEq(voter.epochVoteStart(), block.timestamp + 4 days + 1 hours); - assertEq(voter.epochVoteEnd(), block.timestamp); - assertEq(voter.votingActive(), false); - assertEq(nextDeposit(), block.timestamp + 4 days); - assertEq(clock.epochStartsIn(), 4 days); - - // +1 week + 2 hours: next epoch starts - vm.warp(start + 2 weeks); - } - } -} diff --git a/test/voting/GaugeVote.t.sol b/test/voting/GaugeVote.t.sol deleted file mode 100644 index f5f225b..0000000 --- a/test/voting/GaugeVote.t.sol +++ /dev/null @@ -1,537 +0,0 @@ -pragma solidity ^0.8.17; - -import {Test} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -// aragon contracts -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {MockPluginSetupProcessor} from "@mocks/osx/MockPSP.sol"; -import {MockDAOFactory} from "@mocks/osx/MockDAOFactory.sol"; -import {MockERC20} from "@mocks/MockERC20.sol"; - -import "@helpers/OSxHelpers.sol"; - -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; -import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; -import {VotingEscrow, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; - -import {GaugeVotingBase} from "./GaugeVotingBase.sol"; - -contract TestGaugeVote is GaugeVotingBase { - uint256[] ids; - GaugeVote[] votes; - - address owner = address(0x420); - uint256 lockDeposit = 1000 ether; - uint256 tokenId; - address gauge = address(0x777); - - uint time; - - function _increaseTime(uint _by) internal { - time += _by; - vm.warp(time); - } - - function setUp() public override { - super.setUp(); - - // reset clock - vm.warp(0); - time = block.timestamp; - - // means we have voting power - curve.setWarmupPeriod(0); - - // mint underlying and stake - token.mint(owner, lockDeposit); - vm.startPrank(owner); - { - token.approve(address(escrow), lockDeposit); - tokenId = escrow.createLock(lockDeposit); - } - vm.stopPrank(); - - // activate cp & warp to an active window - _increaseTime(2 weeks + 1 hours + 1); - - assertGt(escrow.votingPower(tokenId), 0); - assertTrue(voter.votingActive(), "voting should be active"); - - // create a gauge - voter.createGauge(gauge, "metadata"); - } - - function testFuzz_cannotVoteOutsideVotingWindow(uint256 time) public { - // warp to a random time - vm.warp(time); - - // should now be inactive (we don't test this part herewe have the epoch logic tests) - vm.assume(!voter.votingActive()); - - // try to vote - vm.expectRevert(VotingInactive.selector); - voter.vote(0, votes); - - // try to reset - vm.expectRevert(VotingInactive.selector); - voter.reset(0); - - // vote multiple - vm.expectRevert(VotingInactive.selector); - voter.voteMultiple(ids, votes); - } - - // can't vote if you don't own the token - function testCannotVoteIfYouDontOwnTheToken() public { - // try to vote as this address (not the holder) - vm.expectRevert(NotApprovedOrOwner.selector); - voter.vote(tokenId, votes); - } - - function testCannotResetIFYouDontOwnTheToken() public { - // make the vote - votes.push(GaugeVote(lockDeposit, gauge)); - vm.prank(owner); - voter.vote(tokenId, votes); - - // try to reset as this address (not the holder) - vm.expectRevert(NotApprovedOrOwner.selector); - voter.reset(tokenId); - } - - // can't vote if you have zero voting power - function testCannotVoteIfYouHaveZeroVotingPower() public { - curve.setWarmupPeriod(1000 weeks); - - // create a second lock - token.mint(owner, lockDeposit); - vm.startPrank(owner); - { - token.approve(address(escrow), lockDeposit); - uint256 newTokenId = escrow.createLock(lockDeposit); - assertEq(escrow.votingPower(newTokenId), 0); - vm.expectRevert(NoVotingPower.selector); - voter.vote(newTokenId, votes); - } - vm.stopPrank(); - } - - function testCannotVoteWithNoVotes() public { - // try to vote with no votes - vm.expectRevert(NoVotes.selector); - vm.prank(owner); - voter.vote(tokenId, votes); - } - - function testCannotVoteForInactiveGauge() public { - // deactivate the gauge - voter.deactivateGauge(gauge); - - // create the vote - votes.push(GaugeVote(lockDeposit, gauge)); - - // try to vote - vm.prank(owner); - vm.expectRevert(abi.encodeWithSelector(GaugeInactive.selector, gauge)); - voter.vote(tokenId, votes); - } - - function testCannotVoteForNonExistentGauge() public { - address notAGauge = address(0x69); - // create the vote - votes.push(GaugeVote(lockDeposit, notAGauge)); - - // try to vote - vm.prank(owner); - vm.expectRevert(abi.encodeWithSelector(GaugeDoesNotExist.selector, notAGauge)); - voter.vote(tokenId, votes); - } - - function testCannotVoteWithZeroVotes() public { - // create the vote - votes.push(GaugeVote(0, gauge)); - - // try to vote - vm.prank(owner); - vm.expectRevert(NoVotes.selector); - voter.vote(tokenId, votes); - } - - function testCannotVoteWithVotesSoSmallTheyRoundToZero() public { - // need to have very low voting power or very high weights - - // make a second gauge - address gauge2 = address(0x69); - voter.createGauge(gauge2, "metadata"); - - // create a new lock w. 1 wei - token.mint(owner, 1); - uint256 newTokenId; - vm.startPrank(owner); - { - token.approve(address(escrow), 1); - newTokenId = escrow.createLock(1); - // warp 2 weeks - vm.warp(block.timestamp + 2 weeks); - assertEq(escrow.votingPower(newTokenId), 1); - - // make the vote: split the 1 wei into 2 votes - votes.push(GaugeVote(1, gauge)); - votes.push(GaugeVote(1, gauge2)); - - // try to vote - vm.expectRevert(NoVotes.selector); - voter.vote(newTokenId, votes); - } - vm.stopPrank(); - } - - function cannotDoubleVote() public { - // create the vote - votes.push(GaugeVote(lockDeposit, gauge)); - votes.push(GaugeVote(lockDeposit, gauge)); - - vm.expectRevert(DoubleVote.selector); - voter.vote(tokenId, votes); - } - - function testSingleVote(uint128 _weight) public { - vm.assume(_weight > 0); - - // create the vote - votes.push(GaugeVote(_weight, gauge)); - - uint votingPower = escrow.votingPower(tokenId); - - // vote - vm.startPrank(owner); - { - vm.expectEmit(true, true, true, true); - emit Voted({ - voter: owner, - gauge: gauge, - epoch: voter.epochId(), - tokenId: tokenId, - votingPowerCastForGauge: votingPower, - totalVotingPowerInGauge: votingPower, - totalVotingPowerInContract: votingPower, - timestamp: block.timestamp - }); - voter.vote(tokenId, votes); - } - vm.stopPrank(); - - // check the vote - assertEq(voter.isVoting(tokenId), true); - assertEq(voter.gaugesVotedFor(tokenId).length, 1); - assertEq(voter.gaugesVotedFor(tokenId)[0], gauge); - assertEq(voter.votes(tokenId, gauge), votingPower); - assertEq(voter.usedVotingPower(tokenId), votingPower); - - // global state - assertEq(voter.totalVotingPowerCast(), votingPower); - assertEq(voter.gaugeVotes(gauge), votingPower); - } - - // 32 bit integers mean we don't round to zero - function testFuzz_vote(uint32 _weight0, uint32 _weight1) public { - vm.assume(_weight0 > 0 && _weight1 > 0); - // setup 2 gauges - address newGauge = address(0x69); - voter.createGauge(newGauge, "metadata"); - - // no need to create a random lock as it's already a large complex number - // do a random allocation of weights - - // create the vote - votes.push(GaugeVote(_weight0, gauge)); - votes.push(GaugeVote(_weight1, newGauge)); - - uint votingPower = escrow.votingPower(tokenId); - - vm.startPrank(owner); - { - voter.vote(tokenId, votes); - } - vm.stopPrank(); - - // overflow math - uint weight0256 = uint256(_weight0); - uint weight1256 = uint256(_weight1); - uint expectedVotesForGauge = (weight0256 * votingPower) / (weight0256 + weight1256); - uint expectedVotesForNewGauge = (weight1256 * votingPower) / (weight0256 + weight1256); - - uint expectedTotalVotes = expectedVotesForGauge + expectedVotesForNewGauge; - assertApproxEqAbs(voter.usedVotingPower(tokenId), expectedTotalVotes, 1); - - // check the vote - assertEq(voter.isVoting(tokenId), true); - assertEq(voter.gaugesVotedFor(tokenId).length, 2); - assertEq(voter.gaugesVotedFor(tokenId)[0], gauge); - assertEq(voter.gaugesVotedFor(tokenId)[1], newGauge); - assertEq(voter.votes(tokenId, gauge), expectedVotesForGauge); - assertEq(voter.votes(tokenId, newGauge), expectedVotesForNewGauge); - assertEq(voter.usedVotingPower(tokenId), expectedTotalVotes); - - // global state - assertEq(voter.totalVotingPowerCast(), expectedTotalVotes); - assertEq(voter.gaugeVotes(gauge), expectedVotesForGauge); - assertEq(voter.gaugeVotes(newGauge), expectedVotesForNewGauge); - } - - function testManualResets() public { - // vote - votes.push(GaugeVote(1000, gauge)); - - uint votingPower = escrow.votingPower(tokenId); - - // vote then reset - vm.startPrank(owner); - { - voter.vote(tokenId, votes); - - // reset - vm.expectEmit(true, true, true, true); - emit Reset({ - voter: owner, - gauge: gauge, - epoch: voter.epochId(), - tokenId: tokenId, - votingPowerRemovedFromGauge: votingPower, - totalVotingPowerInGauge: 0, - totalVotingPowerInContract: 0, - timestamp: block.timestamp - }); - voter.reset(tokenId); - } - vm.stopPrank(); - - // check the vote - assertEq(voter.isVoting(tokenId), false); - assertEq(voter.gaugesVotedFor(tokenId).length, 0); - assertEq(voter.votes(tokenId, gauge), 0); - assertEq(voter.usedVotingPower(tokenId), 0); - - // global state - assertEq(voter.totalVotingPowerCast(), 0); - assertEq(voter.gaugeVotes(gauge), 0); - } - - function testVotingResets() public { - // create a second gauge - address gauge2 = address(0x69); - voter.createGauge(gauge2, "metadata"); - - votes.push(GaugeVote(25, gauge)); - votes.push(GaugeVote(75, gauge2)); - - // vote then revote - vm.startPrank(owner); - { - voter.vote(tokenId, votes); - - // change vote - GaugeVote[] memory newVotes = new GaugeVote[](1); - newVotes[0] = GaugeVote(100, gauge); - - // more voting power - vm.warp(block.timestamp + 1 days); - - // vote again clears the votes - voter.vote(tokenId, newVotes); - } - vm.stopPrank(); - - uint newVotingPower = escrow.votingPower(tokenId); - - // check the vote - assertEq(voter.isVoting(tokenId), true); - assertEq(voter.gaugesVotedFor(tokenId).length, 1); - assertEq(voter.gaugesVotedFor(tokenId)[0], gauge); - assertEq(voter.votes(tokenId, gauge), newVotingPower); - assertEq(voter.usedVotingPower(tokenId), newVotingPower); - - // global state - assertEq(voter.totalVotingPowerCast(), newVotingPower); - assertEq(voter.gaugeVotes(gauge), newVotingPower); - } - - function testCanVoteForMultiple() public { - uint secondDeposit = 500 ether; - - // create a second gauge - address gauge2 = address(0x69); - voter.createGauge(gauge2, "metadata"); - - // create a second lock - token.mint(owner, secondDeposit); - uint tokenIdNew; - vm.startPrank(owner); - - { - token.approve(address(escrow), secondDeposit); - tokenIdNew = escrow.createLock(secondDeposit); - } - vm.stopPrank(); - - // jump 2 weeks so that we have voting power - vm.warp(block.timestamp + 2 weeks); - - assertGt(escrow.votingPower(tokenIdNew), 0); - - // get all the token Ids for the user - uint256[] memory tokens = escrow.ownedTokens(owner); - - vm.prank(owner); - nftLock.setApprovalForAll(address(voter), true); - - uint vp0 = escrow.votingPower(tokens[0]); - uint vp1 = escrow.votingPower(tokens[1]); - - uint totalVotingPower = escrow.votingPowerForAccount(owner); - assertEq(totalVotingPower, vp0 + vp1); - - // vote multiple - votes.push(GaugeVote(50, gauge)); - votes.push(GaugeVote(100, gauge2)); - - vm.prank(owner); - voter.voteMultiple(tokens, votes); - - // we expect the vote for the first token to be 50/150 of the total voting power - // and the second to be 100/150 of the total voting power - - uint expectedVotesForGauge = (50 * vp0) / (50 + 100); - uint expectedVotesForGauge2 = (100 * vp1) / (50 + 100); - - // check the vote - assertEq(voter.isVoting(tokenId), true); - assertEq(voter.gaugesVotedFor(tokenId).length, 2); - assertEq(voter.gaugesVotedFor(tokenId)[0], gauge); - assertEq(voter.gaugesVotedFor(tokenId)[1], gauge2); - assertEq(voter.votes(tokenId, gauge), expectedVotesForGauge); - assertEq(voter.votes(tokenIdNew, gauge2), expectedVotesForGauge2); - } - - // test the event logs: person A votes, person B votes => B's event correctly distinguishes between the two - // then A resets, leaving B's vote in place from the logs - function test2PeopleVoteEvents() public { - address personA = address(0xc0ffee); - address personB = address(0xbabe); - - address gauge2 = address(0x69); - voter.createGauge(gauge2, "metadata"); - - votes.push(GaugeVote(25, gauge)); - votes.push(GaugeVote(75, gauge2)); - - // create lock for A - token.mint(personA, 1000 ether); - uint tokenIdA; - vm.startPrank(personA); - { - token.approve(address(escrow), 1000 ether); - tokenIdA = escrow.createLock(1000 ether); - } - vm.stopPrank(); - - // create lock for B - token.mint(personB, 1000 ether); - uint tokenIdB; - vm.startPrank(personB); - { - token.approve(address(escrow), 1000 ether); - tokenIdB = escrow.createLock(1000 ether); - } - vm.stopPrank(); - - // jump 2 weeks so that we have voting power - vm.warp(block.timestamp + 2 weeks); - - assertGt(escrow.votingPower(tokenIdA), 0); - assertGt(escrow.votingPower(tokenIdB), 0); - - // vote for A then vote for B - vm.prank(personA); - voter.vote(tokenIdA, votes); - - uint256 expectedBVotingPowerGauge0 = (75 * escrow.votingPower(tokenIdB)) / 100; - uint256 expectedAVotingPowerGauge0 = (25 * escrow.votingPower(tokenIdA)) / 100; - uint256 expectedBVotingPowerGauge1 = (25 * escrow.votingPower(tokenIdB)) / 100; - uint256 expectedAVotingPowerGauge1 = (75 * escrow.votingPower(tokenIdA)) / 100; - - // flip the votes - votes[0] = GaugeVote(75, gauge); - votes[1] = GaugeVote(25, gauge2); - - uint aVotingPower = escrow.votingPower(tokenIdA); - uint bVotingPower = escrow.votingPower(tokenIdB); - - uint epoch = voter.epochId(); - // same vote for b - vm.startPrank(personB); - { - vm.expectEmit(true, true, true, true); - emit Voted({ - voter: personB, - gauge: gauge, - epoch: epoch, - tokenId: tokenIdB, - votingPowerCastForGauge: expectedBVotingPowerGauge0, - totalVotingPowerInGauge: expectedBVotingPowerGauge0 + expectedAVotingPowerGauge0, - totalVotingPowerInContract: expectedBVotingPowerGauge0 + aVotingPower, - timestamp: block.timestamp - }); - vm.expectEmit(true, true, true, true); - emit Voted({ - voter: personB, - gauge: gauge2, - epoch: epoch, - tokenId: tokenIdB, - votingPowerCastForGauge: expectedBVotingPowerGauge1, - totalVotingPowerInGauge: expectedBVotingPowerGauge1 + expectedAVotingPowerGauge1, - totalVotingPowerInContract: bVotingPower + aVotingPower, - timestamp: block.timestamp - }); - - voter.vote(tokenIdB, votes); - } - vm.stopPrank(); - - // go back and reset A to check - vm.startPrank(personA); - { - vm.expectEmit(true, true, true, true); - emit Reset({ - voter: personA, - gauge: gauge, - epoch: epoch, - tokenId: tokenIdA, - votingPowerRemovedFromGauge: expectedAVotingPowerGauge0, - totalVotingPowerInGauge: expectedBVotingPowerGauge0, - totalVotingPowerInContract: bVotingPower + expectedAVotingPowerGauge1, - timestamp: block.timestamp - }); - - vm.expectEmit(true, true, true, true); - emit Reset({ - voter: personA, - gauge: gauge2, - epoch: epoch, - tokenId: tokenIdA, - votingPowerRemovedFromGauge: expectedAVotingPowerGauge1, - totalVotingPowerInGauge: expectedBVotingPowerGauge1, - totalVotingPowerInContract: bVotingPower, - timestamp: block.timestamp - }); - - voter.reset(tokenIdA); - } - vm.stopPrank(); - } -} diff --git a/test/voting/GaugeVotingBase.sol b/test/voting/GaugeVotingBase.sol deleted file mode 100644 index 245c50d..0000000 --- a/test/voting/GaugeVotingBase.sol +++ /dev/null @@ -1,270 +0,0 @@ -pragma solidity ^0.8.17; - -import {Test} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -// aragon contracts -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO, PermissionManager} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {MockPluginSetupProcessor} from "@mocks/osx/MockPSP.sol"; -import {MockDAOFactory} from "@mocks/osx/MockDAOFactory.sol"; -import {MockERC20} from "@mocks/MockERC20.sol"; -import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; -import {ProxyLib} from "@libs/ProxyLib.sol"; - -import "@helpers/OSxHelpers.sol"; - -import {Clock} from "@clock/Clock.sol"; -import {ISimpleGaugeVoterStorageEventsErrors} from "src/voting/ISimpleGaugeVoter.sol"; -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; -import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; -import {VotingEscrow, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; - -contract GaugeVotingBase is - Test, - IGaugeVote, - IEscrowCurveTokenStorage, - ISimpleGaugeVoterStorageEventsErrors -{ - using ProxyLib for address; - - MultisigSetup multisigSetup; - SimpleGaugeVoterSetup voterSetup; - - // permissions - PermissionLib.MultiTargetPermission[] voterSetupPermissions; - - MockPluginSetupProcessor psp; - MockDAOFactory daoFactory; - MockERC20 token; - - Lock nftLock; - VotingEscrow escrow; - QuadraticIncreasingEscrow curve; - SimpleGaugeVoter voter; - ExitQueue queue; - - DAO dao; - Clock clock; - Multisig multisig; - - address deployer = address(0x420); - - uint256 constant COOLDOWN = 3 days; - - address voterBase; - - function setUp() public virtual { - // clock reset - vm.roll(0); - vm.warp(0); - - // Deploy the OSx framework - _deployOSX(); - // Deploy a DAO - _deployDAOAndMSig(); - - // new block for multisig - vm.roll(1); - // Define our pluginSetup contract to deploy the VE - _setupVoterContracts(); - _applySetup(); - - // unpause the contract - voter.unpause(); - } - - function _deployOSX() internal { - // deploy the mock PSP with the multisig plugin - multisigSetup = new MultisigSetup(); - psp = new MockPluginSetupProcessor(address(multisigSetup)); - daoFactory = new MockDAOFactory(psp); - } - - function _deployDAOAndMSig() internal { - // use the OSx DAO factory with the Plugin - address[] memory members = new address[](1); - members[0] = deployer; - - // encode a 1/1 multisig that can be adjusted later - bytes memory data = abi.encode( - members, - Multisig.MultisigSettings({onlyListed: true, minApprovals: 1}) - ); - - dao = daoFactory.createDao(_mockDAOSettings(), _mockPluginSettings(data)); - - // nonce 0 is something? - // nonce 1 is implementation contract - // nonce 2 is the msig contract behind the proxy - multisig = Multisig(computeAddress(address(multisigSetup), 2)); - } - - function _setupVoterContracts() public { - token = new MockERC20(); - - voterBase = address(new SimpleGaugeVoter()); - - // deploy setup - voterSetup = new SimpleGaugeVoterSetup( - voterBase, - address(new QuadraticIncreasingEscrow()), - address(new ExitQueue()), - address(new VotingEscrow()), - address(new Clock()), - address(new Lock()) - ); - - // push to the PSP - psp.queueSetup(address(voterSetup)); - - // prepare the installation - bytes memory data = abi.encode( - ISimpleGaugeVoterSetupParams({ - isPaused: true, - token: address(token), - veTokenName: "VE Token", - veTokenSymbol: "VE", - warmup: 3 days, - cooldown: 3 days, - feePercent: 0, - minLock: 1, - minDeposit: 1 wei - }) - ); - (address pluginAddress, IPluginSetup.PreparedSetupData memory preparedSetupData) = psp - .prepareInstallation(address(dao), _mockPrepareInstallationParams(data)); - - // fetch the contracts - voter = SimpleGaugeVoter(pluginAddress); - address[] memory helpers = preparedSetupData.helpers; - curve = QuadraticIncreasingEscrow(helpers[0]); - queue = ExitQueue(helpers[1]); - escrow = VotingEscrow(helpers[2]); - clock = Clock(helpers[3]); - nftLock = Lock(helpers[4]); - - // set the permissions - for (uint i = 0; i < preparedSetupData.permissions.length; i++) { - voterSetupPermissions.push(preparedSetupData.permissions[i]); - } - } - - function _actions() internal view returns (IDAO.Action[] memory) { - IDAO.Action[] memory actions = new IDAO.Action[](9); - - // action 0: apply the ve installation - actions[0] = IDAO.Action({ - to: address(psp), - value: 0, - data: abi.encodeCall( - psp.applyInstallation, - (address(dao), _mockApplyInstallationParams(address(escrow), voterSetupPermissions)) - ) - }); - - // action 2: activate the curve on the ve - actions[1] = IDAO.Action({ - to: address(escrow), - value: 0, - data: abi.encodeWithSelector(escrow.setCurve.selector, address(curve)) - }); - - // action 3: activate the queue on the ve - actions[2] = IDAO.Action({ - to: address(escrow), - value: 0, - data: abi.encodeWithSelector(escrow.setQueue.selector, address(queue)) - }); - - // action 4: set the voter - actions[3] = IDAO.Action({ - to: address(escrow), - value: 0, - data: abi.encodeWithSelector(escrow.setVoter.selector, address(voter)) - }); - - // action 5: set the nft lock - actions[4] = IDAO.Action({ - to: address(escrow), - value: 0, - data: abi.encodeWithSelector(escrow.setLockNFT.selector, address(nftLock)) - }); - - // for testing, give this contract the admin roles on all the periphery contracts - actions[5] = IDAO.Action({ - to: address(dao), - value: 0, - data: abi.encodeCall( - PermissionManager.grant, - (address(voter), address(this), voter.GAUGE_ADMIN_ROLE()) - ) - }); - - actions[6] = IDAO.Action({ - to: address(dao), - value: 0, - data: abi.encodeCall( - PermissionManager.grant, - (address(escrow), address(this), escrow.ESCROW_ADMIN_ROLE()) - ) - }); - - actions[7] = IDAO.Action({ - to: address(dao), - value: 0, - data: abi.encodeCall( - PermissionManager.grant, - (address(queue), address(this), queue.QUEUE_ADMIN_ROLE()) - ) - }); - - actions[8] = IDAO.Action({ - to: address(dao), - value: 0, - data: abi.encodeCall( - PermissionManager.grant, - (address(curve), address(this), curve.CURVE_ADMIN_ROLE()) - ) - }); - - return wrapGrantRevokeRoot(DAO(payable(address(dao))), address(psp), actions); - } - - function _applySetup() internal { - IDAO.Action[] memory actions = _actions(); - - // execute the actions - vm.startPrank(deployer); - { - multisig.createProposal({ - _metadata: "", - _actions: actions, - _allowFailureMap: 0, - _approveProposal: true, - _tryExecution: true, - _startDate: 0, - _endDate: uint64(block.timestamp + 1) - }); - } - vm.stopPrank(); - } - - function _authErr( - address _caller, - address _contract, - bytes32 _perm - ) internal view returns (bytes memory) { - return - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - _contract, - _caller, - _perm - ); - } -} diff --git a/test/voting/Setup.t.sol b/test/voting/Setup.t.sol deleted file mode 100644 index 586441d..0000000 --- a/test/voting/Setup.t.sol +++ /dev/null @@ -1,174 +0,0 @@ -pragma solidity ^0.8.17; - -import {Test} from "forge-std/Test.sol"; -import {console2 as console} from "forge-std/console2.sol"; - -// aragon contracts -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig, MultisigSetup} from "@aragon/multisig/MultisigSetup.sol"; - -import {MockPluginSetupProcessor} from "@mocks/osx/MockPSP.sol"; -import {MockDAOFactory} from "@mocks/osx/MockDAOFactory.sol"; -import {MockERC20} from "@mocks/MockERC20.sol"; - -import "@helpers/OSxHelpers.sol"; - -import {IEscrowCurveTokenStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; -import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; -import {VotingEscrow, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; - -import {GaugeVotingBase} from "./GaugeVotingBase.sol"; - -import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; - -contract VoterSetupTest is GaugeVotingBase { - error WrongHelpersArrayLength(uint256 length); - - function testInstallAndUninstall() public { - // Verify permissions have been granted - assertTrue(dao.hasPermission(address(voter), address(dao), voter.GAUGE_ADMIN_ROLE(), "")); - assertTrue( - dao.hasPermission(address(escrow), address(dao), escrow.ESCROW_ADMIN_ROLE(), "") - ); - assertTrue(dao.hasPermission(address(queue), address(dao), queue.QUEUE_ADMIN_ROLE(), "")); - assertTrue(dao.hasPermission(address(curve), address(dao), curve.CURVE_ADMIN_ROLE(), "")); - assertTrue( - dao.hasPermission( - address(voter), - address(dao), - voter.UPGRADE_PLUGIN_PERMISSION_ID(), - "" - ) - ); - assertTrue(dao.hasPermission(address(clock), address(dao), clock.CLOCK_ADMIN_ROLE(), "")); - assertTrue( - dao.hasPermission(address(nftLock), address(dao), nftLock.LOCK_ADMIN_ROLE(), "") - ); - assertTrue(dao.hasPermission(address(escrow), address(dao), escrow.PAUSER_ROLE(), "")); - assertTrue(dao.hasPermission(address(escrow), address(dao), escrow.SWEEPER_ROLE(), "")); - assertTrue(dao.hasPermission(address(queue), address(dao), queue.WITHDRAW_ROLE(), "")); - - // Prepare uninstallation and revoke permissions - IPluginSetup.SetupPayload memory payload = IPluginSetup.SetupPayload({ - plugin: address(voter), - currentHelpers: new address[](5), - data: bytes("") - }); - - payload.currentHelpers[0] = address(curve); - payload.currentHelpers[1] = address(queue); - payload.currentHelpers[2] = address(escrow); - payload.currentHelpers[3] = address(clock); - payload.currentHelpers[4] = address(nftLock); - - PermissionLib.MultiTargetPermission[] memory revokePermissions = voterSetup - .prepareUninstallation(address(dao), payload); - - vm.prank(address(dao)); - dao.applyMultiTargetPermissions(revokePermissions); - - // Verify permissions have been revoked - assertFalse(dao.hasPermission(address(voter), address(dao), voter.GAUGE_ADMIN_ROLE(), "")); - assertFalse( - dao.hasPermission(address(escrow), address(dao), escrow.ESCROW_ADMIN_ROLE(), "") - ); - assertFalse(dao.hasPermission(address(queue), address(dao), queue.QUEUE_ADMIN_ROLE(), "")); - assertFalse(dao.hasPermission(address(curve), address(dao), curve.CURVE_ADMIN_ROLE(), "")); - assertFalse( - dao.hasPermission( - address(voter), - address(dao), - voter.UPGRADE_PLUGIN_PERMISSION_ID(), - "" - ) - ); - assertFalse(dao.hasPermission(address(clock), address(dao), clock.CLOCK_ADMIN_ROLE(), "")); - assertFalse( - dao.hasPermission(address(nftLock), address(dao), nftLock.LOCK_ADMIN_ROLE(), "") - ); - assertFalse(dao.hasPermission(address(escrow), address(dao), escrow.PAUSER_ROLE(), "")); - assertFalse(dao.hasPermission(address(escrow), address(dao), escrow.SWEEPER_ROLE(), "")); - assertFalse(dao.hasPermission(address(queue), address(dao), queue.WITHDRAW_ROLE(), "")); - } - - function testCantPassIncorrectHelpers() public { - address[] memory currentHelpers = new address[](2); - currentHelpers[0] = address(curve); - currentHelpers[1] = address(queue); - - IPluginSetup.SetupPayload memory payload = IPluginSetup.SetupPayload({ - plugin: address(voter), - currentHelpers: currentHelpers, - data: bytes("") - }); - - vm.startPrank(address(dao)); - { - vm.expectRevert( - abi.encodeWithSelector(WrongHelpersArrayLength.selector, currentHelpers.length) - ); - voterSetup.prepareUninstallation(address(dao), payload); - } - vm.stopPrank(); - } - - function testInstallerAndUninstaller() public { - bool isPaused = true; - string memory veTokenName = "veTokenNameeeeee"; - string memory veTokenSymbol = "veTokenSymbollllll"; - address token = address(new MockERC20()); - uint48 warmup = 5 days; - uint48 cooldown = 7 days; - uint256 feePercent = 0.05e18; - uint48 minLock = 2 weeks; - uint minDeposit = 1e18; - - ISimpleGaugeVoterSetupParams memory params = ISimpleGaugeVoterSetupParams({ - isPaused: isPaused, - token: token, - veTokenName: veTokenName, - veTokenSymbol: veTokenSymbol, - warmup: warmup, - cooldown: cooldown, - feePercent: feePercent, - minLock: minLock, - minDeposit: minDeposit - }); - - bytes memory encodedStruct = voterSetup.encodeSetupData(params); - bytes32 encodedHash = keccak256(encodedStruct); - - bytes memory encodedArgs = voterSetup.encodeSetupData( - isPaused, - veTokenName, - veTokenSymbol, - token, - cooldown, - warmup, - feePercent, - minLock, - minDeposit - ); - bytes32 encodedArgsHash = keccak256(encodedArgs); - - assertEq(encodedHash, encodedArgsHash); - } - - function testImplementation() public view { - assertEq(voterSetup.implementation(), address(voterBase)); - } - - // coverage autism - function testConstructor() public { - new SimpleGaugeVoterSetup( - address(0), - address(0), - address(0), - address(0), - address(0), - address(0) - ); - } -}