├── .env.example ├── .gitattributes ├── .github └── workflows │ ├── lint.yaml │ └── main.yml ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── LICENSE.md ├── PERMISSIONS.md ├── README.md ├── SPECIFICATION.md ├── VOTINGESCROW.md ├── contracts ├── Aero.sol ├── AirdropDistributor.sol ├── EpochGovernor.sol ├── Minter.sol ├── Pool.sol ├── PoolFees.sol ├── ProtocolForwarder.sol ├── ProtocolGovernor.sol ├── RewardsDistributor.sol ├── Router.sol ├── VeArtProxy.sol ├── Voter.sol ├── VotingEscrow.sol ├── art │ ├── BokkyPooBahsDateTimeLibrary.sol │ ├── PerlinNoise.sol │ └── Trig.sol ├── factories │ ├── FactoryRegistry.sol │ ├── GaugeFactory.sol │ ├── ManagedRewardsFactory.sol │ ├── PoolFactory.sol │ └── VotingRewardsFactory.sol ├── gauges │ └── Gauge.sol ├── governance │ ├── GovernorCountingMajority.sol │ ├── GovernorSimple.sol │ ├── GovernorSimpleVotes.sol │ ├── IGovernor.sol │ ├── IVetoGovernor.sol │ ├── IVotes.sol │ ├── VetoGovernor.sol │ ├── VetoGovernorCountingSimple.sol │ ├── VetoGovernorVotes.sol │ └── VetoGovernorVotesQuorumFraction.sol ├── interfaces │ ├── IAero.sol │ ├── IAirdropDistributor.sol │ ├── IEpochGovernor.sol │ ├── IGauge.sol │ ├── IMinter.sol │ ├── IPool.sol │ ├── IPoolCallee.sol │ ├── IReward.sol │ ├── IRewardsDistributor.sol │ ├── IRouter.sol │ ├── IVeArtProxy.sol │ ├── IVoter.sol │ ├── IVotingEscrow.sol │ ├── IWETH.sol │ └── factories │ │ ├── IFactoryRegistry.sol │ │ ├── IGaugeFactory.sol │ │ ├── IManagedRewardsFactory.sol │ │ ├── IPoolFactory.sol │ │ └── IVotingRewardsFactory.sol ├── libraries │ ├── BalanceLogicLibrary.sol │ ├── DelegationLogicLibrary.sol │ ├── ProtocolTimeLibrary.sol │ └── SafeCastLibrary.sol └── rewards │ ├── BribeVotingReward.sol │ ├── FeesVotingReward.sol │ ├── FreeManagedReward.sol │ ├── LockedManagedReward.sol │ ├── ManagedReward.sol │ ├── Reward.sol │ └── VotingReward.sol ├── foundry.toml ├── funding.json ├── hardhat.config.ts ├── package.json ├── remappings.txt ├── script ├── DeployArtProxy.s.sol ├── DeployCore.s.sol ├── DeployGaugesAndPools.s.sol ├── DeployGovernors.s.sol ├── DistributeAirdrops.s.sol ├── README.md ├── constants │ ├── AirdropTEMPLATE.json │ ├── Base.json │ ├── TEMPLATE.json │ ├── airdrop-ci.json │ ├── airdrop.json │ ├── ci.json │ └── output │ │ ├── DeployArtProxy-Base.json │ │ ├── DeployCore-Base.json │ │ ├── DeployCore-ci.json │ │ ├── DeployGaugesAndPools-Base.json │ │ └── DeployGaugesAndPools-ci.json └── hardhat │ ├── DeployCore.ts │ ├── DeployGaugesAndPools.ts │ ├── DeployGovernors.ts │ ├── DistributeAirdrops.ts │ ├── README.md │ └── utils │ └── helpers.ts ├── test ├── Aero.t.sol ├── AirdropDistributor.t.sol ├── Base.sol ├── BaseTest.sol ├── BribeVotingReward.t.sol ├── Deploy.t.sol ├── EpochGovernor.t.sol ├── FactoryRegistry.t.sol ├── FeesVotingReward.t.sol ├── Forwarder.t.sol ├── FreeManagedReward.t.sol ├── Gauge.t.sol ├── Imbalance.t.sol ├── LockedManagedReward.t.sol ├── ManagedNft.t.sol ├── Minter.t.sol ├── MinterAirdrop.t.sol ├── Oracle.t.sol ├── Pool.t.sol ├── PoolFactory.t.sol ├── PoolFees.t.sol ├── ProtocolGovernor.t.sol ├── RewardsDistributor.t.sol ├── Router.t.sol ├── VeArtProxy.t.sol ├── Voter.t.sol ├── VotingEscrow.t.sol ├── WashTrade.t.sol ├── Zap.t.sol ├── e2e │ ├── DelegateTest.t.sol │ ├── ExtendedBaseTest.sol │ ├── ManagedNftFlow.t.sol │ ├── MinterTestFlow.t.sol │ ├── PokeVoteFlow.t.sol │ ├── SimpleBribeVotingRewardFlow.t.sol │ └── VotingEscrowTest.t.sol └── utils │ ├── ERC2771Helper.sol │ ├── MockERC20.sol │ ├── MockERC20WithTransferFee.sol │ ├── MockWETH.sol │ ├── SigUtils.sol │ └── TestOwner.sol ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY_DEPLOY=0x... 2 | 3 | CONSTANTS_FILENAME="Base.json" 4 | OUTPUT_FILENAME="Base.json" 5 | 6 | BASE_RPC_URL= 7 | BASE_SCAN_API_KEY= 8 | BASE_ETHERSCAN_VERIFIER_URL=https://api.basescan.org/api 9 | 10 | BASE_GOERLI_RPC_URL= 11 | BASE_GOERLI_SCAN_API_KEY= 12 | BASE_GOERLI_ETHERSCAN_VERIFIER_URL=https://api-goerli.basescan.org/api 13 | 14 | TENDERLY_RPC_URL= 15 | 16 | BASE_RPC_URL= 17 | BASE_SCAN_API_KEY= 18 | BASE_ETHERSCAN_VERIFIER_URL=https://api.basescan.org/api 19 | 20 | FORK_BLOCK_NUMBER=3161469 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.vy linguist-language=Python 2 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | solidity: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Check out github repository 15 | uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 1 18 | 19 | - name: Setup node.js 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: '18.x' 23 | 24 | - name: Set yarn cache directory path 25 | id: yarn-cache-dir-path 26 | run: echo "::set-output name=dir::$(yarn cache dir)" 27 | 28 | - name: Restore yarn cache 29 | uses: actions/cache@v2 30 | id: yarn-cache 31 | with: 32 | path: | 33 | ${{ steps.yarn-cache-dir-path.outputs.dir }} 34 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 35 | restore-keys: | 36 | ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 37 | ${{ runner.os }}-yarn- 38 | - name: Install node.js dependencies 39 | run: yarn --frozen-lockfile 40 | 41 | - name: Run formater check on *.sol and *.json 42 | run: yarn format:check 43 | 44 | # - name: run linter check on *.sol file 45 | # run: yarn lint -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | submodules: recursive 17 | 18 | - name: Install Foundry 19 | uses: foundry-rs/foundry-toolchain@v1 20 | with: 21 | version: nightly 22 | 23 | - name: "Build the contracts and print their size" 24 | run: | 25 | forge --version 26 | forge build --sizes 27 | 28 | - name: Run tests 29 | run: OUTPUT_FILENAME=ci.json CONSTANTS_FILENAME=Base.json AIRDROPS_FILENAME=airdrop-ci.json forge test -vvv 30 | env: 31 | BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} 32 | OPTIMISM_RPC_URL: ${{ secrets.OPTIMISM_RPC_URL }} 33 | PRIVATE_KEY_DEPLOY: ${{ secrets.PRIVATE_KEY_DEPLOY }} 34 | 35 | - name: slither-static-analysis 36 | continue-on-error: true 37 | uses: luisfontes19/slither-static-analysis-action@v0.3.4 38 | 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | 3 | # Foundry 4 | cache/ 5 | out/ 6 | broadcast/ 7 | 8 | # JS 9 | node_modules/ 10 | package-lock.json 11 | 12 | # Hardhat 13 | solidity-files-cache.json 14 | deployments/ 15 | 16 | artifacts/ 17 | 18 | # Coverage 19 | lcov.info -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | [submodule "lib/forge-std"] 5 | path = lib/forge-std 6 | url = https://github.com/foundry-rs/forge-std 7 | [submodule "lib/openzeppelin-contracts"] 8 | path = lib/openzeppelin-contracts 9 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 10 | [submodule "lib/gsn"] 11 | path = lib/gsn 12 | url = https://github.com/opengsn/gsn 13 | branch = v2.2.5 14 | [submodule "lib/v3-core"] 15 | path = lib/v3-core 16 | url = https://github.com/uniswap/v3-core 17 | branch = v1.0.0 18 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | VeArtProxy.sol 2 | art -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "**.sol", 5 | "options": { 6 | "printWidth": 120, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "bracketSpacing": false 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['test'], 3 | }; 4 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": [], 4 | "rules": { 5 | "compiler-version": ["error", "^0.8.19"], 6 | "avoid-suicide": "error", 7 | "avoid-sha3": "warn" 8 | } 9 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Business Source License 1.1 2 | 3 | License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. 4 | "Business Source License" is a trademark of MariaDB Corporation Ab. 5 | 6 | ----------------------------------------------------------------------------- 7 | 8 | Parameters 9 | 10 | Licensor: Perpetual Cyclist Services LLC 11 | 12 | Licensed Work: Velodrome Smart Contracts 13 | The Licensed Work is (c) 2023 Perpetual Cyclist Services LLC 14 | 15 | Additional Use Grant: Any uses listed and defined at 16 | v2-license-grants.velodrome.eth 17 | 18 | Change Date: The earlier of 2025-06-01 or a date specified at 19 | v2-license-date.velodrome.eth 20 | 21 | Change License: GNU General Public License v2.0 or later 22 | 23 | ----------------------------------------------------------------------------- 24 | 25 | Terms 26 | 27 | The Licensor hereby grants you the right to copy, modify, create derivative 28 | works, redistribute, and make non-production use of the Licensed Work. The 29 | Licensor may make an Additional Use Grant, above, permitting limited 30 | production use. 31 | 32 | Effective on the Change Date, or the fourth anniversary of the first publicly 33 | available distribution of a specific version of the Licensed Work under this 34 | License, whichever comes first, the Licensor hereby grants you rights under 35 | the terms of the Change License, and the rights granted in the paragraph 36 | above terminate. 37 | 38 | If your use of the Licensed Work does not comply with the requirements 39 | currently in effect as described in this License, you must purchase a 40 | commercial license from the Licensor, its affiliated entities, or authorized 41 | resellers, or you must refrain from using the Licensed Work. 42 | 43 | All copies of the original and modified Licensed Work, and derivative works 44 | of the Licensed Work, are subject to this License. This License applies 45 | separately for each version of the Licensed Work and the Change Date may vary 46 | for each version of the Licensed Work released by Licensor. 47 | 48 | You must conspicuously display this License on each original or modified copy 49 | of the Licensed Work. If you receive the Licensed Work in original or 50 | modified form from a third party, the terms and conditions set forth in this 51 | License apply to your use of that work. 52 | 53 | Any use of the Licensed Work in violation of this License will automatically 54 | terminate your rights under this License for the current and all other 55 | versions of the Licensed Work. 56 | 57 | This License does not grant you any right in any trademark or logo of 58 | Licensor or its affiliates (provided that you may use a trademark or logo of 59 | Licensor as expressly required by this License). 60 | 61 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON 62 | AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, 63 | EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF 64 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND 65 | TITLE. 66 | 67 | MariaDB hereby grants you permission to use this License’s text to license 68 | your works, and to refer to it using the trademark "Business Source License", 69 | as long as you comply with the Covenants of Licensor below. 70 | 71 | ----------------------------------------------------------------------------- 72 | 73 | Covenants of Licensor 74 | 75 | In consideration of the right to use this License’s text and the "Business 76 | Source License" name and trademark, Licensor covenants to MariaDB, and to all 77 | other recipients of the licensed work to be provided by Licensor: 78 | 79 | 1. To specify as the Change License the GPL Version 2.0 or any later version, 80 | or a license that is compatible with GPL Version 2.0 or a later version, 81 | where "compatible" means that software provided under the Change License can 82 | be included in a program with software provided under GPL Version 2.0 or a 83 | later version. Licensor may specify additional Change Licenses without 84 | limitation. 85 | 86 | 2. To either: (a) specify an additional grant of rights to use that does not 87 | impose any additional restriction on the right granted in this License, as 88 | the Additional Use Grant; or (b) insert the text "None". 89 | 90 | 3. To specify a Change Date. 91 | 92 | 4. Not to modify this License in any other way. 93 | 94 | ----------------------------------------------------------------------------- 95 | 96 | Notice 97 | 98 | The Business Source License (this document, or the "License") is not an Open 99 | Source license. However, the Licensed Work will eventually be made available 100 | under an Open Source License, as stated in this License. 101 | -------------------------------------------------------------------------------- /VOTINGESCROW.md: -------------------------------------------------------------------------------- 1 | # VotingEscrow 2 | 3 | ## State Transitions 4 | 5 | This table has functions along the rows, with the state required to call the function. Side effects and the output of the function are listed in the boxes. An empty box means that that state cannot be used as an input into that function. 6 | 7 | For example, if we take "Empty State" and `createLock`, this means that given no input, a call to `createManagedLockFor` will create a managed permanent nft. 8 | 9 | - Mint refers to the mint event. Burn refers to the burn event. Convert implies state transition from one state to another (e.g. normal to normal permanent). 10 | - Increases/decreases amount refers to `LockedBalance.amount` being increased/decreased. 11 | - Extends locktime refers to `LockedBalance.end` being extended. 12 | - Delegating involves modifying the `delegatee` field in the latest `tokenId`'s checkpoint, as well as incrementing the `delegatee`'s `delegatedBalance` field in the `delegatee`'s latest checkpoint. 13 | - Dedelegating involves deleting the `delegatee` field in the latest `tokenId`'s checkpoint, as well as decrementing the prior `delegatee`'s `delegatedBalance` field in the `delegatee`'s latest checkpoint. 14 | - Note that for `merge`, only the `to` NFT is allowed to be of type normal permanent. 15 | - The last two rows refer to functions that exist in `RewardsDistributor`. 16 | 17 | | Function | Empty State | Normal | Normal Permanent | Locked | Managed Permanent | 18 | | --- | --- | --- | --- | --- | --- | 19 | | `createLock` | - Mints normal. | | | | | 20 | | `createLockFor` | - Mints normal. | | | | | 21 | | `createManagedLockFor` | - Mints managed permanent.| | | | | 22 | | `depositManaged` | | - Converts to locked.
- Increases managed amount.
- Increases (managed) delegatee balance. | - Dedelegates.
- Converts to locked.
- Increases managed amount.
- Increases (managed) delegatee balance. | | | 23 | | `withdrawManaged` | | | | - Converts to normal.
- May increase amount (locked rewards).
- Extends locktime to maximum.
- Decreases managed balance.
- Decreases (managed) delegatee balance. | | 24 | | `depositFor` | | - Increases amount. | - Increases amount.
- Increases delegatee balance. | | - Increases amount.
- Deposits into LMR.
- Increases delegatee balance. | 25 | | `increaseAmount` | | - Increases amount. | - Increases amount.
- Increases delegatee balance. | | - Increases amount.
- Increases delegatee balance. | 26 | | `increaseUnlockTime` | | - Extends locktime. | | | | 27 | | `withdraw` | | - Burns normal. | | | | 28 | | `merge` | | - Burns `from` normal.
- Increases `to` amount. | - Burns `from` normal.
- Increases `to` amount.
- Increases delegatee balance. | | | 29 | | `split` | | - Burns normal.
- Mints two normal. | - Dedelegates
- Burns normal.
- Mints two normal. | | | 30 | | `lockPermanent` | | - Converts to normal permanent. | | | | 31 | | `unlockPermanent` | | | - Dedelegates
- Converts to normal. | | | 32 | | `delegate` | | | - Dedelegates.
- Delegates. | | - Dedelegates.
- Delegates. | 33 | | `delegateBySig` | | | - Dedelegates.
- Delegates. | | - Dedelegates.
- Delegates. | 34 | | `distributor.claim` | | - Collect rebases. | - Collect rebases. | | - Collect rebases. | 35 | | `distributor.claimMany` | | - Collect rebases. | - Collect rebases. | | - Collect rebases. | 36 | 37 | Note that when a normal nft calls functions like `depositFor`, `increaseAmount` etc, dedelegation still occurs but is skipped as the `tokenId` has no delegatee. 38 | 39 | ## Valid States 40 | 41 | LockedBalance has several valid states: 42 | - `LockedBalance(amount, locktime, false)` when an nft is in normal state. `locktime` is at most `block.timestamp + MAXTIME`. 43 | - `LockedBalance(amount, 0, true)`when an nft is in normal permanent. 44 | - `LockedBalance(0, 0, true)` when a managed nft has no deposits. 45 | - `LockedBalance(0, 0, false)` when an nft is burnt or in locked state. 46 | 47 | A token's `LockedBalance` is updated whenever `amount`, `end` or `isPermanent` changes. 48 | 49 | UserPoint has several valid states: 50 | - `UserPoint(slope, bias, ts, blk, 0)` when an nft is in normal state. 51 | - `UserPoint(0, 0, ts, blk, permanent)` when an nft is in permanent state, with the value of `permanent` equal to the nft's `LockedBalance.amount`. 52 | - `UserPoint(0, 0, ts, blk, 0)` when an nft is burnt. 53 | 54 | A token's UserPoint is updated whenever `LockedBalance` changes. The global point is also updated at the 55 | same time. If there are multiple writes to a token's UserPoint or the GlobalPoint in one block, it will 56 | overwrite the prior point. Points retrieved from the user point history and global point history will always 57 | have unique timestamps. For user points and global points, the first checkpoint at index 0 is always empty. 58 | The first point is written to index 1. A UserPoints for a certain `tokenId` will always have a unique 59 | timestamp and block number. The same is true for GlobalPoints. 60 | 61 | (Voting) checkpoints have several valid states: 62 | - `Checkpoint(ts, owner, 0, 0)` when an nft is not delegating (i.e. in normal / normal permanent / locked / managed permanent state and is not delegating) and has not been delegated to. 63 | - `Checkpoint(ts, owner, delegatedBalance, 0)`when an nft is not delegating (i.e. in normal / normal permanent / locked / managed permanent state and is not delegating) but has been delegated to. 64 | - `Checkpoint(ts, owner, 0, delegatee)` when an nft is delegating (permanent locks only) and has not received any delegations. 65 | - `Checkpoint(ts, owner, delegatedBalance, delegatee)` when an nft is delegating (permanent locks only) and has received delegations. 66 | - `Checkpoint(ts, 0, delegatedBalance, 0)` when an nft is burnt. `delegatedBalance` may be non-zero as this nft may still be delegated to. But it cannot vote as it has no owner. 67 | 68 | The initial voting checkpoint is created on the minting of a token. Subsequent voting checkpoints are 69 | created whenever the `owner`, `delegatedBalance` or `delegatee` changes. Ownership can change from 70 | transfers, or from burn (e.g. merge, split, withdraw). `delegatee` can change from delegation / dedelegation. 71 | `delegatedBalance` is updated on the delegatee whenever a delegation / dedelegation takes place. If there are 72 | multiple writes to a token's voting checkpoints in one block, it will overwrite the prior point. Points 73 | retrieved from the checkpoint history will always have unique timestamps. For voting checkpoints, the first 74 | checkpoint is written to index 0. -------------------------------------------------------------------------------- /contracts/Aero.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import {IAero} from "./interfaces/IAero.sol"; 5 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; 7 | 8 | /// @title Aero 9 | /// @author velodrome.finance 10 | /// @notice The native token in the Protocol ecosystem 11 | /// @dev Emitted by the Minter 12 | contract Aero is IAero, ERC20Permit { 13 | address public minter; 14 | address private owner; 15 | 16 | constructor() ERC20("Aerodrome", "AERO") ERC20Permit("Aerodrome") { 17 | minter = msg.sender; 18 | owner = msg.sender; 19 | } 20 | 21 | /// @dev No checks as its meant to be once off to set minting rights to BaseV1 Minter 22 | function setMinter(address _minter) external { 23 | if (msg.sender != minter) revert NotMinter(); 24 | minter = _minter; 25 | } 26 | 27 | function mint(address account, uint256 amount) external returns (bool) { 28 | if (msg.sender != minter) revert NotMinter(); 29 | _mint(account, amount); 30 | return true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/AirdropDistributor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IAero} from "./interfaces/IAero.sol"; 5 | import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol"; 6 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 7 | import {IAirdropDistributor} from "./interfaces/IAirdropDistributor.sol"; 8 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 9 | 10 | contract AirdropDistributor is IAirdropDistributor, Ownable { 11 | using SafeERC20 for IAero; 12 | /// @inheritdoc IAirdropDistributor 13 | IAero public immutable aero; 14 | /// @inheritdoc IAirdropDistributor 15 | IVotingEscrow public immutable ve; 16 | 17 | constructor(address _ve) { 18 | ve = IVotingEscrow(_ve); 19 | aero = IAero(IVotingEscrow(_ve).token()); 20 | } 21 | 22 | /// @inheritdoc IAirdropDistributor 23 | function distributeTokens(address[] memory _wallets, uint256[] memory _amounts) external override onlyOwner { 24 | uint256 _len = _wallets.length; 25 | if (_len != _amounts.length) revert InvalidParams(); 26 | uint256 _sum; 27 | for (uint256 i = 0; i < _len; i++) { 28 | _sum += _amounts[i]; 29 | } 30 | 31 | if (_sum > aero.balanceOf(address(this))) revert InsufficientBalance(); 32 | aero.safeApprove(address(ve), _sum); 33 | address _wallet; 34 | uint256 _amount; 35 | uint256 _tokenId; 36 | for (uint256 i = 0; i < _len; i++) { 37 | _wallet = _wallets[i]; 38 | _amount = _amounts[i]; 39 | _tokenId = ve.createLock(_amount, 1 weeks); 40 | ve.lockPermanent(_tokenId); 41 | ve.safeTransferFrom(address(this), _wallet, _tokenId); 42 | emit Airdrop(_wallet, _amount, _tokenId); 43 | } 44 | aero.safeApprove(address(ve), 0); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/EpochGovernor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IVotes} from "./governance/IVotes.sol"; 5 | 6 | import {IGovernor} from "./governance/IGovernor.sol"; 7 | import {GovernorSimple} from "./governance/GovernorSimple.sol"; 8 | import {GovernorCountingMajority} from "./governance/GovernorCountingMajority.sol"; 9 | import {GovernorSimpleVotes} from "./governance/GovernorSimpleVotes.sol"; 10 | 11 | /** 12 | * @title EpochGovernor 13 | * @notice Epoch based governance system that allows for a three option majority (against, for, abstain). 14 | * @notice Refer to SPECIFICATION.md. 15 | * @author velodrome.finance, @figs999, @pegahcarter 16 | * @dev Note that hash proposals are unique per epoch, but calls to a function with different values 17 | * may be allowed any number of times. It is best to use EpochGovernor with a function that accepts 18 | * no values. 19 | */ 20 | contract EpochGovernor is GovernorSimple, GovernorCountingMajority, GovernorSimpleVotes { 21 | constructor( 22 | address _forwarder, 23 | IVotes _ve, 24 | address _minter 25 | ) GovernorSimple(_forwarder, "Epoch Governor", _minter) GovernorSimpleVotes(_ve) {} 26 | 27 | function votingDelay() public pure override(IGovernor) returns (uint256) { 28 | return (15 minutes); 29 | } 30 | 31 | function votingPeriod() public pure override(IGovernor) returns (uint256) { 32 | return (1 weeks); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/PoolFees.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | /// @title PoolFees 8 | /// @notice Contract used as 1:1 pool relationship to split out fees. 9 | /// @notice Ensures curve does not need to be modified for LP shares. 10 | contract PoolFees { 11 | using SafeERC20 for IERC20; 12 | address internal immutable pool; // The pool it is bonded to 13 | address internal immutable token0; // token0 of pool, saved localy and statically for gas optimization 14 | address internal immutable token1; // Token1 of pool, saved localy and statically for gas optimization 15 | 16 | error NotPool(); 17 | 18 | constructor(address _token0, address _token1) { 19 | pool = msg.sender; 20 | token0 = _token0; 21 | token1 = _token1; 22 | } 23 | 24 | /// @notice Allow the pool to transfer fees to users 25 | function claimFeesFor(address _recipient, uint256 _amount0, uint256 _amount1) external { 26 | if (msg.sender != pool) revert NotPool(); 27 | if (_amount0 > 0) IERC20(token0).safeTransfer(_recipient, _amount0); 28 | if (_amount1 > 0) IERC20(token1).safeTransfer(_recipient, _amount1); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/ProtocolForwarder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import {Forwarder} from "@opengsn/contracts/src/forwarder/Forwarder.sol"; 5 | 6 | contract ProtocolForwarder is Forwarder { 7 | constructor() Forwarder() {} 8 | } 9 | -------------------------------------------------------------------------------- /contracts/ProtocolGovernor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IVotes} from "./governance/IVotes.sol"; 5 | import {IVotingEscrow} from "contracts/interfaces/IVotingEscrow.sol"; 6 | 7 | import {IVetoGovernor} from "./governance/IVetoGovernor.sol"; 8 | import {VetoGovernor} from "./governance/VetoGovernor.sol"; 9 | import {VetoGovernorCountingSimple} from "./governance/VetoGovernorCountingSimple.sol"; 10 | import {VetoGovernorVotes} from "./governance/VetoGovernorVotes.sol"; 11 | import {VetoGovernorVotesQuorumFraction} from "./governance/VetoGovernorVotesQuorumFraction.sol"; 12 | 13 | /// @title ProtocolGovernor 14 | /// @author velodrome.finance, @figs999, @pegahcarter 15 | /// @notice Protocol governance with timestamp-based voting power from VotingEscrow NFTs 16 | /// Supports vetoing of proposals as mitigation for 51% attacks 17 | /// Votes are cast and counted on a per tokenId basis 18 | contract ProtocolGovernor is 19 | VetoGovernor, 20 | VetoGovernorCountingSimple, 21 | VetoGovernorVotes, 22 | VetoGovernorVotesQuorumFraction 23 | { 24 | address public immutable ve; 25 | address public vetoer; 26 | address public pendingVetoer; 27 | uint256 public constant MAX_PROPOSAL_NUMERATOR = 500; // max 5% 28 | uint256 public constant PROPOSAL_DENOMINATOR = 10_000; 29 | uint256 public proposalNumerator = 2; // start at 0.02% 30 | 31 | error NotTeam(); 32 | error NotPendingVetoer(); 33 | error NotVetoer(); 34 | error ProposalNumeratorTooHigh(); 35 | error ZeroAddress(); 36 | 37 | constructor( 38 | IVotes _ve 39 | ) 40 | VetoGovernor("Protocol Governor") 41 | VetoGovernorVotes(_ve) 42 | VetoGovernorVotesQuorumFraction(4) // 4% 43 | { 44 | ve = address(_ve); 45 | vetoer = msg.sender; 46 | } 47 | 48 | function votingDelay() public pure override(IVetoGovernor) returns (uint256) { 49 | return (15 minutes); 50 | } 51 | 52 | function votingPeriod() public pure override(IVetoGovernor) returns (uint256) { 53 | return (1 weeks); 54 | } 55 | 56 | function setProposalNumerator(uint256 numerator) external { 57 | if (msg.sender != IVotingEscrow(ve).team()) revert NotTeam(); 58 | if (numerator > MAX_PROPOSAL_NUMERATOR) revert ProposalNumeratorTooHigh(); 59 | proposalNumerator = numerator; 60 | } 61 | 62 | function proposalThreshold() public view override(VetoGovernor) returns (uint256) { 63 | return (token.getPastTotalSupply(block.timestamp - 1) * proposalNumerator) / PROPOSAL_DENOMINATOR; 64 | } 65 | 66 | /// @dev Vetoer can be removed once the risk of a 51% attack becomes unfeasible. 67 | /// This can be done by transferring ownership of vetoer to a contract that is "bricked" 68 | /// i.e. a non-zero address contract that is immutable with no ability to call this function. 69 | function setVetoer(address _vetoer) external { 70 | if (msg.sender != vetoer) revert NotVetoer(); 71 | if (_vetoer == address(0)) revert ZeroAddress(); 72 | pendingVetoer = _vetoer; 73 | } 74 | 75 | function acceptVetoer() external { 76 | if (msg.sender != pendingVetoer) revert NotPendingVetoer(); 77 | vetoer = pendingVetoer; 78 | delete pendingVetoer; 79 | } 80 | 81 | /// @notice Support for vetoer to protect against 51% attacks 82 | function veto(uint256 _proposalId) external { 83 | if (msg.sender != vetoer) revert NotVetoer(); 84 | _veto(_proposalId); 85 | } 86 | 87 | function renounceVetoer() external { 88 | if (msg.sender != vetoer) revert NotVetoer(); 89 | delete vetoer; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /contracts/factories/FactoryRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 5 | import {IFactoryRegistry} from "../interfaces/factories/IFactoryRegistry.sol"; 6 | import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; 7 | 8 | /// @title Protocol Factory Registry 9 | /// @author Carter Carlson (@pegahcarter) 10 | /// @notice Protocol Factory Registry to swap and create gauges 11 | contract FactoryRegistry is IFactoryRegistry, Ownable { 12 | using EnumerableSet for EnumerableSet.AddressSet; 13 | 14 | /// @dev factory to create free and locked rewards for a managed veNFT 15 | address private _managedRewardsFactory; 16 | 17 | /// @dev The protocol will always have a usable poolFactory, votingRewardsFactory, and gaugeFactory. The votingRewardsFactory 18 | // and gaugeFactory are defined to the poolFactory which can never be removed 19 | address public immutable fallbackPoolFactory; 20 | 21 | /// @dev Array of poolFactories used to create a gauge and votingRewards 22 | EnumerableSet.AddressSet private _poolFactories; 23 | 24 | struct FactoriesToPoolFactory { 25 | address votingRewardsFactory; 26 | address gaugeFactory; 27 | } 28 | /// @dev the factories linked to the poolFactory 29 | mapping(address => FactoriesToPoolFactory) private _factoriesToPoolsFactory; 30 | 31 | constructor( 32 | address _fallbackPoolFactory, 33 | address _fallbackVotingRewardsFactory, 34 | address _fallbackGaugeFactory, 35 | address _newManagedRewardsFactory 36 | ) { 37 | fallbackPoolFactory = _fallbackPoolFactory; 38 | 39 | approve(_fallbackPoolFactory, _fallbackVotingRewardsFactory, _fallbackGaugeFactory); 40 | setManagedRewardsFactory(_newManagedRewardsFactory); 41 | } 42 | 43 | /// @inheritdoc IFactoryRegistry 44 | function approve(address poolFactory, address votingRewardsFactory, address gaugeFactory) public onlyOwner { 45 | if (poolFactory == address(0) || votingRewardsFactory == address(0) || gaugeFactory == address(0)) 46 | revert ZeroAddress(); 47 | if (_poolFactories.contains(poolFactory)) revert PathAlreadyApproved(); 48 | 49 | FactoriesToPoolFactory memory usedFactories = _factoriesToPoolsFactory[poolFactory]; 50 | 51 | // If the poolFactory *has not* been approved before, can approve any gauge/votingRewards factory 52 | // Only one check is sufficient 53 | if (usedFactories.votingRewardsFactory == address(0)) { 54 | _factoriesToPoolsFactory[poolFactory] = FactoriesToPoolFactory(votingRewardsFactory, gaugeFactory); 55 | } else { 56 | // If the poolFactory *has* been approved before, can only approve the same used gauge/votingRewards factory to 57 | // to maintain state within Voter 58 | if ( 59 | votingRewardsFactory != usedFactories.votingRewardsFactory || gaugeFactory != usedFactories.gaugeFactory 60 | ) revert InvalidFactoriesToPoolFactory(); 61 | } 62 | 63 | _poolFactories.add(poolFactory); 64 | emit Approve(poolFactory, votingRewardsFactory, gaugeFactory); 65 | } 66 | 67 | /// @inheritdoc IFactoryRegistry 68 | function unapprove(address poolFactory) external onlyOwner { 69 | if (poolFactory == fallbackPoolFactory) revert FallbackFactory(); 70 | if (!_poolFactories.contains(poolFactory)) revert PathNotApproved(); 71 | _poolFactories.remove(poolFactory); 72 | (address votingRewardsFactory, address gaugeFactory) = factoriesToPoolFactory(poolFactory); 73 | emit Unapprove(poolFactory, votingRewardsFactory, gaugeFactory); 74 | } 75 | 76 | /// @inheritdoc IFactoryRegistry 77 | function setManagedRewardsFactory(address _newManagedRewardsFactory) public onlyOwner { 78 | if (_newManagedRewardsFactory == _managedRewardsFactory) revert SameAddress(); 79 | if (_newManagedRewardsFactory == address(0)) revert ZeroAddress(); 80 | _managedRewardsFactory = _newManagedRewardsFactory; 81 | emit SetManagedRewardsFactory(_newManagedRewardsFactory); 82 | } 83 | 84 | /// @inheritdoc IFactoryRegistry 85 | function managedRewardsFactory() external view returns (address) { 86 | return _managedRewardsFactory; 87 | } 88 | 89 | /// @inheritdoc IFactoryRegistry 90 | function factoriesToPoolFactory( 91 | address poolFactory 92 | ) public view returns (address votingRewardsFactory, address gaugeFactory) { 93 | FactoriesToPoolFactory memory f = _factoriesToPoolsFactory[poolFactory]; 94 | votingRewardsFactory = f.votingRewardsFactory; 95 | gaugeFactory = f.gaugeFactory; 96 | } 97 | 98 | /// @inheritdoc IFactoryRegistry 99 | function poolFactories() external view returns (address[] memory) { 100 | return _poolFactories.values(); 101 | } 102 | 103 | /// @inheritdoc IFactoryRegistry 104 | function isPoolFactoryApproved(address poolFactory) external view returns (bool) { 105 | return _poolFactories.contains(poolFactory); 106 | } 107 | 108 | /// @inheritdoc IFactoryRegistry 109 | function poolFactoriesLength() external view returns (uint256) { 110 | return _poolFactories.length(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /contracts/factories/GaugeFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IGaugeFactory} from "../interfaces/factories/IGaugeFactory.sol"; 5 | import {Gauge} from "../gauges/Gauge.sol"; 6 | 7 | contract GaugeFactory is IGaugeFactory { 8 | function createGauge( 9 | address _forwarder, 10 | address _pool, 11 | address _feesVotingReward, 12 | address _rewardToken, 13 | bool isPool 14 | ) external returns (address gauge) { 15 | gauge = address(new Gauge(_forwarder, _pool, _feesVotingReward, _rewardToken, msg.sender, isPool)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/factories/ManagedRewardsFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IManagedRewardsFactory} from "../interfaces/factories/IManagedRewardsFactory.sol"; 5 | import {FreeManagedReward} from "../rewards/FreeManagedReward.sol"; 6 | import {LockedManagedReward} from "../rewards/LockedManagedReward.sol"; 7 | 8 | contract ManagedRewardsFactory is IManagedRewardsFactory { 9 | /// @inheritdoc IManagedRewardsFactory 10 | function createRewards( 11 | address _forwarder, 12 | address _voter 13 | ) external returns (address lockedManagedReward, address freeManagedReward) { 14 | lockedManagedReward = address(new LockedManagedReward(_forwarder, _voter)); 15 | freeManagedReward = address(new FreeManagedReward(_forwarder, _voter)); 16 | emit ManagedRewardCreated(_voter, lockedManagedReward, freeManagedReward); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/factories/PoolFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IPoolFactory} from "../interfaces/factories/IPoolFactory.sol"; 5 | import {IPool} from "../interfaces/IPool.sol"; 6 | import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; 7 | 8 | contract PoolFactory is IPoolFactory { 9 | address public immutable implementation; 10 | 11 | bool public isPaused; 12 | address public pauser; 13 | 14 | uint256 public stableFee; 15 | uint256 public volatileFee; 16 | uint256 public constant MAX_FEE = 300; // 3% 17 | // Override to indicate there is custom 0% fee - as a 0 value in the customFee mapping indicates 18 | // that no custom fee rate has been set 19 | uint256 public constant ZERO_FEE_INDICATOR = 420; 20 | address public feeManager; 21 | 22 | /// @dev used to change the name/symbol of the pool by calling emergencyCouncil 23 | address public voter; 24 | 25 | mapping(address => mapping(address => mapping(bool => address))) private _getPool; 26 | address[] public allPools; 27 | mapping(address => bool) private _isPool; // simplified check if its a pool, given that `stable` flag might not be available in peripherals 28 | mapping(address => uint256) public customFee; // override for custom fees 29 | 30 | address internal _temp0; 31 | address internal _temp1; 32 | bool internal _temp; 33 | 34 | constructor(address _implementation) { 35 | implementation = _implementation; 36 | voter = msg.sender; 37 | pauser = msg.sender; 38 | feeManager = msg.sender; 39 | isPaused = false; 40 | stableFee = 5; // 0.05% 41 | volatileFee = 30; // 0.3% 42 | } 43 | 44 | /// @inheritdoc IPoolFactory 45 | function allPoolsLength() external view returns (uint256) { 46 | return allPools.length; 47 | } 48 | 49 | /// @inheritdoc IPoolFactory 50 | function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address) { 51 | return fee > 1 ? address(0) : fee == 1 ? _getPool[tokenA][tokenB][true] : _getPool[tokenA][tokenB][false]; 52 | } 53 | 54 | /// @inheritdoc IPoolFactory 55 | function getPool(address tokenA, address tokenB, bool stable) external view returns (address) { 56 | return _getPool[tokenA][tokenB][stable]; 57 | } 58 | 59 | /// @inheritdoc IPoolFactory 60 | function isPool(address pool) external view returns (bool) { 61 | return _isPool[pool]; 62 | } 63 | 64 | /// @inheritdoc IPoolFactory 65 | function setVoter(address _voter) external { 66 | if (msg.sender != voter) revert NotVoter(); 67 | voter = _voter; 68 | emit SetVoter(_voter); 69 | } 70 | 71 | function setPauser(address _pauser) external { 72 | if (msg.sender != pauser) revert NotPauser(); 73 | if (_pauser == address(0)) revert ZeroAddress(); 74 | pauser = _pauser; 75 | emit SetPauser(_pauser); 76 | } 77 | 78 | function setPauseState(bool _state) external { 79 | if (msg.sender != pauser) revert NotPauser(); 80 | isPaused = _state; 81 | emit SetPauseState(_state); 82 | } 83 | 84 | function setFeeManager(address _feeManager) external { 85 | if (msg.sender != feeManager) revert NotFeeManager(); 86 | if (_feeManager == address(0)) revert ZeroAddress(); 87 | feeManager = _feeManager; 88 | emit SetFeeManager(_feeManager); 89 | } 90 | 91 | /// @inheritdoc IPoolFactory 92 | function setFee(bool _stable, uint256 _fee) external { 93 | if (msg.sender != feeManager) revert NotFeeManager(); 94 | if (_fee > MAX_FEE) revert FeeTooHigh(); 95 | if (_fee == 0) revert ZeroFee(); 96 | if (_stable) { 97 | stableFee = _fee; 98 | } else { 99 | volatileFee = _fee; 100 | } 101 | } 102 | 103 | /// @inheritdoc IPoolFactory 104 | function setCustomFee(address pool, uint256 fee) external { 105 | if (msg.sender != feeManager) revert NotFeeManager(); 106 | if (fee > MAX_FEE && fee != ZERO_FEE_INDICATOR) revert FeeTooHigh(); 107 | if (!_isPool[pool]) revert InvalidPool(); 108 | 109 | customFee[pool] = fee; 110 | emit SetCustomFee(pool, fee); 111 | } 112 | 113 | /// @inheritdoc IPoolFactory 114 | function getFee(address pool, bool _stable) public view returns (uint256) { 115 | uint256 fee = customFee[pool]; 116 | return fee == ZERO_FEE_INDICATOR ? 0 : fee != 0 ? fee : _stable ? stableFee : volatileFee; 117 | } 118 | 119 | /// @inheritdoc IPoolFactory 120 | function createPool(address tokenA, address tokenB, uint24 fee) external returns (address pool) { 121 | if (fee > 1) revert FeeInvalid(); 122 | bool stable = fee == 1; 123 | return createPool(tokenA, tokenB, stable); 124 | } 125 | 126 | /// @inheritdoc IPoolFactory 127 | function createPool(address tokenA, address tokenB, bool stable) public returns (address pool) { 128 | if (tokenA == tokenB) revert SameAddress(); 129 | (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 130 | if (token0 == address(0)) revert ZeroAddress(); 131 | if (_getPool[token0][token1][stable] != address(0)) revert PoolAlreadyExists(); 132 | bytes32 salt = keccak256(abi.encodePacked(token0, token1, stable)); // salt includes stable as well, 3 parameters 133 | pool = Clones.cloneDeterministic(implementation, salt); 134 | IPool(pool).initialize(token0, token1, stable); 135 | _getPool[token0][token1][stable] = pool; 136 | _getPool[token1][token0][stable] = pool; // populate mapping in the reverse direction 137 | allPools.push(pool); 138 | _isPool[pool] = true; 139 | emit PoolCreated(token0, token1, stable, pool, allPools.length); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /contracts/factories/VotingRewardsFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IVotingRewardsFactory} from "../interfaces/factories/IVotingRewardsFactory.sol"; 5 | import {FeesVotingReward} from "../rewards/FeesVotingReward.sol"; 6 | import {BribeVotingReward} from "../rewards/BribeVotingReward.sol"; 7 | 8 | contract VotingRewardsFactory is IVotingRewardsFactory { 9 | /// @inheritdoc IVotingRewardsFactory 10 | function createRewards( 11 | address _forwarder, 12 | address[] memory _rewards 13 | ) external returns (address feesVotingReward, address bribeVotingReward) { 14 | feesVotingReward = address(new FeesVotingReward(_forwarder, msg.sender, _rewards)); 15 | bribeVotingReward = address(new BribeVotingReward(_forwarder, msg.sender, _rewards)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/governance/GovernorCountingMajority.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {GovernorSimple} from "./GovernorSimple.sol"; 5 | 6 | /** 7 | * @dev Modified lightly from OpenZeppelin's GovernorCountingSimple to support a simple three option majority. 8 | * 9 | */ 10 | abstract contract GovernorCountingMajority is GovernorSimple { 11 | /** 12 | * @dev Supported vote types. Matches Governor Bravo ordering. 13 | */ 14 | enum VoteType { 15 | Against, 16 | For, 17 | Abstain 18 | } 19 | 20 | struct ProposalVote { 21 | uint256 againstVotes; 22 | uint256 forVotes; 23 | uint256 abstainVotes; 24 | mapping(uint256 => bool) hasVoted; 25 | } 26 | 27 | mapping(uint256 => ProposalVote) private _proposalVotes; 28 | 29 | /** 30 | * @dev See {IGovernor-COUNTING_MODE}. 31 | */ 32 | // solhint-disable-next-line func-name-mixedcase 33 | function COUNTING_MODE() public pure virtual override returns (string memory) { 34 | return "support=bravo&quorum=for,abstain"; 35 | } 36 | 37 | /** 38 | * @dev See {IGovernor-hasVoted}. 39 | */ 40 | function hasVoted(uint256 proposalId, uint256 tokenId) public view virtual override returns (bool) { 41 | return _proposalVotes[proposalId].hasVoted[tokenId]; 42 | } 43 | 44 | /** 45 | * @dev Accessor to the internal vote counts. 46 | */ 47 | function proposalVotes( 48 | uint256 proposalId 49 | ) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { 50 | ProposalVote storage proposalVote = _proposalVotes[proposalId]; 51 | return (proposalVote.againstVotes, proposalVote.forVotes, proposalVote.abstainVotes); 52 | } 53 | 54 | /** 55 | * @dev Select winner of majority vote. 56 | */ 57 | function _selectWinner(uint256 proposalId) internal view override returns (ProposalState) { 58 | ProposalVote storage proposalVote = _proposalVotes[proposalId]; 59 | uint256 againstVotes = proposalVote.againstVotes; 60 | uint256 forVotes = proposalVote.forVotes; 61 | uint256 abstainVotes = proposalVote.abstainVotes; 62 | if ((againstVotes > forVotes) && (againstVotes > abstainVotes)) { 63 | return ProposalState.Defeated; 64 | } else if ((forVotes > againstVotes) && (forVotes > abstainVotes)) { 65 | return ProposalState.Succeeded; 66 | } else { 67 | return ProposalState.Expired; 68 | } 69 | } 70 | 71 | /** 72 | * @dev See {Governor-_countVote}. In this module, the support follows the `VoteType` enum (from Governor Bravo). 73 | */ 74 | function _countVote( 75 | uint256 proposalId, 76 | uint256 tokenId, 77 | uint8 support, 78 | uint256 weight, 79 | bytes memory // params 80 | ) internal virtual override { 81 | ProposalVote storage proposalVote = _proposalVotes[proposalId]; 82 | 83 | require(!proposalVote.hasVoted[tokenId], "GovernorVotingSimple: vote already cast"); 84 | require(weight > 0, "GovernorVotingSimple: zero voting weight"); 85 | proposalVote.hasVoted[tokenId] = true; 86 | 87 | if (support == uint8(VoteType.Against)) { 88 | proposalVote.againstVotes += weight; 89 | } else if (support == uint8(VoteType.For)) { 90 | proposalVote.forVotes += weight; 91 | } else if (support == uint8(VoteType.Abstain)) { 92 | proposalVote.abstainVotes += weight; 93 | } else { 94 | revert("GovernorVotingSimple: invalid value for enum VoteType"); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /contracts/governance/GovernorSimpleVotes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IERC6372} from "@openzeppelin/contracts/interfaces/IERC6372.sol"; 5 | import {IVotes} from "./IVotes.sol"; 6 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 7 | import {GovernorSimple} from "./GovernorSimple.sol"; 8 | 9 | /** 10 | * @dev Modified lightly from OpenZeppelin's GovernorVotes 11 | */ 12 | abstract contract GovernorSimpleVotes is GovernorSimple { 13 | IVotes public immutable token; 14 | 15 | constructor(IVotes tokenAddress) { 16 | token = IVotes(address(tokenAddress)); 17 | } 18 | 19 | /** 20 | * @dev Clock (as specified in EIP-6372) is set to match the token's clock. Fallback to block numbers if the token 21 | * does not implement EIP-6372. 22 | */ 23 | function clock() public view virtual override returns (uint48) { 24 | try IERC6372(address(token)).clock() returns (uint48 timepoint) { 25 | return timepoint; 26 | } catch { 27 | return SafeCast.toUint48(block.number); 28 | } 29 | } 30 | 31 | /** 32 | * @dev Machine-readable description of the clock as specified in EIP-6372. 33 | */ 34 | // solhint-disable-next-line func-name-mixedcase 35 | function CLOCK_MODE() public view virtual override returns (string memory) { 36 | try IERC6372(address(token)).CLOCK_MODE() returns (string memory clockmode) { 37 | return clockmode; 38 | } catch { 39 | return "mode=blocknumber&from=default"; 40 | } 41 | } 42 | 43 | /** 44 | * Read the voting weight from the token's built in snapshot mechanism (see {Governor-_getVotes}). 45 | */ 46 | function _getVotes( 47 | address account, 48 | uint256 tokenId, 49 | uint256 timepoint, 50 | bytes memory /*params*/ 51 | ) internal view virtual override returns (uint256) { 52 | return token.getPastVotes(account, tokenId, timepoint); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/governance/IVotes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | /// Modified IVotes interface for tokenId based voting 5 | interface IVotes { 6 | /** 7 | * @dev Emitted when an account changes their delegate. 8 | */ 9 | event DelegateChanged(address indexed delegator, uint256 indexed fromDelegate, uint256 indexed toDelegate); 10 | 11 | /** 12 | * @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of votes. 13 | */ 14 | event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance); 15 | 16 | /** 17 | * @dev Returns the amount of votes that `tokenId` had at a specific moment in the past. 18 | * If the account passed in is not the owner, returns 0. 19 | */ 20 | function getPastVotes(address account, uint256 tokenId, uint256 timepoint) external view returns (uint256); 21 | 22 | /** 23 | * @dev Returns the total supply of votes available at a specific moment in the past. If the `clock()` is 24 | * configured to use block numbers, this will return the value the end of the corresponding block. 25 | * 26 | * NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. 27 | * Votes that have not been delegated are still part of total supply, even though they would not participate in a 28 | * vote. 29 | */ 30 | function getPastTotalSupply(uint256 timepoint) external view returns (uint256); 31 | 32 | /** 33 | * @dev Returns the delegate that `tokenId` has chosen. Can never be equal to the delegator's `tokenId`. 34 | * Returns 0 if not delegated. 35 | */ 36 | function delegates(uint256 tokenId) external view returns (uint256); 37 | 38 | /** 39 | * @dev Delegates votes from the sender to `delegatee`. 40 | */ 41 | function delegate(uint256 delegator, uint256 delegatee) external; 42 | 43 | /** 44 | * @dev Delegates votes from `delegator` to `delegatee`. Signer must own `delegator`. 45 | */ 46 | function delegateBySig( 47 | uint256 delegator, 48 | uint256 delegatee, 49 | uint256 nonce, 50 | uint256 expiry, 51 | uint8 v, 52 | bytes32 r, 53 | bytes32 s 54 | ) external; 55 | } 56 | -------------------------------------------------------------------------------- /contracts/governance/VetoGovernorCountingSimple.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | // OpenZeppelin Contracts (last updated v4.8.0) (governance/extensions/GovernorCountingSimple.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import {VetoGovernor} from "./VetoGovernor.sol"; 7 | 8 | /** 9 | * @dev OpenZeppelin's GovernorCountingSimple using VetoGovernor 10 | */ 11 | abstract contract VetoGovernorCountingSimple is VetoGovernor { 12 | /** 13 | * @dev Supported vote types. Matches Governor Bravo ordering. 14 | */ 15 | enum VoteType { 16 | Against, 17 | For, 18 | Abstain 19 | } 20 | 21 | struct ProposalVote { 22 | uint256 againstVotes; 23 | uint256 forVotes; 24 | uint256 abstainVotes; 25 | mapping(uint256 => bool) hasVoted; 26 | } 27 | 28 | mapping(uint256 => ProposalVote) private _proposalVotes; 29 | 30 | /** 31 | * @dev See {IGovernor-COUNTING_MODE}. 32 | */ 33 | // solhint-disable-next-line func-name-mixedcase 34 | function COUNTING_MODE() public pure virtual override returns (string memory) { 35 | return "support=bravo&quorum=for,abstain"; 36 | } 37 | 38 | /** 39 | * @dev See {IGovernor-hasVoted}. 40 | */ 41 | function hasVoted(uint256 proposalId, uint256 tokenId) public view virtual override returns (bool) { 42 | return _proposalVotes[proposalId].hasVoted[tokenId]; 43 | } 44 | 45 | /** 46 | * @dev Accessor to the internal vote counts. 47 | */ 48 | function proposalVotes( 49 | uint256 proposalId 50 | ) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { 51 | ProposalVote storage proposalVote = _proposalVotes[proposalId]; 52 | return (proposalVote.againstVotes, proposalVote.forVotes, proposalVote.abstainVotes); 53 | } 54 | 55 | /** 56 | * @dev See {Governor-_quorumReached}. 57 | */ 58 | function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) { 59 | ProposalVote storage proposalVote = _proposalVotes[proposalId]; 60 | 61 | return quorum(proposalSnapshot(proposalId)) <= proposalVote.forVotes + proposalVote.abstainVotes; 62 | } 63 | 64 | /** 65 | * @dev See {Governor-_voteSucceeded}. In this module, the forVotes must be strictly over the againstVotes. 66 | */ 67 | function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) { 68 | ProposalVote storage proposalVote = _proposalVotes[proposalId]; 69 | 70 | return proposalVote.forVotes > proposalVote.againstVotes; 71 | } 72 | 73 | /** 74 | * @dev See {Governor-_countVote}. In this module, the support follows the `VoteType` enum (from Governor Bravo). 75 | */ 76 | function _countVote( 77 | uint256 proposalId, 78 | uint256 tokenId, 79 | uint8 support, 80 | uint256 weight, 81 | bytes memory // params 82 | ) internal virtual override { 83 | ProposalVote storage proposalVote = _proposalVotes[proposalId]; 84 | 85 | require(!proposalVote.hasVoted[tokenId], "GovernorVotingSimple: vote already cast"); 86 | require(weight > 0, "GovernorVotingSimple: zero voting weight"); 87 | proposalVote.hasVoted[tokenId] = true; 88 | 89 | if (support == uint8(VoteType.Against)) { 90 | proposalVote.againstVotes += weight; 91 | } else if (support == uint8(VoteType.For)) { 92 | proposalVote.forVotes += weight; 93 | } else if (support == uint8(VoteType.Abstain)) { 94 | proposalVote.abstainVotes += weight; 95 | } else { 96 | revert("GovernorVotingSimple: invalid value for enum VoteType"); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /contracts/governance/VetoGovernorVotes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | // OpenZeppelin Contracts (last updated v4.6.0) (governance/extensions/GovernorVotes.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import {VetoGovernor} from "./VetoGovernor.sol"; 7 | import {IERC6372} from "@openzeppelin/contracts/interfaces/IERC6372.sol"; 8 | import {IVotes} from "./IVotes.sol"; 9 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 10 | 11 | /** 12 | * @dev OpenZeppelin's GovernorVotes using VetoGovernor 13 | */ 14 | abstract contract VetoGovernorVotes is VetoGovernor { 15 | IVotes public immutable token; 16 | 17 | constructor(IVotes tokenAddress) { 18 | token = IVotes(address(tokenAddress)); 19 | } 20 | 21 | /** 22 | * @dev Clock (as specified in EIP-6372) is set to match the token's clock. Fallback to block numbers if the token 23 | * does not implement EIP-6372. 24 | */ 25 | function clock() public view virtual override returns (uint48) { 26 | try IERC6372(address(token)).clock() returns (uint48 timepoint) { 27 | return timepoint; 28 | } catch { 29 | return SafeCast.toUint48(block.number); 30 | } 31 | } 32 | 33 | /** 34 | * @dev Machine-readable description of the clock as specified in EIP-6372. 35 | */ 36 | // solhint-disable-next-line func-name-mixedcase 37 | function CLOCK_MODE() public view virtual override returns (string memory) { 38 | try IERC6372(address(token)).CLOCK_MODE() returns (string memory clockmode) { 39 | return clockmode; 40 | } catch { 41 | return "mode=blocknumber&from=default"; 42 | } 43 | } 44 | 45 | /** 46 | * Read the voting weight from the token's built in snapshot mechanism (see {Governor-_getVotes}). 47 | */ 48 | function _getVotes( 49 | address account, 50 | uint256 tokenId, 51 | uint256 timepoint, 52 | bytes memory /*params*/ 53 | ) internal view virtual override returns (uint256) { 54 | return token.getPastVotes(account, tokenId, timepoint); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /contracts/governance/VetoGovernorVotesQuorumFraction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | // OpenZeppelin Contracts (last updated v4.8.0) (governance/extensions/GovernorVotesQuorumFraction.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import {VetoGovernorVotes} from "./VetoGovernorVotes.sol"; 7 | import {Checkpoints} from "@openzeppelin/contracts/utils/Checkpoints.sol"; 8 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 9 | 10 | /** 11 | * @dev OpenZeppelin's GovernorVotesQuorumFraction using VetoGovernor 12 | */ 13 | abstract contract VetoGovernorVotesQuorumFraction is VetoGovernorVotes { 14 | using SafeCast for *; 15 | using Checkpoints for Checkpoints.Trace224; 16 | 17 | uint256 private _quorumNumerator; // DEPRECATED in favor of _quorumNumeratorHistory 18 | 19 | /// @custom:oz-retyped-from Checkpoints.History 20 | Checkpoints.Trace224 private _quorumNumeratorHistory; 21 | 22 | event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator); 23 | 24 | /** 25 | * @dev Initialize quorum as a fraction of the token's total supply. 26 | * 27 | * The fraction is specified as `numerator / denominator`. By default the denominator is 100, so quorum is 28 | * specified as a percent: a numerator of 10 corresponds to quorum being 10% of total supply. The denominator can be 29 | * customized by overriding {quorumDenominator}. 30 | */ 31 | constructor(uint256 quorumNumeratorValue) { 32 | _updateQuorumNumerator(quorumNumeratorValue); 33 | } 34 | 35 | /** 36 | * @dev Returns the current quorum numerator. See {quorumDenominator}. 37 | */ 38 | function quorumNumerator() public view virtual returns (uint256) { 39 | return _quorumNumeratorHistory._checkpoints.length == 0 ? _quorumNumerator : _quorumNumeratorHistory.latest(); 40 | } 41 | 42 | /** 43 | * @dev Returns the quorum numerator at a specific timepoint. See {quorumDenominator}. 44 | */ 45 | function quorumNumerator(uint256 timepoint) public view virtual returns (uint256) { 46 | // If history is empty, fallback to old storage 47 | uint256 length = _quorumNumeratorHistory._checkpoints.length; 48 | if (length == 0) { 49 | return _quorumNumerator; 50 | } 51 | 52 | // Optimistic search, check the latest checkpoint 53 | Checkpoints.Checkpoint224 memory latest = _quorumNumeratorHistory._checkpoints[length - 1]; 54 | if (latest._key <= timepoint) { 55 | return latest._value; 56 | } 57 | 58 | // Otherwise, do the binary search 59 | return _quorumNumeratorHistory.upperLookupRecent(timepoint.toUint32()); 60 | } 61 | 62 | /** 63 | * @dev Returns the quorum denominator. Defaults to 100, but may be overridden. 64 | */ 65 | function quorumDenominator() public view virtual returns (uint256) { 66 | return 100; 67 | } 68 | 69 | /** 70 | * @dev Returns the quorum for a timepoint, in terms of number of votes: `supply * numerator / denominator`. 71 | */ 72 | function quorum(uint256 timepoint) public view virtual override returns (uint256) { 73 | return (token.getPastTotalSupply(timepoint) * quorumNumerator(timepoint)) / quorumDenominator(); 74 | } 75 | 76 | /** 77 | * @dev Changes the quorum numerator. 78 | * 79 | * Emits a {QuorumNumeratorUpdated} event. 80 | * 81 | * Requirements: 82 | * 83 | * - Must be called through a governance proposal. 84 | * - New numerator must be smaller or equal to the denominator. 85 | */ 86 | function updateQuorumNumerator(uint256 newQuorumNumerator) external virtual onlyGovernance { 87 | _updateQuorumNumerator(newQuorumNumerator); 88 | } 89 | 90 | /** 91 | * @dev Changes the quorum numerator. 92 | * 93 | * Emits a {QuorumNumeratorUpdated} event. 94 | * 95 | * Requirements: 96 | * 97 | * - New numerator must be smaller or equal to the denominator. 98 | */ 99 | function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual { 100 | require( 101 | newQuorumNumerator <= quorumDenominator(), 102 | "GovernorVotesQuorumFraction: quorumNumerator over quorumDenominator" 103 | ); 104 | 105 | uint256 oldQuorumNumerator = quorumNumerator(); 106 | 107 | // Make sure we keep track of the original numerator in contracts upgraded from a version without checkpoints. 108 | if (oldQuorumNumerator != 0 && _quorumNumeratorHistory._checkpoints.length == 0) { 109 | _quorumNumeratorHistory._checkpoints.push( 110 | Checkpoints.Checkpoint224({_key: 0, _value: oldQuorumNumerator.toUint224()}) 111 | ); 112 | } 113 | 114 | // Set new quorum for future proposals 115 | _quorumNumeratorHistory.push(clock().toUint32(), newQuorumNumerator.toUint224()); 116 | 117 | emit QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /contracts/interfaces/IAero.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | interface IAero is IERC20 { 7 | error NotMinter(); 8 | error NotOwner(); 9 | 10 | /// @notice Mint an amount of tokens to an account 11 | /// Only callable by Minter.sol 12 | /// @return True if success 13 | function mint(address account, uint256 amount) external returns (bool); 14 | 15 | /// @notice Address of Minter.sol 16 | function minter() external view returns (address); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/interfaces/IAirdropDistributor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IAero} from "./IAero.sol"; 5 | import {IVotingEscrow} from "./IVotingEscrow.sol"; 6 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 7 | 8 | interface IAirdropDistributor { 9 | error InvalidParams(); 10 | error InsufficientBalance(); 11 | 12 | event Airdrop(address indexed _wallet, uint256 _amount, uint256 _tokenId); 13 | 14 | /// @notice Interface of Aero.sol 15 | function aero() external view returns (IAero); 16 | 17 | /// @notice Interface of IVotingEscrow.sol 18 | function ve() external view returns (IVotingEscrow); 19 | 20 | /// @notice Distributes permanently locked NFTs to the desired addresses 21 | /// @param _wallets Addresses of wallets to receive the Airdrop 22 | /// @param _amounts Amounts to be Airdropped 23 | function distributeTokens(address[] memory _wallets, uint256[] memory _amounts) external; 24 | } 25 | -------------------------------------------------------------------------------- /contracts/interfaces/IEpochGovernor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IEpochGovernor { 5 | enum ProposalState { 6 | Pending, 7 | Active, 8 | Canceled, 9 | Defeated, 10 | Succeeded, 11 | Queued, 12 | Expired, 13 | Executed 14 | } 15 | 16 | /// @dev Stores most recent voting result. Will be either Defeated, Succeeded or Expired. 17 | /// Any contracts that wish to use this governor must read from this to determine results. 18 | function result() external returns (ProposalState); 19 | } 20 | -------------------------------------------------------------------------------- /contracts/interfaces/IGauge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IGauge { 5 | error NotAlive(); 6 | error NotAuthorized(); 7 | error NotVoter(); 8 | error NotTeam(); 9 | error RewardRateTooHigh(); 10 | error ZeroAmount(); 11 | error ZeroRewardRate(); 12 | 13 | event Deposit(address indexed from, address indexed to, uint256 amount); 14 | event Withdraw(address indexed from, uint256 amount); 15 | event NotifyReward(address indexed from, uint256 amount); 16 | event ClaimFees(address indexed from, uint256 claimed0, uint256 claimed1); 17 | event ClaimRewards(address indexed from, uint256 amount); 18 | 19 | /// @notice Address of the pool LP token which is deposited (staked) for rewards 20 | function stakingToken() external view returns (address); 21 | 22 | /// @notice Address of the token (AERO) rewarded to stakers 23 | function rewardToken() external view returns (address); 24 | 25 | /// @notice Address of the FeesVotingReward contract linked to the gauge 26 | function feesVotingReward() external view returns (address); 27 | 28 | /// @notice Address of Protocol Voter 29 | function voter() external view returns (address); 30 | 31 | /// @notice Address of Protocol Voting Escrow 32 | function ve() external view returns (address); 33 | 34 | /// @notice Returns if gauge is linked to a legitimate Protocol pool 35 | function isPool() external view returns (bool); 36 | 37 | /// @notice Timestamp end of current rewards period 38 | function periodFinish() external view returns (uint256); 39 | 40 | /// @notice Current reward rate of rewardToken to distribute per second 41 | function rewardRate() external view returns (uint256); 42 | 43 | /// @notice Most recent timestamp contract has updated state 44 | function lastUpdateTime() external view returns (uint256); 45 | 46 | /// @notice Most recent stored value of rewardPerToken 47 | function rewardPerTokenStored() external view returns (uint256); 48 | 49 | /// @notice Amount of stakingToken deposited for rewards 50 | function totalSupply() external view returns (uint256); 51 | 52 | /// @notice Get the amount of stakingToken deposited by an account 53 | function balanceOf(address) external view returns (uint256); 54 | 55 | /// @notice Cached rewardPerTokenStored for an account based on their most recent action 56 | function userRewardPerTokenPaid(address) external view returns (uint256); 57 | 58 | /// @notice Cached amount of rewardToken earned for an account 59 | function rewards(address) external view returns (uint256); 60 | 61 | /// @notice View to see the rewardRate given the timestamp of the start of the epoch 62 | function rewardRateByEpoch(uint256) external view returns (uint256); 63 | 64 | /// @notice Cached amount of fees generated from the Pool linked to the Gauge of token0 65 | function fees0() external view returns (uint256); 66 | 67 | /// @notice Cached amount of fees generated from the Pool linked to the Gauge of token1 68 | function fees1() external view returns (uint256); 69 | 70 | /// @notice Get the current reward rate per unit of stakingToken deposited 71 | function rewardPerToken() external view returns (uint256 _rewardPerToken); 72 | 73 | /// @notice Returns the last time the reward was modified or periodFinish if the reward has ended 74 | function lastTimeRewardApplicable() external view returns (uint256 _time); 75 | 76 | /// @notice Returns accrued balance to date from last claim / first deposit. 77 | function earned(address _account) external view returns (uint256 _earned); 78 | 79 | /// @notice Total amount of rewardToken to distribute for the current rewards period 80 | function left() external view returns (uint256 _left); 81 | 82 | /// @notice Retrieve rewards for an address. 83 | /// @dev Throws if not called by same address or voter. 84 | /// @param _account . 85 | function getReward(address _account) external; 86 | 87 | /// @notice Deposit LP tokens into gauge for msg.sender 88 | /// @param _amount . 89 | function deposit(uint256 _amount) external; 90 | 91 | /// @notice Deposit LP tokens into gauge for any user 92 | /// @param _amount . 93 | /// @param _recipient Recipient to give balance to 94 | function deposit(uint256 _amount, address _recipient) external; 95 | 96 | /// @notice Withdraw LP tokens for user 97 | /// @param _amount . 98 | function withdraw(uint256 _amount) external; 99 | 100 | /// @dev Notifies gauge of gauge rewards. Assumes gauge reward tokens is 18 decimals. 101 | /// If not 18 decimals, rewardRate may have rounding issues. 102 | function notifyRewardAmount(uint256 amount) external; 103 | 104 | /// @dev Notifies gauge of gauge rewards without distributing its fees. 105 | /// Assumes gauge reward tokens is 18 decimals. 106 | /// If not 18 decimals, rewardRate may have rounding issues. 107 | function notifyRewardWithoutClaim(uint256 amount) external; 108 | } 109 | -------------------------------------------------------------------------------- /contracts/interfaces/IMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {IAero} from "./IAero.sol"; 5 | import {IVoter} from "./IVoter.sol"; 6 | import {IVotingEscrow} from "./IVotingEscrow.sol"; 7 | import {IRewardsDistributor} from "./IRewardsDistributor.sol"; 8 | 9 | interface IMinter { 10 | struct AirdropParams { 11 | // List of addresses to receive Liquid Tokens from the airdrop 12 | address[] liquidWallets; 13 | // List of amounts of Liquid Tokens to be issued 14 | uint256[] liquidAmounts; 15 | // List of addresses to receive Locked NFTs from the airdrop 16 | address[] lockedWallets; 17 | // List of amounts of Locked NFTs to be issued 18 | uint256[] lockedAmounts; 19 | } 20 | 21 | error NotTeam(); 22 | error RateTooHigh(); 23 | error ZeroAddress(); 24 | error InvalidParams(); 25 | error AlreadyNudged(); 26 | error NotPendingTeam(); 27 | error NotEpochGovernor(); 28 | error AlreadyInitialized(); 29 | error TailEmissionsInactive(); 30 | 31 | event Mint(address indexed _sender, uint256 _weekly, uint256 _circulating_supply, bool indexed _tail); 32 | event DistributeLocked(address indexed _destination, uint256 _amount, uint256 _tokenId); 33 | event Nudge(uint256 indexed _period, uint256 _oldRate, uint256 _newRate); 34 | event DistributeLiquid(address indexed _destination, uint256 _amount); 35 | event AcceptTeam(address indexed _newTeam); 36 | 37 | /// @notice Interface of Aero.sol 38 | function aero() external view returns (IAero); 39 | 40 | /// @notice Interface of Voter.sol 41 | function voter() external view returns (IVoter); 42 | 43 | /// @notice Interface of IVotingEscrow.sol 44 | function ve() external view returns (IVotingEscrow); 45 | 46 | /// @notice Interface of RewardsDistributor.sol 47 | function rewardsDistributor() external view returns (IRewardsDistributor); 48 | 49 | /// @notice Duration of epoch in seconds 50 | function WEEK() external view returns (uint256); 51 | 52 | /// @notice Decay rate of emissions as percentage of `MAX_BPS` 53 | function WEEKLY_DECAY() external view returns (uint256); 54 | 55 | /// @notice Growth rate of emissions as percentage of `MAX_BPS` in first 14 weeks 56 | function WEEKLY_GROWTH() external view returns (uint256); 57 | 58 | /// @notice Maximum tail emission rate in basis points. 59 | function MAXIMUM_TAIL_RATE() external view returns (uint256); 60 | 61 | /// @notice Minimum tail emission rate in basis points. 62 | function MINIMUM_TAIL_RATE() external view returns (uint256); 63 | 64 | /// @notice Denominator for emissions calculations (as basis points) 65 | function MAX_BPS() external view returns (uint256); 66 | 67 | /// @notice Rate change per proposal 68 | function NUDGE() external view returns (uint256); 69 | 70 | /// @notice When emissions fall below this amount, begin tail emissions 71 | function TAIL_START() external view returns (uint256); 72 | 73 | /// @notice Maximum team percentage in basis points 74 | function MAXIMUM_TEAM_RATE() external view returns (uint256); 75 | 76 | /// @notice Current team percentage in basis points 77 | function teamRate() external view returns (uint256); 78 | 79 | /// @notice Tail emissions rate in basis points 80 | function tailEmissionRate() external view returns (uint256); 81 | 82 | /// @notice Starting weekly emission of 10M AERO (AERO has 18 decimals) 83 | function weekly() external view returns (uint256); 84 | 85 | /// @notice Timestamp of start of epoch that updatePeriod was last called in 86 | function activePeriod() external view returns (uint256); 87 | 88 | /// @notice Number of epochs in which updatePeriod was called 89 | function epochCount() external view returns (uint256); 90 | 91 | /// @notice Boolean used to verify if contract has been initialized 92 | function initialized() external returns (bool); 93 | 94 | /// @dev activePeriod => proposal existing, used to enforce one proposal per epoch 95 | /// @param _activePeriod Timestamp of start of epoch 96 | /// @return True if proposal has been executed, else false 97 | function proposals(uint256 _activePeriod) external view returns (bool); 98 | 99 | /// @notice Current team address in charge of emissions 100 | function team() external view returns (address); 101 | 102 | /// @notice Possible team address pending approval of current team 103 | function pendingTeam() external view returns (address); 104 | 105 | /// @notice Mints liquid tokens and permanently locked NFTs to the provided accounts 106 | /// @param params Struct that stores the wallets and amounts for the Airdrops 107 | function initialize(AirdropParams memory params) external; 108 | 109 | /// @notice Creates a request to change the current team's address 110 | /// @param _team Address of the new team to be chosen 111 | function setTeam(address _team) external; 112 | 113 | /// @notice Accepts the request to replace the current team's address 114 | /// with the requested one, present on variable pendingTeam 115 | function acceptTeam() external; 116 | 117 | /// @notice Creates a request to change the current team's percentage 118 | /// @param _rate New team rate to be set in basis points 119 | function setTeamRate(uint256 _rate) external; 120 | 121 | /// @notice Allows epoch governor to modify the tail emission rate by at most 1 basis point 122 | /// per epoch to a maximum of 100 basis points or to a minimum of 1 basis point. 123 | /// Note: the very first nudge proposal must take place the week prior 124 | /// to the tail emission schedule starting. 125 | /// @dev Throws if not epoch governor. 126 | /// Throws if not currently in tail emission schedule. 127 | /// Throws if already nudged this epoch. 128 | /// Throws if nudging above maximum rate. 129 | /// Throws if nudging below minimum rate. 130 | /// This contract is coupled to EpochGovernor as it requires three option simple majority voting. 131 | function nudge() external; 132 | 133 | /// @notice Calculates rebases according to the formula 134 | /// weekly * ((aero.totalsupply - ve.totalSupply) / aero.totalsupply) ^ 2 / 2 135 | /// Note that ve.totalSupply is the locked ve supply 136 | /// aero.totalSupply is the total ve supply minted 137 | /// @param _minted Amount of AERO minted this epoch 138 | /// @return _growth Rebases 139 | function calculateGrowth(uint256 _minted) external view returns (uint256 _growth); 140 | 141 | /// @notice Processes emissions and rebases. Callable once per epoch (1 week). 142 | /// @return _period Start of current epoch. 143 | function updatePeriod() external returns (uint256 _period); 144 | } 145 | -------------------------------------------------------------------------------- /contracts/interfaces/IPoolCallee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IPoolCallee { 5 | function hook(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external; 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/IReward.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IReward { 5 | error InvalidReward(); 6 | error NotAuthorized(); 7 | error NotGauge(); 8 | error NotEscrowToken(); 9 | error NotSingleToken(); 10 | error NotVotingEscrow(); 11 | error NotWhitelisted(); 12 | error ZeroAmount(); 13 | 14 | event Deposit(address indexed from, uint256 indexed tokenId, uint256 amount); 15 | event Withdraw(address indexed from, uint256 indexed tokenId, uint256 amount); 16 | event NotifyReward(address indexed from, address indexed reward, uint256 indexed epoch, uint256 amount); 17 | event ClaimRewards(address indexed from, address indexed reward, uint256 amount); 18 | 19 | /// @notice A checkpoint for marking balance 20 | struct Checkpoint { 21 | uint256 timestamp; 22 | uint256 balanceOf; 23 | } 24 | 25 | /// @notice A checkpoint for marking supply 26 | struct SupplyCheckpoint { 27 | uint256 timestamp; 28 | uint256 supply; 29 | } 30 | 31 | /// @notice Epoch duration constant (7 days) 32 | function DURATION() external view returns (uint256); 33 | 34 | /// @notice Address of Voter.sol 35 | function voter() external view returns (address); 36 | 37 | /// @notice Address of VotingEscrow.sol 38 | function ve() external view returns (address); 39 | 40 | /// @dev Address which has permission to externally call _deposit() & _withdraw() 41 | function authorized() external view returns (address); 42 | 43 | /// @notice Total amount currently deposited via _deposit() 44 | function totalSupply() external view returns (uint256); 45 | 46 | /// @notice Current amount deposited by tokenId 47 | function balanceOf(uint256 tokenId) external view returns (uint256); 48 | 49 | /// @notice Amount of tokens to reward depositors for a given epoch 50 | /// @param token Address of token to reward 51 | /// @param epochStart Startime of rewards epoch 52 | /// @return Amount of token 53 | function tokenRewardsPerEpoch(address token, uint256 epochStart) external view returns (uint256); 54 | 55 | /// @notice Most recent timestamp a veNFT has claimed their rewards 56 | /// @param token Address of token rewarded 57 | /// @param tokenId veNFT unique identifier 58 | /// @return Timestamp 59 | function lastEarn(address token, uint256 tokenId) external view returns (uint256); 60 | 61 | /// @notice True if a token is or has been an active reward token, else false 62 | function isReward(address token) external view returns (bool); 63 | 64 | /// @notice The number of checkpoints for each tokenId deposited 65 | function numCheckpoints(uint256 tokenId) external view returns (uint256); 66 | 67 | /// @notice The total number of checkpoints 68 | function supplyNumCheckpoints() external view returns (uint256); 69 | 70 | /// @notice Deposit an amount into the rewards contract to earn future rewards associated to a veNFT 71 | /// @dev Internal notation used as only callable internally by `authorized`. 72 | /// @param amount Amount deposited for the veNFT 73 | /// @param tokenId Unique identifier of the veNFT 74 | function _deposit(uint256 amount, uint256 tokenId) external; 75 | 76 | /// @notice Withdraw an amount from the rewards contract associated to a veNFT 77 | /// @dev Internal notation used as only callable internally by `authorized`. 78 | /// @param amount Amount deposited for the veNFT 79 | /// @param tokenId Unique identifier of the veNFT 80 | function _withdraw(uint256 amount, uint256 tokenId) external; 81 | 82 | /// @notice Claim the rewards earned by a veNFT staker 83 | /// @param tokenId Unique identifier of the veNFT 84 | /// @param tokens Array of tokens to claim rewards of 85 | function getReward(uint256 tokenId, address[] memory tokens) external; 86 | 87 | /// @notice Add rewards for stakers to earn 88 | /// @param token Address of token to reward 89 | /// @param amount Amount of token to transfer to rewards 90 | function notifyRewardAmount(address token, uint256 amount) external; 91 | 92 | /// @notice Determine the prior balance for an account as of a block number 93 | /// @dev Block number must be a finalized block or else this function will revert to prevent misinformation. 94 | /// @param tokenId The token of the NFT to check 95 | /// @param timestamp The timestamp to get the balance at 96 | /// @return The balance the account had as of the given block 97 | function getPriorBalanceIndex(uint256 tokenId, uint256 timestamp) external view returns (uint256); 98 | 99 | /// @notice Determine the prior index of supply staked by of a timestamp 100 | /// @dev Timestamp must be <= current timestamp 101 | /// @param timestamp The timestamp to get the index at 102 | /// @return Index of supply checkpoint 103 | function getPriorSupplyIndex(uint256 timestamp) external view returns (uint256); 104 | 105 | /// @notice Get number of rewards tokens 106 | function rewardsListLength() external view returns (uint256); 107 | 108 | /// @notice Calculate how much in rewards are earned for a specific token and veNFT 109 | /// @param token Address of token to fetch rewards of 110 | /// @param tokenId Unique identifier of the veNFT 111 | /// @return Amount of token earned in rewards 112 | function earned(address token, uint256 tokenId) external view returns (uint256); 113 | } 114 | -------------------------------------------------------------------------------- /contracts/interfaces/IRewardsDistributor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {IVotingEscrow} from "./IVotingEscrow.sol"; 5 | 6 | interface IRewardsDistributor { 7 | event CheckpointToken(uint256 time, uint256 tokens); 8 | event Claimed(uint256 indexed tokenId, uint256 indexed epochStart, uint256 indexed epochEnd, uint256 amount); 9 | 10 | error NotMinter(); 11 | error NotManagedOrNormalNFT(); 12 | error UpdatePeriod(); 13 | 14 | /// @notice 7 days in seconds 15 | function WEEK() external view returns (uint256); 16 | 17 | /// @notice Timestamp of contract creation 18 | function startTime() external view returns (uint256); 19 | 20 | /// @notice Timestamp of most recent claim of tokenId 21 | function timeCursorOf(uint256 tokenId) external view returns (uint256); 22 | 23 | /// @notice The last timestamp Minter has called checkpointToken() 24 | function lastTokenTime() external view returns (uint256); 25 | 26 | /// @notice Interface of VotingEscrow.sol 27 | function ve() external view returns (IVotingEscrow); 28 | 29 | /// @notice Address of token used for distributions (AERO) 30 | function token() external view returns (address); 31 | 32 | /// @notice Address of Minter.sol 33 | /// Authorized caller of checkpointToken() 34 | function minter() external view returns (address); 35 | 36 | /// @notice Amount of token in contract when checkpointToken() was last called 37 | function tokenLastBalance() external view returns (uint256); 38 | 39 | /// @notice Called by Minter to notify Distributor of rebases 40 | function checkpointToken() external; 41 | 42 | /// @notice Returns the amount of rebases claimable for a given token ID 43 | /// @dev Allows claiming of rebases up to 50 epochs old 44 | /// @param tokenId The token ID to check 45 | /// @return The amount of rebases claimable for the given token ID 46 | function claimable(uint256 tokenId) external view returns (uint256); 47 | 48 | /// @notice Claims rebases for a given token ID 49 | /// @dev Allows claiming of rebases up to 50 epochs old 50 | /// `Minter.updatePeriod()` must be called before claiming 51 | /// @param tokenId The token ID to claim for 52 | /// @return The amount of rebases claimed 53 | function claim(uint256 tokenId) external returns (uint256); 54 | 55 | /// @notice Claims rebases for a list of token IDs 56 | /// @dev `Minter.updatePeriod()` must be called before claiming 57 | /// @param tokenIds The token IDs to claim for 58 | /// @return Whether or not the claim succeeded 59 | function claimMany(uint256[] calldata tokenIds) external returns (bool); 60 | 61 | /// @notice Used to set minter once on initialization 62 | /// @dev Callable once by Minter only, Minter is immutable 63 | function setMinter(address _minter) external; 64 | } 65 | -------------------------------------------------------------------------------- /contracts/interfaces/IVeArtProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | interface IVeArtProxy { 5 | /// @dev Art configuration 6 | struct Config { 7 | // NFT metadata variables 8 | int256 _tokenId; 9 | int256 _balanceOf; 10 | int256 _lockedEnd; 11 | int256 _lockedAmount; 12 | // Line art variables 13 | int256 shape; 14 | uint256 palette; 15 | int256 maxLines; 16 | int256 dash; 17 | // Randomness variables 18 | int256 seed1; 19 | int256 seed2; 20 | int256 seed3; 21 | } 22 | 23 | /// @dev Individual line art path variables. 24 | struct lineConfig { 25 | string color; 26 | uint256 stroke; 27 | uint256 offset; 28 | uint256 offsetHalf; 29 | uint256 offsetDashSum; 30 | uint256 pathLength; 31 | } 32 | 33 | /// @dev Represents an (x,y) coordinate in a line. 34 | struct Point { 35 | int256 x; 36 | int256 y; 37 | } 38 | 39 | /// @notice Generate a SVG based on veNFT metadata 40 | /// @param _tokenId Unique veNFT identifier 41 | /// @return output SVG metadata as HTML tag 42 | function tokenURI(uint256 _tokenId) external view returns (string memory output); 43 | 44 | /// @notice Generate only the foreground elements of the line art for an NFT (excluding SVG header), for flexibility purposes. 45 | /// @param _tokenId Unique veNFT identifier 46 | /// @return output Encoded output of generateShape() 47 | function lineArtPathsOnly(uint256 _tokenId) external view returns (bytes memory output); 48 | 49 | /// @notice Generate the master art config metadata for a veNFT 50 | /// @param _tokenId Unique veNFT identifier 51 | /// @return cfg Config struct 52 | function generateConfig(uint256 _tokenId) external view returns (Config memory cfg); 53 | 54 | /// @notice Generate the points for two stripe lines based on the config generated for a veNFT 55 | /// @param cfg Master art config metadata of a veNFT 56 | /// @param l Number of line drawn 57 | /// @return Line (x, y) coordinates of the drawn stripes 58 | function twoStripes(Config memory cfg, int256 l) external pure returns (Point[100] memory Line); 59 | 60 | /// @notice Generate the points for circles based on the config generated for a veNFT 61 | /// @param cfg Master art config metadata of a veNFT 62 | /// @param l Number of circles drawn 63 | /// @return Line (x, y) coordinates of the drawn circles 64 | function circles(Config memory cfg, int256 l) external pure returns (Point[100] memory Line); 65 | 66 | /// @notice Generate the points for interlocking circles based on the config generated for a veNFT 67 | /// @param cfg Master art config metadata of a veNFT 68 | /// @param l Number of interlocking circles drawn 69 | /// @return Line (x, y) coordinates of the drawn interlocking circles 70 | function interlockingCircles(Config memory cfg, int256 l) external pure returns (Point[100] memory Line); 71 | 72 | /// @notice Generate the points for corners based on the config generated for a veNFT 73 | /// @param cfg Master art config metadata of a veNFT 74 | /// @param l Number of corners drawn 75 | /// @return Line (x, y) coordinates of the drawn corners 76 | function corners(Config memory cfg, int256 l) external pure returns (Point[100] memory Line); 77 | 78 | /// @notice Generate the points for a curve based on the config generated for a veNFT 79 | /// @param cfg Master art config metadata of a veNFT 80 | /// @param l Number of curve drawn 81 | /// @return Line (x, y) coordinates of the drawn curve 82 | function curves(Config memory cfg, int256 l) external pure returns (Point[100] memory Line); 83 | 84 | /// @notice Generate the points for a spiral based on the config generated for a veNFT 85 | /// @param cfg Master art config metadata of a veNFT 86 | /// @param l Number of spiral drawn 87 | /// @return Line (x, y) coordinates of the drawn spiral 88 | function spiral(Config memory cfg, int256 l) external pure returns (Point[100] memory Line); 89 | 90 | /// @notice Generate the points for an explosion based on the config generated for a veNFT 91 | /// @param cfg Master art config metadata of a veNFT 92 | /// @param l Number of explosion drawn 93 | /// @return Line (x, y) coordinates of the drawn explosion 94 | function explosion(Config memory cfg, int256 l) external pure returns (Point[100] memory Line); 95 | 96 | /// @notice Generate the points for a wormhole based on the config generated for a veNFT 97 | /// @param cfg Master art config metadata of a veNFT 98 | /// @param l Number of wormhole drawn 99 | /// @return Line (x, y) coordinates of the drawn wormhole 100 | function wormhole(Config memory cfg, int256 l) external pure returns (Point[100] memory Line); 101 | } 102 | -------------------------------------------------------------------------------- /contracts/interfaces/IWETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | interface IWETH is IERC20 { 7 | function deposit() external payable; 8 | 9 | function withdraw(uint256) external; 10 | } 11 | -------------------------------------------------------------------------------- /contracts/interfaces/factories/IFactoryRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IFactoryRegistry { 5 | error FallbackFactory(); 6 | error InvalidFactoriesToPoolFactory(); 7 | error PathAlreadyApproved(); 8 | error PathNotApproved(); 9 | error SameAddress(); 10 | error ZeroAddress(); 11 | 12 | event Approve(address indexed poolFactory, address indexed votingRewardsFactory, address indexed gaugeFactory); 13 | event Unapprove(address indexed poolFactory, address indexed votingRewardsFactory, address indexed gaugeFactory); 14 | event SetManagedRewardsFactory(address indexed _newRewardsFactory); 15 | 16 | /// @notice Approve a set of factories used in the Protocol. 17 | /// Router.sol is able to swap any poolFactories currently approved. 18 | /// Cannot approve address(0) factories. 19 | /// Cannot aprove path that is already approved. 20 | /// Each poolFactory has one unique set and maintains state. In the case a poolFactory is unapproved 21 | /// and then re-approved, the same set of factories must be used. In other words, you cannot overwrite 22 | /// the factories tied to a poolFactory address. 23 | /// VotingRewardsFactories and GaugeFactories may use the same address across multiple poolFactories. 24 | /// @dev Callable by onlyOwner 25 | /// @param poolFactory . 26 | /// @param votingRewardsFactory . 27 | /// @param gaugeFactory . 28 | function approve(address poolFactory, address votingRewardsFactory, address gaugeFactory) external; 29 | 30 | /// @notice Unapprove a set of factories used in the Protocol. 31 | /// While a poolFactory is unapproved, Router.sol cannot swap with pools made from the corresponding factory 32 | /// Can only unapprove an approved path. 33 | /// Cannot unapprove the fallback path (core v2 factories). 34 | /// @dev Callable by onlyOwner 35 | /// @param poolFactory . 36 | function unapprove(address poolFactory) external; 37 | 38 | /// @notice Factory to create free and locked rewards for a managed veNFT 39 | function managedRewardsFactory() external view returns (address); 40 | 41 | /// @notice Set the rewards factory address 42 | /// @dev Callable by onlyOwner 43 | /// @param _newManagedRewardsFactory address of new managedRewardsFactory 44 | function setManagedRewardsFactory(address _newManagedRewardsFactory) external; 45 | 46 | /// @notice Get the factories correlated to a poolFactory. 47 | /// Once set, this can never be modified. 48 | /// Returns the correlated factories even after an approved poolFactory is unapproved. 49 | function factoriesToPoolFactory( 50 | address poolFactory 51 | ) external view returns (address votingRewardsFactory, address gaugeFactory); 52 | 53 | /// @notice Get all PoolFactories approved by the registry 54 | /// @dev The same PoolFactory address cannot be used twice 55 | /// @return Array of PoolFactory addresses 56 | function poolFactories() external view returns (address[] memory); 57 | 58 | /// @notice Check if a PoolFactory is approved within the factory registry. Router uses this method to 59 | /// ensure a pool swapped from is approved. 60 | /// @param poolFactory . 61 | /// @return True if PoolFactory is approved, else false 62 | function isPoolFactoryApproved(address poolFactory) external view returns (bool); 63 | 64 | /// @notice Get the length of the poolFactories array 65 | function poolFactoriesLength() external view returns (uint256); 66 | } 67 | -------------------------------------------------------------------------------- /contracts/interfaces/factories/IGaugeFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IGaugeFactory { 5 | function createGauge( 6 | address _forwarder, 7 | address _pool, 8 | address _feesVotingReward, 9 | address _ve, 10 | bool isPool 11 | ) external returns (address); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/factories/IManagedRewardsFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IManagedRewardsFactory { 5 | event ManagedRewardCreated( 6 | address indexed voter, 7 | address indexed lockedManagedReward, 8 | address indexed freeManagedReward 9 | ); 10 | 11 | /// @notice creates a LockedManagedReward and a FreeManagedReward contract for a managed veNFT 12 | /// @param _forwarder Address of trusted forwarder 13 | /// @param _voter Address of Voter.sol 14 | /// @return lockedManagedReward Address of LockedManagedReward contract created 15 | /// @return freeManagedReward Address of FreeManagedReward contract created 16 | function createRewards( 17 | address _forwarder, 18 | address _voter 19 | ) external returns (address lockedManagedReward, address freeManagedReward); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/interfaces/factories/IPoolFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IPoolFactory { 5 | event SetFeeManager(address feeManager); 6 | event SetPauser(address pauser); 7 | event SetPauseState(bool state); 8 | event SetVoter(address voter); 9 | event PoolCreated(address indexed token0, address indexed token1, bool indexed stable, address pool, uint256); 10 | event SetCustomFee(address indexed pool, uint256 fee); 11 | 12 | error FeeInvalid(); 13 | error FeeTooHigh(); 14 | error InvalidPool(); 15 | error NotFeeManager(); 16 | error NotPauser(); 17 | error NotVoter(); 18 | error PoolAlreadyExists(); 19 | error SameAddress(); 20 | error ZeroFee(); 21 | error ZeroAddress(); 22 | 23 | /// @notice returns the number of pools created from this factory 24 | function allPoolsLength() external view returns (uint256); 25 | 26 | /// @notice Is a valid pool created by this factory. 27 | /// @param . 28 | function isPool(address pool) external view returns (bool); 29 | 30 | /// @notice Return address of pool created by this factory 31 | /// @param tokenA . 32 | /// @param tokenB . 33 | /// @param stable True if stable, false if volatile 34 | function getPool(address tokenA, address tokenB, bool stable) external view returns (address); 35 | 36 | /// @notice Support for v3-style pools which wraps around getPool(tokenA,tokenB,stable) 37 | /// @dev fee is converted to stable boolean. 38 | /// @param tokenA . 39 | /// @param tokenB . 40 | /// @param fee 1 if stable, 0 if volatile, else returns address(0) 41 | function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address); 42 | 43 | /// @dev Only called once to set to Voter.sol - Voter does not have a function 44 | /// to call this contract method, so once set it's immutable. 45 | /// This also follows convention of setVoterAndDistributor() in VotingEscrow.sol 46 | /// @param _voter . 47 | function setVoter(address _voter) external; 48 | 49 | function setPauser(address _pauser) external; 50 | 51 | function setPauseState(bool _state) external; 52 | 53 | function setFeeManager(address _feeManager) external; 54 | 55 | /// @notice Set default fee for stable and volatile pools. 56 | /// @dev Throws if higher than maximum fee. 57 | /// Throws if fee is zero. 58 | /// @param _stable Stable or volatile pool. 59 | /// @param _fee . 60 | function setFee(bool _stable, uint256 _fee) external; 61 | 62 | /// @notice Set overriding fee for a pool from the default 63 | /// @dev A custom fee of zero means the default fee will be used. 64 | function setCustomFee(address _pool, uint256 _fee) external; 65 | 66 | /// @notice Returns fee for a pool, as custom fees are possible. 67 | function getFee(address _pool, bool _stable) external view returns (uint256); 68 | 69 | /// @notice Create a pool given two tokens and if they're stable/volatile 70 | /// @dev token order does not matter 71 | /// @param tokenA . 72 | /// @param tokenB . 73 | /// @param stable . 74 | function createPool(address tokenA, address tokenB, bool stable) external returns (address pool); 75 | 76 | /// @notice Support for v3-style pools which wraps around createPool(tokena,tokenB,stable) 77 | /// @dev fee is converted to stable boolean 78 | /// @dev token order does not matter 79 | /// @param tokenA . 80 | /// @param tokenB . 81 | /// @param fee 1 if stable, 0 if volatile, else revert 82 | function createPool(address tokenA, address tokenB, uint24 fee) external returns (address pool); 83 | 84 | function isPaused() external view returns (bool); 85 | 86 | function voter() external view returns (address); 87 | 88 | function implementation() external view returns (address); 89 | } 90 | -------------------------------------------------------------------------------- /contracts/interfaces/factories/IVotingRewardsFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IVotingRewardsFactory { 5 | /// @notice creates a BribeVotingReward and a FeesVotingReward contract for a gauge 6 | /// @param _forwarder Address of trusted forwarder 7 | /// @param _rewards Addresses of pool tokens to be used as valid rewards tokens 8 | /// @return feesVotingReward Address of FeesVotingReward contract created 9 | /// @return bribeVotingReward Address of BribeVotingReward contract created 10 | function createRewards( 11 | address _forwarder, 12 | address[] memory _rewards 13 | ) external returns (address feesVotingReward, address bribeVotingReward); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/libraries/BalanceLogicLibrary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IVotingEscrow} from "../interfaces/IVotingEscrow.sol"; 5 | import {SafeCastLibrary} from "./SafeCastLibrary.sol"; 6 | 7 | library BalanceLogicLibrary { 8 | using SafeCastLibrary for uint256; 9 | using SafeCastLibrary for int128; 10 | 11 | uint256 internal constant WEEK = 1 weeks; 12 | 13 | /// @notice Binary search to get the user point index for a token id at or prior to a given timestamp 14 | /// @dev If a user point does not exist prior to the timestamp, this will return 0. 15 | /// @param _userPointEpoch State of all user point epochs 16 | /// @param _userPointHistory State of all user point history 17 | /// @param _tokenId . 18 | /// @param _timestamp . 19 | /// @return User point index 20 | function getPastUserPointIndex( 21 | mapping(uint256 => uint256) storage _userPointEpoch, 22 | mapping(uint256 => IVotingEscrow.UserPoint[1000000000]) storage _userPointHistory, 23 | uint256 _tokenId, 24 | uint256 _timestamp 25 | ) internal view returns (uint256) { 26 | uint256 _userEpoch = _userPointEpoch[_tokenId]; 27 | if (_userEpoch == 0) return 0; 28 | // First check most recent balance 29 | if (_userPointHistory[_tokenId][_userEpoch].ts <= _timestamp) return (_userEpoch); 30 | // Next check implicit zero balance 31 | if (_userPointHistory[_tokenId][1].ts > _timestamp) return 0; 32 | 33 | uint256 lower = 0; 34 | uint256 upper = _userEpoch; 35 | while (upper > lower) { 36 | uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow 37 | IVotingEscrow.UserPoint storage userPoint = _userPointHistory[_tokenId][center]; 38 | if (userPoint.ts == _timestamp) { 39 | return center; 40 | } else if (userPoint.ts < _timestamp) { 41 | lower = center; 42 | } else { 43 | upper = center - 1; 44 | } 45 | } 46 | return lower; 47 | } 48 | 49 | /// @notice Binary search to get the global point index at or prior to a given timestamp 50 | /// @dev If a checkpoint does not exist prior to the timestamp, this will return 0. 51 | /// @param _epoch Current global point epoch 52 | /// @param _pointHistory State of all global point history 53 | /// @param _timestamp . 54 | /// @return Global point index 55 | function getPastGlobalPointIndex( 56 | uint256 _epoch, 57 | mapping(uint256 => IVotingEscrow.GlobalPoint) storage _pointHistory, 58 | uint256 _timestamp 59 | ) internal view returns (uint256) { 60 | if (_epoch == 0) return 0; 61 | // First check most recent balance 62 | if (_pointHistory[_epoch].ts <= _timestamp) return (_epoch); 63 | // Next check implicit zero balance 64 | if (_pointHistory[1].ts > _timestamp) return 0; 65 | 66 | uint256 lower = 0; 67 | uint256 upper = _epoch; 68 | while (upper > lower) { 69 | uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow 70 | IVotingEscrow.GlobalPoint storage globalPoint = _pointHistory[center]; 71 | if (globalPoint.ts == _timestamp) { 72 | return center; 73 | } else if (globalPoint.ts < _timestamp) { 74 | lower = center; 75 | } else { 76 | upper = center - 1; 77 | } 78 | } 79 | return lower; 80 | } 81 | 82 | /// @notice Get the current voting power for `_tokenId` 83 | /// @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility 84 | /// Fetches last user point prior to a certain timestamp, then walks forward to timestamp. 85 | /// @param _userPointEpoch State of all user point epochs 86 | /// @param _userPointHistory State of all user point history 87 | /// @param _tokenId NFT for lock 88 | /// @param _t Epoch time to return voting power at 89 | /// @return User voting power 90 | function balanceOfNFTAt( 91 | mapping(uint256 => uint256) storage _userPointEpoch, 92 | mapping(uint256 => IVotingEscrow.UserPoint[1000000000]) storage _userPointHistory, 93 | uint256 _tokenId, 94 | uint256 _t 95 | ) external view returns (uint256) { 96 | uint256 _epoch = getPastUserPointIndex(_userPointEpoch, _userPointHistory, _tokenId, _t); 97 | // epoch 0 is an empty point 98 | if (_epoch == 0) return 0; 99 | IVotingEscrow.UserPoint memory lastPoint = _userPointHistory[_tokenId][_epoch]; 100 | if (lastPoint.permanent != 0) { 101 | return lastPoint.permanent; 102 | } else { 103 | lastPoint.bias -= lastPoint.slope * (_t - lastPoint.ts).toInt128(); 104 | if (lastPoint.bias < 0) { 105 | lastPoint.bias = 0; 106 | } 107 | return lastPoint.bias.toUint256(); 108 | } 109 | } 110 | 111 | /// @notice Calculate total voting power at some point in the past 112 | /// @param _slopeChanges State of all slopeChanges 113 | /// @param _pointHistory State of all global point history 114 | /// @param _epoch The epoch to start search from 115 | /// @param _t Time to calculate the total voting power at 116 | /// @return Total voting power at that time 117 | function supplyAt( 118 | mapping(uint256 => int128) storage _slopeChanges, 119 | mapping(uint256 => IVotingEscrow.GlobalPoint) storage _pointHistory, 120 | uint256 _epoch, 121 | uint256 _t 122 | ) external view returns (uint256) { 123 | uint256 epoch_ = getPastGlobalPointIndex(_epoch, _pointHistory, _t); 124 | // epoch 0 is an empty point 125 | if (epoch_ == 0) return 0; 126 | IVotingEscrow.GlobalPoint memory _point = _pointHistory[epoch_]; 127 | int128 bias = _point.bias; 128 | int128 slope = _point.slope; 129 | uint256 ts = _point.ts; 130 | uint256 t_i = (ts / WEEK) * WEEK; 131 | for (uint256 i = 0; i < 255; ++i) { 132 | t_i += WEEK; 133 | int128 dSlope = 0; 134 | if (t_i > _t) { 135 | t_i = _t; 136 | } else { 137 | dSlope = _slopeChanges[t_i]; 138 | } 139 | bias -= slope * (t_i - ts).toInt128(); 140 | if (t_i == _t) { 141 | break; 142 | } 143 | slope += dSlope; 144 | ts = t_i; 145 | } 146 | 147 | if (bias < 0) { 148 | bias = 0; 149 | } 150 | return bias.toUint256() + _point.permanentLockBalance; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /contracts/libraries/ProtocolTimeLibrary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | library ProtocolTimeLibrary { 5 | uint256 internal constant WEEK = 7 days; 6 | 7 | /// @dev Returns start of epoch based on current timestamp 8 | function epochStart(uint256 timestamp) internal pure returns (uint256) { 9 | unchecked { 10 | return timestamp - (timestamp % WEEK); 11 | } 12 | } 13 | 14 | /// @dev Returns start of next epoch / end of current epoch 15 | function epochNext(uint256 timestamp) internal pure returns (uint256) { 16 | unchecked { 17 | return timestamp - (timestamp % WEEK) + WEEK; 18 | } 19 | } 20 | 21 | /// @dev Returns start of voting window 22 | function epochVoteStart(uint256 timestamp) internal pure returns (uint256) { 23 | unchecked { 24 | return timestamp - (timestamp % WEEK) + 1 hours; 25 | } 26 | } 27 | 28 | /// @dev Returns end of voting window / beginning of unrestricted voting window 29 | function epochVoteEnd(uint256 timestamp) internal pure returns (uint256) { 30 | unchecked { 31 | return timestamp - (timestamp % WEEK) + WEEK - 1 hours; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/libraries/SafeCastLibrary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | /// @title SafeCast Library 5 | /// @author velodrome.finance 6 | /// @notice Safely convert unsigned and signed integers without overflow / underflow 7 | library SafeCastLibrary { 8 | error SafeCastOverflow(); 9 | error SafeCastUnderflow(); 10 | 11 | /// @dev Safely convert uint256 to int128 12 | function toInt128(uint256 value) internal pure returns (int128) { 13 | if (value > uint128(type(int128).max)) revert SafeCastOverflow(); 14 | return int128(uint128(value)); 15 | } 16 | 17 | /// @dev Safely convert int128 to uint256 18 | function toUint256(int128 value) internal pure returns (uint256) { 19 | if (value < 0) revert SafeCastUnderflow(); 20 | return uint256(int256(value)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/rewards/BribeVotingReward.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IVoter} from "../interfaces/IVoter.sol"; 5 | import {VotingReward} from "./VotingReward.sol"; 6 | 7 | /// @notice Bribes pay out rewards for a given pool based on the votes that were received from the user (goes hand in hand with Voter.vote()) 8 | contract BribeVotingReward is VotingReward { 9 | constructor( 10 | address _forwarder, 11 | address _voter, 12 | address[] memory _rewards 13 | ) VotingReward(_forwarder, _voter, _rewards) {} 14 | 15 | /// @inheritdoc VotingReward 16 | function notifyRewardAmount(address token, uint256 amount) external override nonReentrant { 17 | address sender = _msgSender(); 18 | 19 | if (!isReward[token]) { 20 | if (!IVoter(voter).isWhitelistedToken(token)) revert NotWhitelisted(); 21 | isReward[token] = true; 22 | rewards.push(token); 23 | } 24 | 25 | _notifyRewardAmount(sender, token, amount); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/rewards/FeesVotingReward.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {VotingReward} from "./VotingReward.sol"; 5 | import {IVoter} from "../interfaces/IVoter.sol"; 6 | 7 | /// @notice Bribes pay out rewards for a given pool based on the votes that were received from the user (goes hand in hand with Voter.vote()) 8 | contract FeesVotingReward is VotingReward { 9 | constructor( 10 | address _forwarder, 11 | address _voter, 12 | address[] memory _rewards 13 | ) VotingReward(_forwarder, _voter, _rewards) {} 14 | 15 | /// @inheritdoc VotingReward 16 | function notifyRewardAmount(address token, uint256 amount) external override nonReentrant { 17 | address sender = _msgSender(); 18 | if (IVoter(voter).gaugeToFees(sender) != address(this)) revert NotGauge(); 19 | if (!isReward[token]) revert InvalidReward(); 20 | 21 | _notifyRewardAmount(sender, token, amount); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/rewards/FreeManagedReward.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {ManagedReward} from "./ManagedReward.sol"; 5 | import {IVotingEscrow} from "../interfaces/IVotingEscrow.sol"; 6 | import {IVoter} from "../interfaces/IVoter.sol"; 7 | 8 | /// @notice Stores rewards that are free to be distributed 9 | /// @dev Rewards are distributed based on weight contribution to managed nft 10 | contract FreeManagedReward is ManagedReward { 11 | constructor(address _forwarder, address _voter) ManagedReward(_forwarder, _voter) {} 12 | 13 | /// @inheritdoc ManagedReward 14 | function getReward(uint256 tokenId, address[] memory tokens) external override nonReentrant { 15 | if (!IVotingEscrow(ve).isApprovedOrOwner(_msgSender(), tokenId)) revert NotAuthorized(); 16 | 17 | address owner = IVotingEscrow(ve).ownerOf(tokenId); 18 | 19 | _getReward(owner, tokenId, tokens); 20 | } 21 | 22 | /// @inheritdoc ManagedReward 23 | function notifyRewardAmount(address token, uint256 amount) external override nonReentrant { 24 | address sender = _msgSender(); 25 | if (!isReward[token]) { 26 | if (!IVoter(voter).isWhitelistedToken(token)) revert NotWhitelisted(); 27 | isReward[token] = true; 28 | rewards.push(token); 29 | } 30 | 31 | _notifyRewardAmount(sender, token, amount); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/rewards/LockedManagedReward.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {ManagedReward} from "./ManagedReward.sol"; 5 | import {IVotingEscrow} from "../interfaces/IVotingEscrow.sol"; 6 | 7 | /// @notice Stores rewards that are max-locked (i.e. rebases / tokens that were compounded) 8 | /// @dev Rewards are distributed based on weight contribution to managed nft 9 | contract LockedManagedReward is ManagedReward { 10 | constructor(address _forwarder, address _voter) ManagedReward(_forwarder, _voter) {} 11 | 12 | /// @inheritdoc ManagedReward 13 | /// @dev Called by VotingEscrow to retrieve locked rewards 14 | function getReward(uint256 tokenId, address[] memory tokens) external override nonReentrant { 15 | address sender = _msgSender(); 16 | if (sender != ve) revert NotVotingEscrow(); 17 | if (tokens.length != 1) revert NotSingleToken(); 18 | if (tokens[0] != IVotingEscrow(ve).token()) revert NotEscrowToken(); 19 | 20 | _getReward(sender, tokenId, tokens); 21 | } 22 | 23 | /// @inheritdoc ManagedReward 24 | /// @dev Called by VotingEscrow to add rebases / compounded rewards for disbursement 25 | function notifyRewardAmount(address token, uint256 amount) external override nonReentrant { 26 | address sender = _msgSender(); 27 | if (sender != ve) revert NotVotingEscrow(); 28 | if (token != IVotingEscrow(ve).token()) revert NotEscrowToken(); 29 | 30 | _notifyRewardAmount(sender, token, amount); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/rewards/ManagedReward.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {Reward} from "./Reward.sol"; 5 | import {IVotingEscrow} from "../interfaces/IVotingEscrow.sol"; 6 | import {IVoter} from "../interfaces/IVoter.sol"; 7 | 8 | /// @title Base managed veNFT reward contract for distribution of rewards by token id 9 | abstract contract ManagedReward is Reward { 10 | constructor(address _forwarder, address _voter) Reward(_forwarder, _voter) { 11 | address _ve = IVoter(_voter).ve(); 12 | address _token = IVotingEscrow(_ve).token(); 13 | rewards.push(_token); 14 | isReward[_token] = true; 15 | 16 | authorized = _ve; 17 | } 18 | 19 | /// @inheritdoc Reward 20 | function getReward(uint256 tokenId, address[] memory tokens) external virtual override {} 21 | 22 | /// @inheritdoc Reward 23 | function notifyRewardAmount(address token, uint256 amount) external virtual override {} 24 | } 25 | -------------------------------------------------------------------------------- /contracts/rewards/VotingReward.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {Reward} from "./Reward.sol"; 5 | import {IVotingEscrow} from "../interfaces/IVotingEscrow.sol"; 6 | 7 | /// @title Base voting reward contract for distribution of rewards by token id 8 | /// on a weekly basis 9 | abstract contract VotingReward is Reward { 10 | constructor(address _forwarder, address _voter, address[] memory _rewards) Reward(_forwarder, _voter) { 11 | uint256 _length = _rewards.length; 12 | for (uint256 i; i < _length; i++) { 13 | if (_rewards[i] != address(0)) { 14 | isReward[_rewards[i]] = true; 15 | rewards.push(_rewards[i]); 16 | } 17 | } 18 | 19 | authorized = _voter; 20 | } 21 | 22 | /// @inheritdoc Reward 23 | function getReward(uint256 tokenId, address[] memory tokens) external override nonReentrant { 24 | address sender = _msgSender(); 25 | if (!IVotingEscrow(ve).isApprovedOrOwner(sender, tokenId) && sender != voter) revert NotAuthorized(); 26 | 27 | address _owner = IVotingEscrow(ve).ownerOf(tokenId); 28 | _getReward(_owner, tokenId, tokens); 29 | } 30 | 31 | /// @inheritdoc Reward 32 | function notifyRewardAmount(address token, uint256 amount) external virtual override {} 33 | } 34 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | 5 | fs_permissions = [{ access = "read-write", path = "./"}] 6 | 7 | [rpc_endpoints] 8 | base = "${BASE_RPC_URL}" 9 | base_goerli = "${BASE_GOERLI_RPC_URL}" 10 | tenderly = "${TENDERLY_RPC_URL}" 11 | 12 | [etherscan] 13 | base = { key = "${BASE_SCAN_API_KEY}", url = "${BASE_ETHERSCAN_VERIFIER_URL}" } 14 | base_goerli = { key = "${BASE_GOERLI_SCAN_API_KEY}", url = "${BASE_GOERLI_ETHERSCAN_VERIFIER_URL}" } 15 | -------------------------------------------------------------------------------- /funding.json: -------------------------------------------------------------------------------- 1 | { 2 | "opRetro": { 3 | "projectId": "0x4288aacdda65af69c03b2c9b9d057e2b93ed1b7671e481b03c6a7b33349d2365" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | import * as tdly from "@tenderly/hardhat-tenderly"; 3 | import "@nomicfoundation/hardhat-toolbox"; 4 | import "@nomicfoundation/hardhat-foundry"; 5 | 6 | dotenv.config(); 7 | tdly.setup({ automaticVerifications: true }); 8 | 9 | 10 | export default { 11 | defaultNetwork: "tenderly", 12 | networks: { 13 | hardhat: { 14 | }, 15 | tenderly: { 16 | url: `${process.env.TENDERLY_RPC_URL}`, 17 | accounts: [`${process.env.PRIVATE_KEY_DEPLOY}`] 18 | }, 19 | base: { 20 | url: `${process.env.BASE_RPC_URL}`, 21 | accounts: [`${process.env.PRIVATE_KEY_DEPLOY}`] 22 | } 23 | }, 24 | solidity: { 25 | version: "0.8.19", 26 | settings: { 27 | optimizer: { 28 | enabled: true, 29 | runs: 200 30 | } 31 | } 32 | }, 33 | tenderly: { 34 | username: "velodrome-finance", 35 | project: "v2", 36 | privateVerification: false 37 | }, 38 | paths: { 39 | sources: "./contracts", 40 | tests: "./test", 41 | cache: "./cache", 42 | artifacts: "./artifacts" 43 | }, 44 | typechain: { 45 | outDir: "artifacts/types", 46 | target: "ethers-v5" 47 | } 48 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aerodrome", 3 | "version": "2.0.0", 4 | "description": "Base layer AMM on EVMs", 5 | "directories": { 6 | "lib": "lib" 7 | }, 8 | "devDependencies": { 9 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", 10 | "@nomicfoundation/hardhat-foundry": "^1.0.1", 11 | "@nomicfoundation/hardhat-network-helpers": "^1.0.8", 12 | "@nomicfoundation/hardhat-toolbox": "^2.0.2", 13 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 14 | "@openzeppelin/contracts": "4.8.0", 15 | "@tenderly/hardhat-tenderly": "1.7.5", 16 | "@typechain/ethers-v5": "^10.2.1", 17 | "@typechain/hardhat": "^6.1.6", 18 | "@types/chai": "^4.3.5", 19 | "@types/mocha": "^10.0.1", 20 | "chai": "^4.3.7", 21 | "dotenv": "^16.0.3", 22 | "fs": "^0.0.1-security", 23 | "hardhat": "^2.13.1", 24 | "hardhat-gas-reporter": "^1.0.9", 25 | "prettier": "^2.6.2", 26 | "prettier-plugin-solidity": "^1.0.0-beta.19", 27 | "solhint": "^3.3.7", 28 | "solhint-plugin-prettier": "^0.0.5", 29 | "solidity-coverage": "^0.8.2", 30 | "ts-node": "^10.9.1", 31 | "typechain": "^8.2.0", 32 | "typescript": "^5.1.3", 33 | "decimal.js": "^10.4.3" 34 | }, 35 | "scripts": { 36 | "format": "prettier --write 'contracts/**/*.(sol|json)' 'test/**/*.(sol|json)' 'script/**/*.(sol|ts)'", 37 | "format:check": "prettier --check 'contracts/**/*.*(sol|json)' 'test/**/*.(sol|json)' 'script/**/*.(sol|ts)'", 38 | "lint": "solhint --fix 'contracts/**/*.sol' 'test/**/*.sol' 'script/**/*.sol'", 39 | "lint:check": "solhint 'contracts/**/*.sol' 'test/**/*.sol' 'script/**/*.sol'" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | @openzeppelin/=lib/openzeppelin-contracts/ 4 | @opengsn/=lib/gsn/packages/ 5 | @uniswap/v3-core/=lib/v3-core/ 6 | utils/=test/utils/ 7 | -------------------------------------------------------------------------------- /script/DeployArtProxy.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IVotingEscrow, VotingEscrow} from "contracts/VotingEscrow.sol"; 5 | import {VeArtProxy} from "contracts/VeArtProxy.sol"; 6 | 7 | import "forge-std/StdJson.sol"; 8 | import "../test/Base.sol"; 9 | 10 | /// @notice Script to deploy the ArtProxy contract 11 | contract DeployArtProxy is Script { 12 | using stdJson for string; 13 | 14 | uint256 public deployPrivateKey = vm.envUint("PRIVATE_KEY_DEPLOY"); 15 | address public deployerAddress = vm.rememberKey(deployPrivateKey); 16 | string public outputFilename = vm.envString("OUTPUT_FILENAME"); 17 | string public jsonOutput; 18 | string public basePath; 19 | string public path; 20 | 21 | address public escrow; 22 | VeArtProxy public artProxy; 23 | 24 | constructor() { 25 | string memory root = vm.projectRoot(); 26 | basePath = string.concat(root, "/script/constants/"); 27 | path = string.concat(basePath, "output/DeployCore-"); 28 | path = string.concat(path, outputFilename); 29 | jsonOutput = vm.readFile(path); 30 | 31 | // load in var 32 | escrow = abi.decode(jsonOutput.parseRaw(".VotingEscrow"), (address)); 33 | } 34 | 35 | function run() public { 36 | console.log("Using Voting Escrow: %s", address(escrow)); 37 | vm.broadcast(deployPrivateKey); 38 | artProxy = new VeArtProxy(escrow); 39 | console.log("Deploying ArtProxy to: %s", address(artProxy)); 40 | 41 | path = string.concat(basePath, "output/DeployArtProxy-"); 42 | path = string.concat(path, outputFilename); 43 | vm.writeJson(vm.serializeAddress("v2", "ArtProxy", address(artProxy)), path); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /script/DeployCore.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "forge-std/StdJson.sol"; 5 | import "../test/Base.sol"; 6 | 7 | contract DeployCore is Base { 8 | using stdJson for string; 9 | string public basePath; 10 | string public path; 11 | 12 | uint256 public deployPrivateKey = vm.envUint("PRIVATE_KEY_DEPLOY"); 13 | address public deployerAddress = vm.rememberKey(deployPrivateKey); 14 | string public constantsFilename = vm.envString("CONSTANTS_FILENAME"); 15 | string public outputFilename = vm.envString("OUTPUT_FILENAME"); 16 | string public jsonConstants; 17 | string public jsonOutput; 18 | 19 | uint256 public constant AIRDROPPER_BALANCE = 200_000_000 * 1e18; 20 | 21 | // Vars to be set in each deploy script 22 | address feeManager; 23 | address team; 24 | address emergencyCouncil; 25 | 26 | struct AirdropInfo { 27 | uint256 amount; 28 | address wallet; 29 | } 30 | 31 | constructor() { 32 | string memory root = vm.projectRoot(); 33 | basePath = string.concat(root, "/script/constants/"); 34 | 35 | // load constants 36 | path = string.concat(basePath, constantsFilename); 37 | jsonConstants = vm.readFile(path); 38 | WETH = IWETH(abi.decode(vm.parseJson(jsonConstants, ".WETH"), (address))); 39 | allowedManager = abi.decode(vm.parseJson(jsonConstants, ".allowedManager"), (address)); 40 | team = abi.decode(vm.parseJson(jsonConstants, ".team"), (address)); 41 | feeManager = abi.decode(vm.parseJson(jsonConstants, ".feeManager"), (address)); 42 | emergencyCouncil = abi.decode(vm.parseJson(jsonConstants, ".emergencyCouncil"), (address)); 43 | } 44 | 45 | function run() public { 46 | _deploySetupBefore(); 47 | _coreSetup(); 48 | _deploySetupAfter(); 49 | } 50 | 51 | function _deploySetupBefore() public { 52 | // more constants loading - this needs to be done in-memory and not storage 53 | address[] memory _tokens = abi.decode(vm.parseJson(jsonConstants, ".whitelistTokens"), (address[])); 54 | for (uint256 i = 0; i < _tokens.length; i++) { 55 | tokens.push(_tokens[i]); 56 | } 57 | 58 | // Loading output and use output path to later save deployed contracts 59 | path = string.concat(basePath, "output/DeployCore-"); 60 | path = string.concat(path, outputFilename); 61 | 62 | // start broadcasting transactions 63 | vm.startBroadcast(deployerAddress); 64 | 65 | // deploy AERO 66 | AERO = new Aero(); 67 | 68 | tokens.push(address(AERO)); 69 | } 70 | 71 | function _deploySetupAfter() public { 72 | // Initializes the Minter 73 | _initializeMinter(); 74 | 75 | // Set protocol state to team 76 | escrow.setTeam(team); 77 | minter.setTeam(team); 78 | factory.setPauser(team); 79 | voter.setEmergencyCouncil(emergencyCouncil); 80 | voter.setEpochGovernor(team); 81 | voter.setGovernor(team); 82 | factoryRegistry.transferOwnership(team); 83 | 84 | // Set contract vars 85 | factory.setFeeManager(feeManager); 86 | factory.setVoter(address(voter)); 87 | 88 | // finish broadcasting transactions 89 | vm.stopBroadcast(); 90 | 91 | // write to file 92 | vm.writeJson(vm.serializeAddress("v2", "AERO", address(AERO)), path); 93 | vm.writeJson(vm.serializeAddress("v2", "VotingEscrow", address(escrow)), path); 94 | vm.writeJson(vm.serializeAddress("v2", "Forwarder", address(forwarder)), path); 95 | vm.writeJson(vm.serializeAddress("v2", "ArtProxy", address(artProxy)), path); 96 | vm.writeJson(vm.serializeAddress("v2", "Distributor", address(distributor)), path); 97 | vm.writeJson(vm.serializeAddress("v2", "Voter", address(voter)), path); 98 | vm.writeJson(vm.serializeAddress("v2", "Router", address(router)), path); 99 | vm.writeJson(vm.serializeAddress("v2", "Minter", address(minter)), path); 100 | vm.writeJson(vm.serializeAddress("v2", "PoolFactory", address(factory)), path); 101 | vm.writeJson(vm.serializeAddress("v2", "VotingRewardsFactory", address(votingRewardsFactory)), path); 102 | vm.writeJson(vm.serializeAddress("v2", "GaugeFactory", address(gaugeFactory)), path); 103 | vm.writeJson(vm.serializeAddress("v2", "ManagedRewardsFactory", address(managedRewardsFactory)), path); 104 | vm.writeJson(vm.serializeAddress("v2", "FactoryRegistry", address(factoryRegistry)), path); 105 | vm.writeJson(vm.serializeAddress("v2", "AirdropDistributor", address(airdrop)), path); 106 | } 107 | 108 | function _initializeMinter() public { 109 | // Fetching Liquid Token Airdrop info, including the address of the recently deployed AirdropDistributor 110 | AirdropInfo[] memory infos = abi.decode(jsonConstants.parseRaw(".minter.liquid"), (AirdropInfo[])); 111 | (address[] memory liquidWallets, uint256[] memory liquidAmounts) = _getLiquidAirdropInfo( 112 | address(airdrop), 113 | AIRDROPPER_BALANCE, 114 | infos 115 | ); 116 | 117 | // Fetching Locked NFTs Airdrop info 118 | infos = abi.decode(jsonConstants.parseRaw(".minter.locked"), (AirdropInfo[])); 119 | (address[] memory lockedWallets, uint256[] memory lockedAmounts) = _getLockedAirdropInfo(infos); 120 | // Airdrops All Tokens 121 | minter.initialize( 122 | IMinter.AirdropParams({ 123 | liquidWallets: liquidWallets, 124 | liquidAmounts: liquidAmounts, 125 | lockedWallets: lockedWallets, 126 | lockedAmounts: lockedAmounts 127 | }) 128 | ); 129 | } 130 | 131 | function _getLiquidAirdropInfo( 132 | address airdropDistributor, 133 | uint256 distributorAmount, 134 | AirdropInfo[] memory infos 135 | ) public pure returns (address[] memory wallets, uint256[] memory amounts) { 136 | uint256 len = infos.length + 1; 137 | wallets = new address[](len); 138 | amounts = new uint256[](len); 139 | wallets[0] = airdropDistributor; 140 | amounts[0] = distributorAmount; 141 | 142 | for (uint256 i = 1; i < len; i++) { 143 | AirdropInfo memory drop = infos[i - 1]; 144 | wallets[i] = drop.wallet; 145 | amounts[i] = drop.amount; 146 | } 147 | } 148 | 149 | function _getLockedAirdropInfo( 150 | AirdropInfo[] memory infos 151 | ) public pure returns (address[] memory wallets, uint256[] memory amounts) { 152 | uint256 len = infos.length; 153 | wallets = new address[](len); 154 | amounts = new uint256[](len); 155 | 156 | for (uint256 i = 0; i < len; i++) { 157 | AirdropInfo memory drop = infos[i]; 158 | wallets[i] = drop.wallet; 159 | amounts[i] = drop.amount; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /script/DeployGaugesAndPools.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "forge-std/StdJson.sol"; 5 | import "../test/Base.sol"; 6 | 7 | /// @notice Deploy script to deploy new pools and gauges 8 | contract DeployGaugesAndPools is Script { 9 | using stdJson for string; 10 | 11 | uint256 public deployPrivateKey = vm.envUint("PRIVATE_KEY_DEPLOY"); 12 | address public deployerAddress = vm.rememberKey(deployPrivateKey); 13 | string public constantsFilename = vm.envString("CONSTANTS_FILENAME"); 14 | string public outputFilename = vm.envString("OUTPUT_FILENAME"); 15 | string public jsonConstants; 16 | string public jsonOutput; 17 | 18 | PoolFactory public factory; 19 | Voter public voter; 20 | address public AERO; 21 | 22 | struct PoolNonAero { 23 | bool stable; 24 | address tokenA; 25 | address tokenB; 26 | } 27 | 28 | struct PoolAero { 29 | bool stable; 30 | address token; 31 | } 32 | 33 | address[] pools; 34 | address[] gauges; 35 | 36 | constructor() {} 37 | 38 | function run() public { 39 | string memory root = vm.projectRoot(); 40 | string memory basePath = string.concat(root, "/script/constants/"); 41 | string memory path = string.concat(basePath, constantsFilename); 42 | 43 | // load in vars 44 | jsonConstants = vm.readFile(path); 45 | PoolNonAero[] memory _pools = abi.decode(jsonConstants.parseRaw(".pools"), (PoolNonAero[])); 46 | PoolAero[] memory poolsAero = abi.decode(jsonConstants.parseRaw(".poolsAero"), (PoolAero[])); 47 | 48 | path = string.concat(basePath, "output/DeployCore-"); 49 | path = string.concat(path, outputFilename); 50 | jsonOutput = vm.readFile(path); 51 | factory = PoolFactory(abi.decode(jsonOutput.parseRaw(".PoolFactory"), (address))); 52 | voter = Voter(abi.decode(jsonOutput.parseRaw(".Voter"), (address))); 53 | AERO = abi.decode(jsonOutput.parseRaw(".AERO"), (address)); 54 | 55 | vm.startBroadcast(deployerAddress); 56 | 57 | // Deploy all non-AERO pools & gauges 58 | for (uint256 i = 0; i < _pools.length; i++) { 59 | address newPool = factory.createPool(_pools[i].tokenA, _pools[i].tokenB, _pools[i].stable); 60 | address newGauge = voter.createGauge(address(factory), newPool); 61 | 62 | pools.push(newPool); 63 | gauges.push(newGauge); 64 | } 65 | 66 | // Deploy all AERO pools & gauges 67 | for (uint256 i = 0; i < poolsAero.length; i++) { 68 | address newPool = factory.createPool(AERO, poolsAero[i].token, poolsAero[i].stable); 69 | address newGauge = voter.createGauge(address(factory), newPool); 70 | 71 | pools.push(newPool); 72 | gauges.push(newGauge); 73 | } 74 | 75 | vm.stopBroadcast(); 76 | 77 | // Write to file 78 | path = string.concat(basePath, "output/DeployGaugesAndPools-"); 79 | path = string.concat(path, outputFilename); 80 | vm.writeJson(vm.serializeAddress("v2", "gaugesPools", gauges), path); 81 | vm.writeJson(vm.serializeAddress("v2", "pools", pools), path); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /script/DeployGovernors.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "forge-std/StdJson.sol"; 5 | import "../test/Base.sol"; 6 | 7 | contract DeployGovernors is Script { 8 | using stdJson for string; 9 | 10 | uint256 public deployPrivateKey = vm.envUint("PRIVATE_KEY_DEPLOY"); 11 | address public deployAddress = vm.rememberKey(deployPrivateKey); 12 | string public constantsFilename = vm.envString("CONSTANTS_FILENAME"); 13 | string public outputFilename = vm.envString("OUTPUT_FILENAME"); 14 | string public jsonConstants; 15 | string public jsonOutput; 16 | 17 | address team; 18 | 19 | VotingEscrow public escrow; 20 | Forwarder public forwarder; 21 | Minter public minter; 22 | ProtocolGovernor public governor; 23 | EpochGovernor public epochGovernor; 24 | 25 | constructor() {} 26 | 27 | function run() public { 28 | string memory root = vm.projectRoot(); 29 | string memory basePath = string.concat(root, "/script/constants/"); 30 | 31 | string memory path = string.concat(basePath, constantsFilename); 32 | jsonConstants = vm.readFile(path); 33 | team = abi.decode(vm.parseJson(jsonConstants, ".team"), (address)); 34 | 35 | path = string.concat(basePath, "output/DeployCore-"); 36 | path = string.concat(path, outputFilename); 37 | jsonOutput = vm.readFile(path); 38 | escrow = VotingEscrow(abi.decode(vm.parseJson(jsonOutput, ".VotingEscrow"), (address))); 39 | forwarder = Forwarder(abi.decode(vm.parseJson(jsonOutput, ".Forwarder"), (address))); 40 | minter = Minter(abi.decode(vm.parseJson(jsonOutput, ".Minter"), (address))); 41 | 42 | vm.startBroadcast(deployAddress); 43 | 44 | governor = new ProtocolGovernor(escrow); 45 | epochGovernor = new EpochGovernor(address(forwarder), escrow, address(minter)); 46 | 47 | governor.setVetoer(escrow.team()); 48 | 49 | vm.stopBroadcast(); 50 | 51 | // write to file 52 | path = string.concat(basePath, "output/DeployGovernors-"); 53 | path = string.concat(path, outputFilename); 54 | vm.writeJson(vm.serializeAddress("v2", "Governor", address(governor)), path); 55 | vm.writeJson(vm.serializeAddress("v2", "EpochGovernor", address(epochGovernor)), path); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /script/DistributeAirdrops.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "forge-std/StdJson.sol"; 5 | import "../test/Base.sol"; 6 | 7 | /// @notice Script to distribute all the provided Airdrops 8 | contract DistributeAirdrops is Script { 9 | using stdJson for string; 10 | 11 | uint256 public deployPrivateKey = vm.envUint("PRIVATE_KEY_DEPLOY"); 12 | address public deployerAddress = vm.rememberKey(deployPrivateKey); 13 | string public airdropFilename = vm.envString("AIRDROPS_FILENAME"); 14 | string public outputFilename = vm.envString("OUTPUT_FILENAME"); 15 | uint256 public WALLET_BATCH_SIZE = 50; 16 | uint256 public MAX_AIRDROPS = 500; 17 | string public jsonConstants; 18 | string public basePath; 19 | string public path; 20 | 21 | AirdropDistributor public airdrop; 22 | Aero public AERO; 23 | 24 | struct AirdropInfo { 25 | uint256 amount; 26 | address wallet; 27 | } 28 | 29 | constructor() { 30 | string memory root = vm.projectRoot(); 31 | basePath = string.concat(root, "/script/constants/"); 32 | path = string.concat(basePath, "output/DeployCore-"); 33 | path = string.concat(path, outputFilename); 34 | jsonConstants = vm.readFile(path); 35 | 36 | AERO = Aero(abi.decode(jsonConstants.parseRaw(".AERO"), (address))); 37 | airdrop = AirdropDistributor(abi.decode(jsonConstants.parseRaw(".AirdropDistributor"), (address))); 38 | 39 | path = string.concat(basePath, airdropFilename); 40 | jsonConstants = vm.readFile(path); 41 | } 42 | 43 | function run() public { 44 | AirdropInfo[] memory infos = abi.decode(jsonConstants.parseRaw(".airdrop"), (AirdropInfo[])); 45 | 46 | (address[] memory wallets, uint256[] memory amounts) = getArraysFromInfo(infos); 47 | (wallets, amounts) = getArraySlice(wallets, amounts); // Only use first {MAX_AIRDROPS} elements of the arrays 48 | uint256 walletsLength = wallets.length; 49 | require(walletsLength == amounts.length, "Invalid parameters"); 50 | uint256 sum; 51 | for (uint256 i = 0; i < walletsLength; i++) { 52 | sum += amounts[i]; 53 | } 54 | uint256 aeroBal = AERO.balanceOf(address(airdrop)); 55 | if (sum > aeroBal) amounts[0] = amounts[0] - (sum - aeroBal); // remove dust from first airdrop if needed 56 | 57 | path = string.concat(basePath, "output/AirdropDistribution-"); 58 | path = string.concat(path, outputFilename); 59 | 60 | uint256 lastBatchSize = walletsLength % WALLET_BATCH_SIZE; 61 | uint256 nBatches = walletsLength / WALLET_BATCH_SIZE; 62 | 63 | uint256 batchLen; 64 | address[] memory batchWallets; 65 | uint256[] memory batchAmounts; 66 | vm.startBroadcast(deployerAddress); 67 | for (uint256 i = 0; i <= nBatches; i++) { 68 | if (i != nBatches) { 69 | // Not last batch 70 | batchWallets = new address[](WALLET_BATCH_SIZE); 71 | batchAmounts = new uint256[](WALLET_BATCH_SIZE); 72 | batchLen = WALLET_BATCH_SIZE; 73 | } else { 74 | if (lastBatchSize == 0) continue; 75 | batchWallets = new address[](lastBatchSize); 76 | batchAmounts = new uint256[](lastBatchSize); 77 | batchLen = lastBatchSize; 78 | } 79 | 80 | // Fetches the wallets from current batch 81 | uint256 firstIndex = i * WALLET_BATCH_SIZE; 82 | for (uint256 j = 0; j < batchLen; j++) { 83 | batchWallets[j] = wallets[j + firstIndex]; 84 | batchAmounts[j] = amounts[j + firstIndex]; 85 | } 86 | 87 | // Distribute batch 88 | airdrop.distributeTokens(batchWallets, batchAmounts); 89 | // Write batch to file 90 | for (uint256 j = 0; j < batchLen; j++) { 91 | vm.writeJson(vm.serializeUint("airdrop", vm.toString(batchWallets[j]), batchAmounts[j]), path); 92 | } 93 | } 94 | airdrop.renounceOwnership(); 95 | vm.stopBroadcast(); 96 | } 97 | 98 | function getArraysFromInfo( 99 | AirdropInfo[] memory infos 100 | ) public pure returns (address[] memory _wallets, uint256[] memory _amounts) { 101 | uint256 len = infos.length; 102 | _wallets = new address[](len); 103 | _amounts = new uint256[](len); 104 | 105 | for (uint256 i = 0; i < len; i++) { 106 | AirdropInfo memory drop = infos[i]; 107 | _wallets[i] = drop.wallet; 108 | _amounts[i] = drop.amount; 109 | } 110 | } 111 | 112 | function getArraySlice( 113 | address[] memory _wallets, 114 | uint256[] memory _amounts 115 | ) public view returns (address[] memory wallets, uint256[] memory amounts) { 116 | if (MAX_AIRDROPS > _wallets.length) { 117 | wallets = _wallets; 118 | amounts = _amounts; 119 | } else { 120 | uint256 _len = MAX_AIRDROPS; 121 | wallets = new address[](_len); 122 | amounts = new uint256[](_len); 123 | for (uint256 i = 0; i < _len; i++) { 124 | wallets[i] = _wallets[i]; 125 | amounts[i] = _amounts[i]; 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /script/README.md: -------------------------------------------------------------------------------- 1 | ## Deploy Protocol 2 | 3 | The Protocol deployment is a multi-step process. Unlike testing, we cannot impersonate governance to submit transactions and must wait on the necessary protocol actions to complete setup. This README goes through the necessary instructions to deploy the protocol. 4 | 5 | ### Environment setup 6 | 1. Copy-pasta `.env.sample` into a new `.env` and set the environment variables. `PRIVATE_KEY_DEPLOY` is the private key to deploy all scripts. 7 | 2. Copy-pasta `script/constants/TEMPLATE.json` into a new file `script/constants/{CONSTANTS_FILENAME}`. For example, "Base.json" in the .env would be a file of `script/constants/Base.json`. Set the variables in the new file. 8 | 3. Copy-pasta `script/constants/AirdropTEMPLATE.json` into a new file `script/constants/{AIRDROPS_FILENAME}`. For example, "Airdrop.json" in the .env would be a file of `script/constants/Airdrop.json`. Set the addresses and amounts in this file to setup the Airdrops to be distributed. 9 | 10 | 4. Run tests to ensure deployment state is configured correctly: 11 | ```ml 12 | forge init 13 | forge build 14 | forge test 15 | ``` 16 | 17 | *Note that this will create a `script/constants/output/{OUTPUT_FILENAME}` file with the contract addresses created in testing. If you are using the same constants for multiple deployments (for example, deploying in a local fork and then in prod), you can rename `OUTPUT_FILENAME` to store the new contract addresses while using the same constants. 18 | 19 | 5. Ensure all deployments are set properly. In project directory terminal: 20 | ``` 21 | source .env 22 | ``` 23 | 24 | ### Deployment 25 | - Note that if deploying to a chain other than Base/Base Goerli, if you have a different .env variable name used for `RPC_URL`, `SCAN_API_KEY` and `ETHERSCAN_VERIFIER_URL`, you will need to use the corresponding chain name by also updating `foundry.toml`. For this example we're deploying onto Base. 26 | 27 | 1. Deploy the Protocol Core 28 | ``` 29 | forge script script/DeployCore.s.sol:DeployCore --broadcast --slow --rpc-url base --verify -vvvv 30 | ``` 31 | 2. Accept pending team as team. This needs to be done by the `minter.pendingTeam()` address. Within the deployed `Minter` contract call `acceptTeam()`. 32 | 33 | 3. Deploy gauges and pools. These gauges are built on the Protocol using the newly created pools. 34 | ``` 35 | forge script script/DeployGaugesAndPools.s.sol:DeployGaugesAndPools --broadcast --slow --rpc-url base --verify -vvvv 36 | ``` 37 | 38 | 4. Distribute locked NFTs using the AirdropDistributor. This needs to be done by the `airdrop.owner()` address. 39 | ``` 40 | forge script script/DistributeAirdrops.s.sol:DistributeAirdrops --broadcast --slow --gas-estimate-multiplier 200 --legacy --rpc-url base --verify -vvvv 41 | ``` 42 | 43 | 5. Deploy governor contracts 44 | ``` 45 | forge script script/DeployGovernors.s.sol:DeployGovernors --broadcast --slow --rpc-url base --verify -vvvv 46 | ``` 47 | 6. Update the governor addresses. This needs to be done by the `escrow.team()` address. Within `voter`: 48 | - call `setEpochGovernor()` using the `EpochGovernor` address located in `script/constants/output/{OUTPUT_FILENAME}` 49 | - call `setGovernor()` using the `Governor` address located in the same file. 50 | 51 | 7. Accept governor vetoer status. This also needs to be done by the `escrow.team()` address. Within the deployed `Governor` contract call `acceptVetoer()`. 52 | -------------------------------------------------------------------------------- /script/constants/AirdropTEMPLATE.json: -------------------------------------------------------------------------------- 1 | { 2 | "airdrop": [ 3 | { 4 | "amount": 0, 5 | "wallet": "0x0000000000000000000000000000000000000000" 6 | }, 7 | { 8 | "amount": 0, 9 | "wallet": "0x0000000000000000000000000000000000000000" 10 | }, 11 | { 12 | "amount": 0, 13 | "wallet": "0x0000000000000000000000000000000000000000" 14 | }, 15 | { 16 | "amount": 0, 17 | "wallet": "0x0000000000000000000000000000000000000000" 18 | }, 19 | { 20 | "amount": 0, 21 | "wallet": "0x0000000000000000000000000000000000000000" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /script/constants/Base.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowedManager": "0xE6A41fE61E7a1996B59d508661e3f524d6A32075", 3 | "feeManager": "0xE6A41fE61E7a1996B59d508661e3f524d6A32075", 4 | "emergencyCouncil": "0x99249b10593fCa1Ae9DAE6D4819F1A6dae5C013D", 5 | "team": "0xE6A41fE61E7a1996B59d508661e3f524d6A32075", 6 | "WETH": "0x4200000000000000000000000000000000000006", 7 | "whitelistTokens": [ 8 | "0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b", 9 | "0x26f3901aC8a79c50fb0d8289C74f0d09AdC42E29", 10 | "0x2dAD3a13ef0C6366220f989157009e501e7938F8", 11 | "0x4621b7A9c75199271F773Ebd9A499dbd165c3191", 12 | "0xc142171B138DB17a1B7Cb999C44526094a4dae05", 13 | "0x6B4712AE9797C199edd44F897cA09BC57628a1CF", 14 | "0x00e1724885473B63bCE08a9f0a52F35b0979e35A", 15 | "0xa334884bF6b0A066d553D19e507315E839409e62", 16 | "0xf7A0dd3317535eC4f4d29ADF9d620B3d8D5D5069", 17 | "0xA61BeB4A3d02decb01039e378237032B351125B4", 18 | "0xbf1aeA8670D2528E08334083616dD9C5F3B087aE", 19 | "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", 20 | "0xF544251D25f3d243A36B07e7E7962a678f952691", 21 | "0x4A3A6Dd60A34bB2Aba60D73B4C88315E9CeB6A3D", 22 | "0xc5102fE9359FD9a28f877a67E36B0F050d81a3CC", 23 | "0xB79DD08EA68A908A97220C76d19A6aA9cBDE4376", 24 | "0x65a2508C429a6078a7BC2f7dF81aB575BD9D9275", 25 | "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA", 26 | "0xDE5ed76E7c05eC5e4572CfC88d1ACEA165109E44", 27 | "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", 28 | "0xBa5E6fa2f33f3955f0cef50c63dCC84861eAb663", 29 | "0xFF8adeC2221f9f4D8dfbAFa6B9a297d17603493D", 30 | "0x4200000000000000000000000000000000000006" 31 | ], 32 | "pools": [ 33 | { 34 | "stable": false, 35 | "tokenA": "0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b", 36 | "tokenB": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA" 37 | }, 38 | { 39 | "stable": false, 40 | "tokenA": "0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b", 41 | "tokenB": "0x4200000000000000000000000000000000000006" 42 | }, 43 | { 44 | "stable": false, 45 | "tokenA": "0x26f3901aC8a79c50fb0d8289C74f0d09AdC42E29", 46 | "tokenB": "0x4200000000000000000000000000000000000006" 47 | }, 48 | { 49 | "stable": false, 50 | "tokenA": "0x2dAD3a13ef0C6366220f989157009e501e7938F8", 51 | "tokenB": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA" 52 | }, 53 | { 54 | "stable": true, 55 | "tokenA": "0x4621b7A9c75199271F773Ebd9A499dbd165c3191", 56 | "tokenB": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA" 57 | }, 58 | { 59 | "stable": false, 60 | "tokenA": "0x6B4712AE9797C199edd44F897cA09BC57628a1CF", 61 | "tokenB": "0x4200000000000000000000000000000000000006" 62 | }, 63 | { 64 | "stable": false, 65 | "tokenA": "0x00e1724885473B63bCE08a9f0a52F35b0979e35A", 66 | "tokenB": "0xf7A0dd3317535eC4f4d29ADF9d620B3d8D5D5069" 67 | }, 68 | { 69 | "stable": false, 70 | "tokenA": "0xa334884bF6b0A066d553D19e507315E839409e62", 71 | "tokenB": "0xf7A0dd3317535eC4f4d29ADF9d620B3d8D5D5069" 72 | }, 73 | { 74 | "stable": true, 75 | "tokenA": "0xbf1aeA8670D2528E08334083616dD9C5F3B087aE", 76 | "tokenB": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA" 77 | }, 78 | { 79 | "stable": true, 80 | "tokenA": "0xbf1aeA8670D2528E08334083616dD9C5F3B087aE", 81 | "tokenB": "0x4621b7A9c75199271F773Ebd9A499dbd165c3191" 82 | }, 83 | { 84 | "stable": false, 85 | "tokenA": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", 86 | "tokenB": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA" 87 | }, 88 | { 89 | "stable": false, 90 | "tokenA": "0xF544251D25f3d243A36B07e7E7962a678f952691", 91 | "tokenB": "0x4200000000000000000000000000000000000006" 92 | }, 93 | { 94 | "stable": true, 95 | "tokenA": "0x4A3A6Dd60A34bB2Aba60D73B4C88315E9CeB6A3D", 96 | "tokenB": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA" 97 | }, 98 | { 99 | "stable": false, 100 | "tokenA": "0x4A3A6Dd60A34bB2Aba60D73B4C88315E9CeB6A3D", 101 | "tokenB": "0x4200000000000000000000000000000000000006" 102 | }, 103 | { 104 | "stable": false, 105 | "tokenA": "0xc5102fE9359FD9a28f877a67E36B0F050d81a3CC", 106 | "tokenB": "0x4200000000000000000000000000000000000006" 107 | }, 108 | { 109 | "stable": true, 110 | "tokenA": "0xB79DD08EA68A908A97220C76d19A6aA9cBDE4376", 111 | "tokenB": "0x65a2508C429a6078a7BC2f7dF81aB575BD9D9275" 112 | }, 113 | { 114 | "stable": true, 115 | "tokenA": "0xB79DD08EA68A908A97220C76d19A6aA9cBDE4376", 116 | "tokenB": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA" 117 | }, 118 | { 119 | "stable": false, 120 | "tokenA": "0xDE5ed76E7c05eC5e4572CfC88d1ACEA165109E44", 121 | "tokenB": "0x4200000000000000000000000000000000000006" 122 | }, 123 | { 124 | "stable": true, 125 | "tokenA": "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", 126 | "tokenB": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA" 127 | } 128 | ], 129 | "poolsAero": [ 130 | { 131 | "stable": false, 132 | "token": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA" 133 | }, 134 | { 135 | "stable": false, 136 | "token": "0x4200000000000000000000000000000000000006" 137 | } 138 | ], 139 | "minter": { 140 | "liquid": [ 141 | { 142 | "amount": 50000000000000000000000000, 143 | "wallet": "0xBDE0c70BdC242577c52dFAD53389F82fd149EA5a" 144 | } 145 | ], 146 | "locked": [ 147 | { 148 | "amount": 70000000000000000000000000, 149 | "wallet": "0xBDE0c70BdC242577c52dFAD53389F82fd149EA5a" 150 | }, 151 | { 152 | "amount": 50000000000000000000000000, 153 | "wallet": "0xBDE0c70BdC242577c52dFAD53389F82fd149EA5a" 154 | }, 155 | { 156 | "amount": 25000000000000000000000000, 157 | "wallet": "0xBDE0c70BdC242577c52dFAD53389F82fd149EA5a" 158 | }, 159 | { 160 | "amount": 105000000000000000000000000, 161 | "wallet": "0x834C0DA026d5F933C2c18Fa9F8Ba7f1f792fDa52" 162 | } 163 | ] 164 | } 165 | } -------------------------------------------------------------------------------- /script/constants/TEMPLATE.json: -------------------------------------------------------------------------------- 1 | { 2 | "feeManager": "0x0000000000000000000000000000000000000000", 3 | "allowedManager": "0x0000000000000000000000000000000000000000", 4 | "team": "0x0000000000000000000000000000000000000000", 5 | "WETH": "0x0000000000000000000000000000000000000000", 6 | "whitelistTokens": [ 7 | "0x0000000000000000000000000000000000000000", 8 | "0x0000000000000000000000000000000000000000" 9 | ], 10 | "pools": [ 11 | { 12 | "stable": false, 13 | "tokenA": "0x0000000000000000000000000000000000000000", 14 | "tokenB": "0x0000000000000000000000000000000000000000" 15 | }, 16 | { 17 | "stable": true, 18 | "tokenA": "0x0000000000000000000000000000000000000000", 19 | "tokenB": "0x0000000000000000000000000000000000000000" 20 | } 21 | ], 22 | "poolsAero": [ 23 | { 24 | "stable": false, 25 | "token": "0x0000000000000000000000000000000000000000" 26 | } 27 | ], 28 | "minter": { 29 | "liquid": [ 30 | { 31 | "amount": 0, 32 | "wallet": "0x0000000000000000000000000000000000000000" 33 | }, 34 | { 35 | "amount": 0, 36 | "wallet": "0x0000000000000000000000000000000000000000" 37 | }, 38 | { 39 | "amount": 0, 40 | "wallet": "0x0000000000000000000000000000000000000000" 41 | }, 42 | { 43 | "amount": 0, 44 | "wallet": "0x0000000000000000000000000000000000000000" 45 | }, 46 | { 47 | "amount": 0, 48 | "wallet": "0x0000000000000000000000000000000000000000" 49 | } 50 | ], 51 | "locked": [ 52 | { 53 | "amount": 0, 54 | "wallet": "0x0000000000000000000000000000000000000000" 55 | }, 56 | { 57 | "amount": 0, 58 | "wallet": "0x0000000000000000000000000000000000000000" 59 | }, 60 | { 61 | "amount": 0, 62 | "wallet": "0x0000000000000000000000000000000000000000" 63 | } 64 | ] 65 | } 66 | } -------------------------------------------------------------------------------- /script/constants/airdrop-ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "airdrop": [ 3 | { 4 | "amount": 1000000000000, 5 | "wallet": "0x262595fa2a3A86adACDe208589614d483e3eF1C0" 6 | }, 7 | { 8 | "amount": 2000, 9 | "wallet": "0x64492E25C30031EDAD55E57cEA599CDB1F06dad1" 10 | }, 11 | { 12 | "amount": 3000, 13 | "wallet": "0x924Ba5Ce9f91ddED37b4ebf8c0dc82A40202fc0A" 14 | }, 15 | { 16 | "amount": 4000, 17 | "wallet": "0x265188114EB5d5536BC8654d8e9710FE72C28c4d" 18 | }, 19 | { 20 | "amount": 5000, 21 | "wallet": "0x73B3074ac649A8dc31c2C90a124469456301a30F" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /script/constants/ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowedManager": "0xBA4BB89f4d1E66AA86B60696534892aE0cCf91F5", 3 | "feeManager": "0xBA4BB89f4d1E66AA86B60696534892aE0cCf91F5", 4 | "emergencyCouncil": "0x838352F4E3992187a33a04826273dB3992Ee2b3f", 5 | "team": "0xBA4BB89f4d1E66AA86B60696534892aE0cCf91F5", 6 | "WETH": "0x4200000000000000000000000000000000000006", 7 | "whitelistTokens": [ 8 | "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", 9 | "0x4200000000000000000000000000000000000006", 10 | "0x8aE125E8653821E851F12A49F7765db9a9ce7384", 11 | "0x8c6f28f2F1A3C87F0f938b96d27520d9751ec8d9", 12 | "0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb" 13 | ], 14 | "pools": [ 15 | { 16 | "stable": true, 17 | "tokenA": "0x3E29D3A9316dAB217754d13b28646B76607c5f04", 18 | "tokenB": "0x6806411765Af15Bddd26f8f544A34cC40cb9838B" 19 | }, 20 | { 21 | "stable": false, 22 | "tokenA": "0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb", 23 | "tokenB": "0xFdb794692724153d1488CcdBE0C56c252596735F" 24 | }, 25 | { 26 | "stable": false, 27 | "tokenA": "0x340fE1D898ECCAad394e2ba0fC1F93d27c7b717A", 28 | "tokenB": "0x7F5c764cBc14f9669B88837ca1490cCa17c31607" 29 | }, 30 | { 31 | "stable": true, 32 | "tokenA": "0xc40F949F8a4e094D1b49a23ea9241D289B7b2819", 33 | "tokenB": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1" 34 | }, 35 | { 36 | "stable": true, 37 | "tokenA": "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", 38 | "tokenB": "0xc5b001DC33727F8F26880B184090D3E252470D45" 39 | } 40 | ], 41 | "poolsAero": [ 42 | { 43 | "stable": true, 44 | "tokenB": "0x46f21fDa29F1339e0aB543763FF683D399e393eC" 45 | }, 46 | { 47 | "stable": false, 48 | "tokenA": "0x3417E54A51924C225330f8770514aD5560B9098D" 49 | }, 50 | { 51 | "stable": false, 52 | "tokenB": "0x7F5c764cBc14f9669B88837ca1490cCa17c31607" 53 | }, 54 | { 55 | "stable": false, 56 | "tokenB": "0x7aE97042a4A0eB4D1eB370C34BfEC71042a056B7" 57 | }, 58 | { 59 | "stable": false, 60 | "token": "0x4200000000000000000000000000000000000006" 61 | } 62 | ], 63 | "v1": { 64 | "AERO": "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05", 65 | "VotingEscrow": "0x9c7305eb78a432ced5C4D14Cac27E8Ed569A2e26", 66 | "Voter": "0x09236cfF45047DBee6B921e00704bed6D6B8Cf7e", 67 | "Factory": "0x25CbdDb98b35ab1FF77413456B31EC81A6B6B746", 68 | "Router": "0x9c12939390052919aF3155f41Bf4160Fd3666A6f", 69 | "Gov": "0x64DD805aa894dc001f8505e000c7535179D96C9E", 70 | "Distributor": "0x5d5Bea9f0Fc13d967511668a60a3369fD53F784F", 71 | "Minter": "0x3460Dc71A8863710D1C907B8d9D5DBC053a4102d" 72 | }, 73 | "minter": { 74 | "liquid": [ 75 | { 76 | "amount": 1000000000000, 77 | "wallet": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" 78 | }, 79 | { 80 | "amount": 2000, 81 | "wallet": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" 82 | }, 83 | { 84 | "amount": 3000, 85 | "wallet": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" 86 | }, 87 | { 88 | "amount": 4000, 89 | "wallet": "0x90F79bf6EB2c4f870365E785982E1f101E93b906" 90 | }, 91 | { 92 | "amount": 5000, 93 | "wallet": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" 94 | } 95 | ], 96 | "locked": [ 97 | { 98 | "amount": 1000, 99 | "wallet": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" 100 | }, 101 | { 102 | "amount": 2000, 103 | "wallet": "0x976EA74026E726554dB657fA54763abd0C3a0aa9" 104 | }, 105 | { 106 | "amount": 3000, 107 | "wallet": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955" 108 | } 109 | ] 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /script/constants/output/DeployArtProxy-Base.json: -------------------------------------------------------------------------------- 1 | { 2 | "ArtProxy": "0x024503003fFE9AF285f47c1DaAaA497D9f1166D0" 3 | } 4 | -------------------------------------------------------------------------------- /script/constants/output/DeployCore-Base.json: -------------------------------------------------------------------------------- 1 | { 2 | "AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", 3 | "AirdropDistributor": "0xE4c69af018B2EA9e575026c0472B6531A2bC382F", 4 | "ArtProxy": "0x024503003fFE9AF285f47c1DaAaA497D9f1166D0", 5 | "Distributor": "0x227f65131A261548b057215bB1D5Ab2997964C7d", 6 | "FactoryRegistry": "0x5C3F18F06CC09CA1910767A34a20F771039E37C0", 7 | "Forwarder": "0x15e62707FCA7352fbE35F51a8D6b0F8066A05DCc", 8 | "GaugeFactory": "0x35f35cA5B132CaDf2916BaB57639128eAC5bbcb5", 9 | "ManagedRewardsFactory": "0xFdA1fb5A2a5B23638C7017950506a36dcFD2bDC3", 10 | "Minter": "0xeB018363F0a9Af8f91F06FEe6613a751b2A33FE5", 11 | "PoolFactory": "0x420DD381b31aEf6683db6B902084cB0FFECe40Da", 12 | "Router": "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43", 13 | "Voter": "0x16613524e02ad97eDfeF371bC883F2F5d6C480A5", 14 | "VotingEscrow": "0xeBf418Fe2512e7E6bd9b87a8F0f294aCDC67e6B4", 15 | "VotingRewardsFactory": "0x45cA74858C579E717ee29A86042E0d53B252B504" 16 | } 17 | -------------------------------------------------------------------------------- /script/constants/output/DeployCore-ci.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /script/constants/output/DeployGaugesAndPools-Base.json: -------------------------------------------------------------------------------- 1 | { 2 | "gaugesPools": [ 3 | "0x50f0249B824033Cf0AF0C8b9fe1c67c2842A34d5", 4 | "0xfaE8C18D83655Fbf31af10d2e9A1Ad5bA77D0377", 5 | "0xaD3d4aFd779e4A57734557CcE25F87032614fE47", 6 | "0xf05a3De3dD6910042f506c6105bC14920C4289B6", 7 | "0xeAE066C25106006fB386A3a8b1698A0cB6931c1a", 8 | "0x98F44CE76BDCaD37f00a98B275631B1D796862fa", 9 | "0x5072261dE01Ca7D933C39893C4c4B009a351DF43", 10 | "0x109Cc0f2640457b2A483C2eef189827750A676bB", 11 | "0xC01E2ff20501839db7B28F5Cb3eD2876fEa3d6b1", 12 | "0xDe23611176b16720346f4Df071D1aA01752c68C1", 13 | "0x9a8D9F5C698C1F2B0d48a955BC531F2c7D67Af84", 14 | "0xa81dac2e9caa218Fcd039D7CEdEB7847cf362213", 15 | "0xBb0b5819Fa91076a1D6aaFa0D018E27a416DE029", 16 | "0x81387016224D4EA8F938A1c2207c5Cc4A5AEa393", 17 | "0xb42A9e58773993ed15A5eED65633e85870185e13", 18 | "0x87803Cb321624921cedaAD4555F07Daa0D1Ed325", 19 | "0xa5972f0C07a2D1cc5A8457c28d88ec4f3C009864", 20 | "0x7eC1926A50D2D253011cC9891935eBc476713bb1", 21 | "0xCF1D5Aa63083fda05c7f8871a9fDbfed7bA49060", 22 | "0x9a202c932453fB3d04003979B121E80e5A14eE7b", 23 | "0x96a24aB830D4ec8b1F6f04Ceac104F1A3b211a01" 24 | ], 25 | "pools": [ 26 | "0x723AEf6543aecE026a15662Be4D3fb3424D502A9", 27 | "0x2722C8f9B5E2aC72D1f225f8e8c990E449ba0078", 28 | "0xAf58FDeE432a104a932a899968d60F92838886cd", 29 | "0x2B7704f1cb9324cD8586B33C6c540CbD64E58237", 30 | "0x0B25c51637c43decd6CC1C1e3da4518D54ddb528", 31 | "0xA819Af1CC8AbE618eA8abaDeb464960F7451CEAB", 32 | "0x6e23f90E25f432c9dF9eF9276472DDa3951e4465", 33 | "0x218F511431194B2C756D67a137DE536beA74E498", 34 | "0xf6Aec4F97623E691a9426a69BaF5501509fCa05D", 35 | "0x8b432C54d6e8E1B8D1802753514AB53044Af1861", 36 | "0xb680190fB16f417647e69D6A84719aE9c7E5E20a", 37 | "0x2d25E0514f23c6367687dE89Bd5167dc754D4934", 38 | "0x982A169edFC1cf8Fd38dBeE76735547b23875BD1", 39 | "0x535d457FFC1c4459300Bb1dd775f37808F808282", 40 | "0x75D18ee68bB93BE5cB2dcCFa0d8151E25CBC8Eb8", 41 | "0x1b05e4e814b3431a48b8164c41eaC834d9cE2Da6", 42 | "0x4a3636608d7Bc5776CB19Eb72cAa36EbB9Ea683B", 43 | "0x9e4CB8b916289864321661CE02cf66aa5BA63C94", 44 | "0x6EAB8c1B93f5799daDf2C687a30230a540DbD636", 45 | "0x2223F9FE624F69Da4D8256A7bCc9104FBA7F8f75", 46 | "0x7f670f78B17dEC44d5Ef68a48740b6f8849cc2e6" 47 | ] 48 | } -------------------------------------------------------------------------------- /script/constants/output/DeployGaugesAndPools-ci.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /script/hardhat/DeployGaugesAndPools.ts: -------------------------------------------------------------------------------- 1 | import { getContractAt } from "./utils/helpers"; 2 | import { PoolFactory, Voter } from "../../artifacts/types"; 3 | import jsonConstants from "../constants/Base.json"; 4 | import deployedContracts from "../constants/output/ProtocolOutput.json"; 5 | 6 | async function main() { 7 | const factory = await getContractAt( 8 | "PoolFactory", 9 | deployedContracts.poolFactory 10 | ); 11 | const voter = await getContractAt("Voter", deployedContracts.voter); 12 | 13 | // Deploy non-AERO pools and gauges 14 | for (var i = 0; i < jsonConstants.pools.length; i++) { 15 | const { stable, tokenA, tokenB } = jsonConstants.pools[i]; 16 | await factory.functions["createPool(address,address,bool)"]( 17 | tokenA, 18 | tokenB, 19 | stable, 20 | { gasLimit: 5000000 } 21 | ); 22 | let pool = await factory.functions["getPool(address,address,bool)"]( 23 | tokenA, 24 | tokenB, 25 | stable, 26 | { 27 | gasLimit: 5000000, 28 | } 29 | ); 30 | await voter.createGauge( 31 | deployedContracts.poolFactory, // PoolFactory 32 | pool[0], 33 | { gasLimit: 5000000 } 34 | ); 35 | } 36 | 37 | // Deploy AERO pools and gauges 38 | for (var i = 0; i < jsonConstants.poolsAero.length; i++) { 39 | const [stable, token] = Object.values(jsonConstants.poolsAero[i]); 40 | await factory.functions["createPool(address,address,bool)"]( 41 | deployedContracts.AERO, 42 | token, 43 | stable, 44 | { 45 | gasLimit: 5000000, 46 | } 47 | ); 48 | let pool = await factory.functions["getPool(address,address,bool)"]( 49 | deployedContracts.AERO, 50 | token, 51 | stable, 52 | { 53 | gasLimit: 5000000, 54 | } 55 | ); 56 | await voter.createGauge( 57 | deployedContracts.poolFactory, // PoolFactory 58 | pool[0], 59 | { gasLimit: 5000000 } 60 | ); 61 | } 62 | } 63 | 64 | main().catch((error) => { 65 | console.error(error); 66 | process.exitCode = 1; 67 | }); 68 | -------------------------------------------------------------------------------- /script/hardhat/DeployGovernors.ts: -------------------------------------------------------------------------------- 1 | import { getContractAt, deploy } from "./utils/helpers"; 2 | import { ProtocolGovernor, EpochGovernor } from "../../artifacts/types"; 3 | import jsonConstants from "../constants/Base.json"; 4 | import deployedContracts from "../constants/output/ProtocolOutput.json"; 5 | 6 | async function main() { 7 | const governor = await deploy( 8 | "ProtocolGovernor", 9 | undefined, 10 | deployedContracts.votingEscrow 11 | ); 12 | const epochGovernor = await deploy( 13 | "EpochGovernor", 14 | undefined, 15 | deployedContracts.forwarder, 16 | deployedContracts.votingEscrow, 17 | deployedContracts.minter 18 | ); 19 | 20 | await governor.setVetoer(jsonConstants.team); 21 | } 22 | 23 | main().catch((error) => { 24 | console.error(error); 25 | process.exitCode = 1; 26 | }); 27 | -------------------------------------------------------------------------------- /script/hardhat/DistributeAirdrops.ts: -------------------------------------------------------------------------------- 1 | import { getContractAt } from "./utils/helpers"; 2 | import { AirdropDistributor } from "../../artifacts/types"; 3 | import { ethers } from "hardhat"; 4 | import path from "path"; 5 | import * as fs from "fs"; 6 | import Decimal from "decimal.js"; 7 | 8 | async function main() { 9 | const BATCH_SIZE = 10; 10 | 11 | // parse contract 12 | const root = path.resolve(__dirname, "../"); 13 | const rawContractData = fs.readFileSync( 14 | `${root}/constants/output/DeployCore-${process.env.OUTPUT_FILENAME}` 15 | ); 16 | const contractData = JSON.parse(rawContractData.toString()); 17 | const distributor = await getContractAt( 18 | "AirdropDistributor", 19 | contractData.AirdropDistributor 20 | ); 21 | 22 | // parse airdrop information 23 | const rawdata = fs.readFileSync( 24 | `${root}/constants/${process.env.AIRDROPS_FILENAME}` 25 | ); 26 | const data = JSON.parse(rawdata.toString()).airdrop; 27 | const addresses: string[] = data.map((entry: any) => entry.owner); 28 | const values: Decimal[] = data.map( 29 | (entry: any) => new Decimal(entry.airdrop) 30 | ); 31 | const sum: Decimal = values.reduce( 32 | (total, currentAmount) => total.plus(currentAmount), 33 | new Decimal(0) 34 | ); 35 | const expected = new Decimal(200_000_000 * 1e18); 36 | if (sum.greaterThan(expected)) { 37 | const diff = sum.minus(expected); // calculate difference, will be dust 38 | values[0] = values[0].minus(diff); // remove from very first value 39 | } 40 | 41 | const amounts: string[] = values.map((entry: Decimal) => entry.toFixed()); // remove scientific notation 42 | 43 | let count = 0; 44 | for (let i = 0; i < addresses.length; i += BATCH_SIZE) { 45 | const end = Math.min(i + BATCH_SIZE, addresses.length); 46 | const addressBatch = addresses.slice(i, end); 47 | const amountBatch = amounts.slice(i, end); 48 | 49 | const feeData: ethers.types.FeeData = await ethers.provider.getFeeData(); 50 | const tx = await distributor.distributeTokens(addressBatch, amountBatch, { 51 | gasLimit: 15000000, 52 | maxPriorityFeePerGas: feeData.lastBaseFeePerGas.div(50), 53 | }); 54 | 55 | count++; 56 | console.log("Tx No.:", count); 57 | console.log("Transaction sent:", tx.hash); 58 | await tx.wait(); 59 | console.log("Transaction confirmed:", tx.hash); 60 | } 61 | 62 | await distributor.renounceOwnership(); 63 | } 64 | 65 | main().catch((error) => { 66 | console.error(error); 67 | process.exitCode = 1; 68 | }); 69 | -------------------------------------------------------------------------------- /script/hardhat/README.md: -------------------------------------------------------------------------------- 1 | # Hardhat Deployment Instructions 2 | 3 | Hardhat support was included as a way to provide an easy way to test the contracts on Tenderly with contract verification. 4 | 5 | ## Set Up 6 | 7 | 1. Create a new fork on Tenderly. Once you have the fork, copy the fork id number (the component after `/fork`/ in the URL) and set it as your `TENDERLY_FORK_ID` in the `.env` file. The other fields that must be set include `PRIVATE_KEY_DEPLOY`, set to a private key used for testing. 8 | 2. Install packages via `npm install` or `yarn install`. 9 | 3. Follow the instructions of the [tenderly hardhat package](https://github.com/Tenderly/hardhat-tenderly/tree/master/packages/tenderly-hardhat) to install the tenderly cli and login. 10 | 11 | ## Deployment 12 | 13 | 1. Deploy the Protocol contracts: 14 | 15 | `npx hardhat run script/hardhat/DeployCore.ts --network tenderly` 16 | 17 | The contracts that were deployed will be saved in `script/constants/output/ProtocolOutput.json`. 18 | 19 | 2. Deploy pools and create gauges for them. 20 | 21 | `npx hardhat run script/hardhat/DeployGaugesAndPools.ts --network tenderly` 22 | 23 | 3. Deploy Governors. You will need to set the governors manually in tenderly. 24 | 25 | `npx hardhat run script/hardhat/DeployGovernors.ts --network tenderly` 26 | -------------------------------------------------------------------------------- /script/hardhat/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "@ethersproject/contracts"; 2 | import { ethers } from "hardhat"; 3 | import { Libraries } from "hardhat/types"; 4 | 5 | export async function deployLibrary( 6 | typeName: string, 7 | ...args: any[] 8 | ): Promise { 9 | const ctrFactory = await ethers.getContractFactory(typeName); 10 | 11 | const ctr = (await ctrFactory.deploy(...args)) as unknown as Contract; 12 | await ctr.deployed(); 13 | return ctr; 14 | } 15 | 16 | export async function deploy( 17 | typeName: string, 18 | libraries?: Libraries, 19 | ...args: any[] 20 | ): Promise { 21 | const ctrFactory = await ethers.getContractFactory(typeName, { libraries }); 22 | 23 | const ctr = (await ctrFactory.deploy(...args)) as unknown as Type; 24 | await (ctr as unknown as Contract).deployed(); 25 | return ctr; 26 | } 27 | 28 | export async function getContractAt( 29 | typeName: string, 30 | address: string 31 | ): Promise { 32 | const ctr = (await ethers.getContractAt( 33 | typeName, 34 | address 35 | )) as unknown as Type; 36 | return ctr; 37 | } 38 | -------------------------------------------------------------------------------- /test/Aero.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "./BaseTest.sol"; 5 | 6 | contract AeroTest is BaseTest { 7 | Aero token; 8 | 9 | function _setUp() public override { 10 | token = new Aero(); 11 | } 12 | 13 | function testCannotSetMinterIfNotMinter() public { 14 | vm.prank(address(owner2)); 15 | vm.expectRevert(IAero.NotMinter.selector); 16 | token.setMinter(address(owner3)); 17 | } 18 | 19 | function testSetMinter() public { 20 | token.setMinter(address(owner3)); 21 | 22 | assertEq(token.minter(), address(owner3)); 23 | } 24 | 25 | function testCannotMintIfNotMinter() public { 26 | vm.prank(address(owner2)); 27 | vm.expectRevert(IAero.NotMinter.selector); 28 | token.mint(address(owner2), TOKEN_1); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/AirdropDistributor.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "forge-std/StdJson.sol"; 6 | import {SafeCastLibrary} from "../contracts/libraries/SafeCastLibrary.sol"; 7 | 8 | import "./BaseTest.sol"; 9 | 10 | contract AirdropDistributorTest is BaseTest { 11 | using stdStorage for StdStorage; 12 | using SafeCastLibrary for int128; 13 | 14 | event Airdrop(address indexed _destination, uint256 _amount, uint256 _tokenId); 15 | 16 | uint256 public constant N_TEST_WALLETS = 66; 17 | uint256 public constant INITIAL_DISTRIBUTOR_BALANCE = TOKEN_100M; 18 | 19 | constructor() { 20 | deploymentType = Deployment.DEFAULT; 21 | } 22 | 23 | function _setUp() public override { 24 | address[] memory _wallets = new address[](1); 25 | uint256[] memory _amounts = new uint256[](1); 26 | _wallets[0] = address(airdrop); 27 | _amounts[0] = INITIAL_DISTRIBUTOR_BALANCE; 28 | 29 | // Mints tokens to Airdrop Distributor 30 | assertEq(AERO.balanceOf(address(airdrop)), 0); 31 | vm.prank(minter.team()); 32 | minter.initialize( 33 | IMinter.AirdropParams({ 34 | liquidWallets: _wallets, 35 | liquidAmounts: _amounts, 36 | lockedWallets: new address[](0), 37 | lockedAmounts: new uint256[](0) 38 | }) 39 | ); 40 | } 41 | 42 | function testAirdropDistributorDeployment() public { 43 | assertEq(airdrop.owner(), address(owner)); 44 | assertEq(address(airdrop.ve()), address(escrow)); 45 | assertEq(address(airdrop.aero()), address(AERO)); 46 | assertEq(AERO.balanceOf(address(airdrop)), INITIAL_DISTRIBUTOR_BALANCE); 47 | assertEq(escrow.balanceOf(address(airdrop)), 0); 48 | } 49 | 50 | function testAirdropDistributor() public { 51 | uint256 preAeroBal = AERO.balanceOf(address(airdrop)); 52 | 53 | (address[] memory _wallets, uint256[] memory _amounts) = _getWalletsAmounts(N_TEST_WALLETS, TOKEN_10K); 54 | uint256 _len = _wallets.length; 55 | uint256 sum; 56 | // Ensures AirdropDistributor has enough balance 57 | for (uint256 i = 0; i < _len; i++) { 58 | sum += _amounts[i]; 59 | } 60 | assertGe(AERO.balanceOf(address(airdrop)), sum); 61 | 62 | // Expects emission of all events from the Airdrop 63 | for (uint256 i = 0; i < _len; i++) { 64 | vm.expectEmit(true, false, false, true, address(airdrop)); 65 | emit Airdrop(_wallets[i], _amounts[i], i + 1); 66 | } 67 | // Airdrops tokens 68 | vm.prank(address(owner)); 69 | airdrop.distributeTokens(_wallets, _amounts); 70 | uint256 newAeroBal = AERO.balanceOf(address(airdrop)); 71 | 72 | // Asserts Distributor's token balances 73 | assertEq(preAeroBal - sum, newAeroBal); 74 | assertEq(escrow.balanceOf(address(airdrop)), 0); 75 | // Ensures every token is permanently locked, with the rightful amount and predicted TokenId 76 | for (uint256 i = 0; i < _len; i++) { 77 | uint256 tokenId = i + 1; 78 | assertEq(escrow.balanceOf(_wallets[i]), 1); 79 | IVotingEscrow.LockedBalance memory locked = escrow.locked(tokenId); 80 | assertEq(locked.amount.toUint256(), _amounts[i]); 81 | assertTrue(locked.isPermanent); 82 | assertEq(locked.end, 0); 83 | } 84 | } 85 | 86 | function testCannotAirdropIfInvalidParams() public { 87 | (address[] memory _wallets, ) = _getWalletsAmounts(N_TEST_WALLETS, TOKEN_10K); 88 | (, uint256[] memory _amounts) = _getWalletsAmounts(N_TEST_WALLETS + 1, TOKEN_10K); 89 | 90 | vm.prank(address(owner)); 91 | vm.expectRevert(IAirdropDistributor.InvalidParams.selector); 92 | airdrop.distributeTokens(_wallets, _amounts); 93 | } 94 | 95 | function testCannotAirdropIfNotOwner() public { 96 | (address[] memory _wallets, uint256[] memory _amounts) = _getWalletsAmounts(N_TEST_WALLETS, TOKEN_10K); 97 | 98 | vm.prank(address(owner2)); 99 | vm.expectRevert("Ownable: caller is not the owner"); 100 | airdrop.distributeTokens(_wallets, _amounts); 101 | } 102 | 103 | function testCannotAirdropIfInsufficientBalance() public { 104 | address[] memory _wallets = new address[](1); 105 | uint256[] memory _amounts = new uint256[](1); 106 | _wallets[0] = address(owner2); 107 | _amounts[0] = INITIAL_DISTRIBUTOR_BALANCE; 108 | uint256 _len = _wallets.length; 109 | uint256 sum; 110 | // Ensures AirdropDistributor has enough balance 111 | for (uint256 i = 0; i < _len; i++) { 112 | sum += _amounts[i]; 113 | } 114 | stdstore.target(address(AERO)).sig("balanceOf(address)").with_key(address(airdrop)).checked_write(sum - 1); 115 | 116 | vm.expectRevert(IAirdropDistributor.InsufficientBalance.selector); 117 | airdrop.distributeTokens(_wallets, _amounts); 118 | assertEq(AERO.balanceOf(address(airdrop)), sum - 1); 119 | } 120 | 121 | function _getWalletsAmounts( 122 | uint256 walletQuantity, 123 | uint256 amount 124 | ) internal pure returns (address[] memory _wallets, uint256[] memory _amounts) { 125 | _wallets = new address[](walletQuantity); 126 | _amounts = new uint256[](walletQuantity); 127 | 128 | for (uint32 i = 0; i < walletQuantity; i++) { 129 | _wallets[i] = vm.addr(i + 1); 130 | // Multiplying by i+1 to test different amounts 131 | _amounts[i] = (i + 1) * amount; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /test/Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {ManagedRewardsFactory} from "contracts/factories/ManagedRewardsFactory.sol"; 5 | import {VotingRewardsFactory} from "contracts/factories/VotingRewardsFactory.sol"; 6 | import {GaugeFactory} from "contracts/factories/GaugeFactory.sol"; 7 | import {PoolFactory, IPoolFactory} from "contracts/factories/PoolFactory.sol"; 8 | import {IFactoryRegistry, FactoryRegistry} from "contracts/factories/FactoryRegistry.sol"; 9 | import {Pool} from "contracts/Pool.sol"; 10 | import {IMinter, Minter} from "contracts/Minter.sol"; 11 | import {IReward, Reward} from "contracts/rewards/Reward.sol"; 12 | import {FeesVotingReward} from "contracts/rewards/FeesVotingReward.sol"; 13 | import {BribeVotingReward} from "contracts/rewards/BribeVotingReward.sol"; 14 | import {FreeManagedReward} from "contracts/rewards/FreeManagedReward.sol"; 15 | import {LockedManagedReward} from "contracts/rewards/LockedManagedReward.sol"; 16 | import {IGauge, Gauge} from "contracts/gauges/Gauge.sol"; 17 | import {PoolFees} from "contracts/PoolFees.sol"; 18 | import {RewardsDistributor, IRewardsDistributor} from "contracts/RewardsDistributor.sol"; 19 | import {IAirdropDistributor, AirdropDistributor} from "contracts/AirdropDistributor.sol"; 20 | import {IRouter, Router} from "contracts/Router.sol"; 21 | import {IAero, Aero} from "contracts/Aero.sol"; 22 | import {IVoter, Voter} from "contracts/Voter.sol"; 23 | import {VeArtProxy} from "contracts/VeArtProxy.sol"; 24 | import {IVotingEscrow, VotingEscrow} from "contracts/VotingEscrow.sol"; 25 | import {ProtocolGovernor} from "contracts/ProtocolGovernor.sol"; 26 | import {EpochGovernor} from "contracts/EpochGovernor.sol"; 27 | import {SafeCastLibrary} from "contracts/libraries/SafeCastLibrary.sol"; 28 | import {IWETH} from "contracts/interfaces/IWETH.sol"; 29 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 30 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 31 | import {SigUtils} from "test/utils/SigUtils.sol"; 32 | import {Forwarder} from "@opengsn/contracts/src/forwarder/Forwarder.sol"; 33 | 34 | import "forge-std/Script.sol"; 35 | import "forge-std/Test.sol"; 36 | 37 | /// @notice Base contract used for tests and deployment scripts 38 | abstract contract Base is Script, Test { 39 | enum Deployment { 40 | DEFAULT, 41 | FORK, 42 | CUSTOM 43 | } 44 | /// @dev Determines whether or not to use the base set up configuration 45 | /// Local deployment used by default 46 | Deployment deploymentType; 47 | 48 | IWETH public WETH; 49 | Aero public AERO; 50 | address[] public tokens; 51 | 52 | /// @dev Core Deployment 53 | Forwarder public forwarder; 54 | Pool public implementation; 55 | Router public router; 56 | VotingEscrow public escrow; 57 | VeArtProxy public artProxy; 58 | PoolFactory public factory; 59 | FactoryRegistry public factoryRegistry; 60 | GaugeFactory public gaugeFactory; 61 | VotingRewardsFactory public votingRewardsFactory; 62 | ManagedRewardsFactory public managedRewardsFactory; 63 | Voter public voter; 64 | RewardsDistributor public distributor; 65 | Minter public minter; 66 | AirdropDistributor public airdrop; 67 | Gauge public gauge; 68 | ProtocolGovernor public governor; 69 | EpochGovernor public epochGovernor; 70 | 71 | /// @dev Global address to set 72 | address public allowedManager; 73 | 74 | function _coreSetup() public { 75 | deployFactories(); 76 | 77 | forwarder = new Forwarder(); 78 | 79 | escrow = new VotingEscrow(address(forwarder), address(AERO), address(factoryRegistry)); 80 | artProxy = new VeArtProxy(address(escrow)); 81 | escrow.setArtProxy(address(artProxy)); 82 | 83 | // Setup voter and distributor 84 | distributor = new RewardsDistributor(address(escrow)); 85 | voter = new Voter(address(forwarder), address(escrow), address(factoryRegistry)); 86 | 87 | escrow.setVoterAndDistributor(address(voter), address(distributor)); 88 | escrow.setAllowedManager(allowedManager); 89 | 90 | // Setup router 91 | router = new Router( 92 | address(forwarder), 93 | address(factoryRegistry), 94 | address(factory), 95 | address(voter), 96 | address(WETH) 97 | ); 98 | 99 | // Setup minter 100 | minter = new Minter(address(voter), address(escrow), address(distributor)); 101 | distributor.setMinter(address(minter)); 102 | AERO.setMinter(address(minter)); 103 | 104 | airdrop = new AirdropDistributor(address(escrow)); 105 | 106 | /// @dev tokens are already set in the respective setupBefore() 107 | voter.initialize(tokens, address(minter)); 108 | } 109 | 110 | function deployFactories() public { 111 | implementation = new Pool(); 112 | factory = new PoolFactory(address(implementation)); 113 | 114 | votingRewardsFactory = new VotingRewardsFactory(); 115 | gaugeFactory = new GaugeFactory(); 116 | managedRewardsFactory = new ManagedRewardsFactory(); 117 | factoryRegistry = new FactoryRegistry( 118 | address(factory), 119 | address(votingRewardsFactory), 120 | address(gaugeFactory), 121 | address(managedRewardsFactory) 122 | ); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /test/Forwarder.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "./BaseTest.sol"; 5 | import "./utils/ERC2771Helper.sol"; 6 | 7 | contract ForwarderTest is BaseTest { 8 | using ECDSA for bytes32; 9 | 10 | // first public/private key provided by anvil 11 | address sender = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; 12 | uint256 senderPrivateKey = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; 13 | 14 | ERC2771Helper erc2771Helper; 15 | 16 | function _setUp() public override { 17 | erc2771Helper = new ERC2771Helper(); 18 | 19 | // fund forwarder with ETH for txs and fund from with AERO 20 | vm.deal(address(forwarder), 1e18); 21 | deal(address(AERO), sender, TOKEN_100K, true); 22 | 23 | // Approve owner and sender transfers of AERO 24 | AERO.approve(address(escrow), type(uint256).max); 25 | vm.prank(sender); 26 | AERO.approve(address(escrow), type(uint256).max); 27 | } 28 | 29 | function testForwarderCreateLock() public { 30 | bytes memory payload = abi.encodeWithSelector(escrow.createLock.selector, TOKEN_1, MAXTIME); 31 | bytes32 requestType = erc2771Helper.registerRequestType( 32 | forwarder, 33 | "createLock", 34 | "uint256 _value,uint256 _lockDuration" 35 | ); 36 | 37 | handleRequest(address(escrow), payload, requestType); 38 | assertEq(escrow.ownerOf(1), sender); 39 | } 40 | 41 | function testForwarderVote() public { 42 | skip(1 hours + 1); 43 | escrow.createLockFor(TOKEN_1, MAXTIME, sender); 44 | address[] memory pools = new address[](1); 45 | pools[0] = address(pool); 46 | uint256[] memory weights = new uint256[](1); 47 | weights[0] = 10000; 48 | 49 | // build request 50 | bytes memory payload = abi.encodeWithSelector(voter.vote.selector, 1, pools, weights); 51 | bytes32 requestType = erc2771Helper.registerRequestType( 52 | forwarder, 53 | "vote", 54 | "uint256 _tokenId,address[] _poolVote,uint256[] _weights" 55 | ); 56 | 57 | handleRequest(address(voter), payload, requestType); 58 | assertTrue(escrow.voted(1)); 59 | } 60 | 61 | function handleRequest(address _to, bytes memory payload, bytes32 requestType) internal { 62 | IForwarder.ForwardRequest memory request = IForwarder.ForwardRequest({ 63 | from: sender, 64 | to: _to, 65 | value: 0, 66 | gas: 5_000_000, 67 | nonce: forwarder.getNonce(sender), 68 | data: payload, 69 | validUntil: 0 70 | }); 71 | 72 | // TODO: move this to Base.sol once working 73 | bytes32 domainSeparator = erc2771Helper.registerDomain( 74 | forwarder, 75 | Strings.toHexString(uint256(uint160(_to)), 20), 76 | "1" 77 | ); 78 | 79 | bytes memory suffixData = "0"; 80 | bytes32 digest = keccak256( 81 | abi.encodePacked( 82 | "\x19\x01", 83 | domainSeparator, 84 | keccak256(forwarder._getEncoded(request, requestType, suffixData)) 85 | ) 86 | ); 87 | 88 | // sign request 89 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(senderPrivateKey, digest); 90 | bytes memory signature = abi.encodePacked(r, s, v); 91 | 92 | require(digest.recover(signature) == request.from, "FWD: signature mismatch"); 93 | 94 | forwarder.execute(request, domainSeparator, requestType, suffixData, signature); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/FreeManagedReward.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "./BaseTest.sol"; 5 | 6 | contract FreeManagedRewardTest is BaseTest { 7 | event NotifyReward(address indexed from, address indexed reward, uint256 indexed epoch, uint256 amount); 8 | 9 | FreeManagedReward freeManagedReward; 10 | uint256 mTokenId; 11 | 12 | function _setUp() public override { 13 | // ve 14 | AERO.approve(address(escrow), TOKEN_1); 15 | escrow.createLock(TOKEN_1, MAXTIME); 16 | vm.startPrank(address(owner2)); 17 | AERO.approve(address(escrow), TOKEN_1); 18 | escrow.createLock(TOKEN_1, MAXTIME); 19 | vm.stopPrank(); 20 | vm.startPrank(address(owner3)); 21 | AERO.approve(address(escrow), TOKEN_1); 22 | escrow.createLock(TOKEN_1, MAXTIME); 23 | vm.stopPrank(); 24 | 25 | vm.prank(address(governor)); 26 | mTokenId = escrow.createManagedLockFor(address(owner4)); 27 | freeManagedReward = FreeManagedReward(escrow.managedToFree(mTokenId)); 28 | skip(1); 29 | } 30 | 31 | function testCannotNotifyRewardWithZeroAmount() public { 32 | vm.expectRevert(IReward.ZeroAmount.selector); 33 | freeManagedReward.notifyRewardAmount(address(LR), 0); 34 | } 35 | 36 | function testCannotNotifyRewardAmountIfTokenNotWhitelisted() public { 37 | address token = address(new MockERC20("TEST", "TEST", 18)); 38 | 39 | assertEq(voter.isWhitelistedToken(token), false); 40 | 41 | vm.expectRevert(IReward.NotWhitelisted.selector); 42 | freeManagedReward.notifyRewardAmount(token, TOKEN_1); 43 | } 44 | 45 | function testNotifyRewardAmount() public { 46 | LR.approve(address(freeManagedReward), TOKEN_1); 47 | uint256 pre = LR.balanceOf(address(owner)); 48 | vm.expectEmit(true, true, true, true, address(freeManagedReward)); 49 | emit NotifyReward(address(owner), address(LR), 604800, TOKEN_1); 50 | freeManagedReward.notifyRewardAmount(address(LR), TOKEN_1); 51 | uint256 post = LR.balanceOf(address(owner)); 52 | 53 | assertEq(freeManagedReward.isReward(address(LR)), true); 54 | assertEq(freeManagedReward.tokenRewardsPerEpoch(address(LR), 604800), TOKEN_1); 55 | assertEq(pre - post, TOKEN_1); 56 | assertEq(LR.balanceOf(address(freeManagedReward)), TOKEN_1); 57 | 58 | skip(1 hours); 59 | 60 | LR.approve(address(freeManagedReward), TOKEN_1 * 2); 61 | pre = LR.balanceOf(address(owner)); 62 | vm.expectEmit(true, true, true, true, address(freeManagedReward)); 63 | emit NotifyReward(address(owner), address(LR), 604800, TOKEN_1 * 2); 64 | freeManagedReward.notifyRewardAmount(address(LR), TOKEN_1 * 2); 65 | post = LR.balanceOf(address(owner)); 66 | 67 | assertEq(freeManagedReward.tokenRewardsPerEpoch(address(LR), 604800), TOKEN_1 * 3); 68 | assertEq(pre - post, TOKEN_1 * 2); 69 | assertEq(LR.balanceOf(address(freeManagedReward)), TOKEN_1 * 3); 70 | } 71 | 72 | function testCannotGetRewardIfNotOwnerOrApproved() public { 73 | skip(1 weeks / 2); 74 | 75 | uint256 reward = TOKEN_1; 76 | 77 | // create a bribe 78 | LR.approve(address(freeManagedReward), reward); 79 | freeManagedReward.notifyRewardAmount((address(LR)), reward); 80 | 81 | voter.depositManaged(1, mTokenId); 82 | 83 | skipToNextEpoch(1); 84 | 85 | // rewards 86 | address[] memory rewards = new address[](1); 87 | rewards[0] = address(LR); 88 | 89 | vm.prank(address(owner2)); 90 | vm.expectRevert(IReward.NotAuthorized.selector); 91 | freeManagedReward.getReward(1, rewards); 92 | } 93 | 94 | function testGetReward() public { 95 | skip(1 weeks / 2); 96 | 97 | uint256 reward = TOKEN_1; 98 | 99 | // create a bribe 100 | LR.approve(address(freeManagedReward), reward); 101 | freeManagedReward.notifyRewardAmount((address(LR)), reward); 102 | 103 | voter.depositManaged(1, mTokenId); 104 | 105 | skipToNextEpoch(1); 106 | 107 | // rewards 108 | address[] memory rewards = new address[](1); 109 | rewards[0] = address(LR); 110 | 111 | uint256 pre = LR.balanceOf(address(owner)); 112 | freeManagedReward.getReward(1, rewards); 113 | uint256 post = LR.balanceOf(address(owner)); 114 | 115 | assertEq(post - pre, TOKEN_1); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /test/LockedManagedReward.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "./BaseTest.sol"; 5 | 6 | contract LockedManagedRewardTest is BaseTest { 7 | event NotifyReward(address indexed from, address indexed reward, uint256 indexed epoch, uint256 amount); 8 | 9 | LockedManagedReward lockedManagedReward; 10 | uint256 mTokenId; 11 | 12 | function _setUp() public override { 13 | // ve 14 | AERO.approve(address(escrow), TOKEN_1); 15 | escrow.createLock(TOKEN_1, MAXTIME); 16 | vm.startPrank(address(owner2)); 17 | AERO.approve(address(escrow), TOKEN_1); 18 | escrow.createLock(TOKEN_1, MAXTIME); 19 | vm.stopPrank(); 20 | vm.startPrank(address(owner3)); 21 | AERO.approve(address(escrow), TOKEN_1); 22 | escrow.createLock(TOKEN_1, MAXTIME); 23 | vm.stopPrank(); 24 | 25 | vm.prank(address(governor)); 26 | mTokenId = escrow.createManagedLockFor(address(owner4)); 27 | lockedManagedReward = LockedManagedReward(escrow.managedToLocked(mTokenId)); 28 | skip(1); 29 | } 30 | 31 | function testCannotNotifyRewardIfNotVotingEscrow() public { 32 | vm.prank(address(owner2)); 33 | vm.expectRevert(IReward.NotVotingEscrow.selector); 34 | lockedManagedReward.notifyRewardAmount(address(AERO), 0); 35 | } 36 | 37 | function testCannotNotifyRewardWithZeroAmount() public { 38 | vm.prank(address(escrow)); 39 | vm.expectRevert(IReward.ZeroAmount.selector); 40 | lockedManagedReward.notifyRewardAmount(address(AERO), 0); 41 | } 42 | 43 | function testCannotNotifyRewardAmountIfNotEscrowToken() public { 44 | address token = address(new MockERC20("TEST", "TEST", 18)); 45 | assertEq(voter.isWhitelistedToken(token), false); 46 | 47 | vm.prank(address(escrow)); 48 | vm.expectRevert(IReward.NotEscrowToken.selector); 49 | lockedManagedReward.notifyRewardAmount(token, TOKEN_1); 50 | } 51 | 52 | function testNotifyRewardAmount() public { 53 | deal(address(AERO), address(escrow), TOKEN_1 * 3); 54 | 55 | vm.prank(address(escrow)); 56 | AERO.approve(address(lockedManagedReward), TOKEN_1); 57 | uint256 pre = AERO.balanceOf(address(escrow)); 58 | vm.prank(address(escrow)); 59 | vm.expectEmit(true, true, true, true, address(lockedManagedReward)); 60 | emit NotifyReward(address(escrow), address(AERO), 604800, TOKEN_1); 61 | lockedManagedReward.notifyRewardAmount(address(AERO), TOKEN_1); 62 | uint256 post = AERO.balanceOf(address(escrow)); 63 | 64 | assertEq(lockedManagedReward.isReward(address(AERO)), true); 65 | assertEq(lockedManagedReward.tokenRewardsPerEpoch(address(AERO), 604800), TOKEN_1); 66 | assertEq(pre - post, TOKEN_1); 67 | assertEq(AERO.balanceOf(address(lockedManagedReward)), TOKEN_1); 68 | 69 | skip(1 hours); 70 | 71 | vm.prank(address(escrow)); 72 | AERO.approve(address(lockedManagedReward), TOKEN_1 * 2); 73 | pre = AERO.balanceOf(address(escrow)); 74 | vm.prank(address(escrow)); 75 | vm.expectEmit(true, true, true, true, address(lockedManagedReward)); 76 | emit NotifyReward(address(escrow), address(AERO), 604800, TOKEN_1 * 2); 77 | lockedManagedReward.notifyRewardAmount(address(AERO), TOKEN_1 * 2); 78 | post = AERO.balanceOf(address(escrow)); 79 | 80 | assertEq(lockedManagedReward.tokenRewardsPerEpoch(address(AERO), 604800), TOKEN_1 * 3); 81 | assertEq(pre - post, TOKEN_1 * 2); 82 | assertEq(AERO.balanceOf(address(lockedManagedReward)), TOKEN_1 * 3); 83 | } 84 | 85 | function testCannotGetRewardIfNotSingleToken() public { 86 | skip(1 weeks / 2); 87 | 88 | voter.depositManaged(1, mTokenId); 89 | _addLockedReward(TOKEN_1); 90 | 91 | skipToNextEpoch(1); 92 | 93 | address[] memory rewards = new address[](2); 94 | rewards[0] = address(AERO); 95 | rewards[1] = address(WETH); 96 | 97 | vm.prank(address(escrow)); 98 | vm.expectRevert(IReward.NotSingleToken.selector); 99 | lockedManagedReward.getReward(1, rewards); 100 | } 101 | 102 | function testCannotGetRewardIfNotEscrowToken() public { 103 | skip(1 weeks / 2); 104 | 105 | address token = address(new MockERC20("TEST", "TEST", 18)); 106 | address[] memory rewards = new address[](1); 107 | rewards[0] = token; 108 | 109 | vm.prank(address(escrow)); 110 | vm.expectRevert(IReward.NotEscrowToken.selector); 111 | lockedManagedReward.getReward(1, rewards); 112 | } 113 | 114 | function testCannotGetRewardIfNotVotingEscrow() public { 115 | skip(1 weeks / 2); 116 | 117 | voter.depositManaged(1, mTokenId); 118 | _addLockedReward(TOKEN_1); 119 | 120 | skipToNextEpoch(1); 121 | 122 | address[] memory rewards = new address[](1); 123 | rewards[0] = address(AERO); 124 | 125 | vm.prank(address(owner2)); 126 | vm.expectRevert(IReward.NotVotingEscrow.selector); 127 | lockedManagedReward.getReward(1, rewards); 128 | } 129 | 130 | function testGetReward() public { 131 | skip(1 weeks / 2); 132 | 133 | uint256 pre = convert(escrow.locked(1).amount); 134 | voter.depositManaged(1, mTokenId); 135 | _addLockedReward(TOKEN_1); 136 | 137 | skipToNextEpoch(1 hours + 1); 138 | 139 | voter.withdrawManaged(1); 140 | uint256 post = convert(escrow.locked(1).amount); 141 | 142 | assertEq(post - pre, TOKEN_1); 143 | } 144 | 145 | function _addLockedReward(uint256 _amount) internal { 146 | deal(address(AERO), address(distributor), _amount); 147 | vm.startPrank(address(distributor)); 148 | AERO.approve(address(escrow), _amount); 149 | escrow.depositFor(mTokenId, _amount); 150 | vm.stopPrank(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /test/Oracle.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "./BaseTest.sol"; 5 | 6 | contract OracleTest is BaseTest { 7 | constructor() { 8 | deploymentType = Deployment.CUSTOM; 9 | } 10 | 11 | function deployBaseCoins() public { 12 | deployOwners(); 13 | deployCoins(); 14 | mintStables(); 15 | uint256[] memory amounts = new uint256[](1); 16 | amounts[0] = 1e25; 17 | mintToken(address(AERO), owners, amounts); 18 | escrow = VotingEscrow(address(AERO)); 19 | } 20 | 21 | function confirmTokensForFraxUsdc() public { 22 | deployBaseCoins(); 23 | deployFactories(); 24 | factory.setFee(true, 1); 25 | factory.setFee(false, 1); 26 | 27 | escrow = new VotingEscrow(address(forwarder), address(AERO), address(factoryRegistry)); 28 | VeArtProxy artProxy = new VeArtProxy(address(escrow)); 29 | escrow.setArtProxy(address(artProxy)); 30 | voter = new Voter(address(forwarder), address(escrow), address(factoryRegistry)); 31 | router = new Router( 32 | address(forwarder), 33 | address(factoryRegistry), 34 | address(factory), 35 | address(voter), 36 | address(WETH) 37 | ); 38 | deployPoolWithOwner(address(owner)); 39 | 40 | (address token0, address token1) = router.sortTokens(address(USDC), address(FRAX)); 41 | assertEq((pool.token0()), token0); 42 | assertEq((pool.token1()), token1); 43 | } 44 | 45 | function mintAndBurnTokensForPoolFraxUsdc() public { 46 | confirmTokensForFraxUsdc(); 47 | 48 | USDC.transfer(address(pool), USDC_1); 49 | FRAX.transfer(address(pool), TOKEN_1); 50 | pool.mint(address(owner)); 51 | assertEq(pool.getAmountOut(USDC_1, address(USDC)), 945128557522723966); 52 | } 53 | 54 | function routerAddLiquidity() public { 55 | mintAndBurnTokensForPoolFraxUsdc(); 56 | 57 | USDC.approve(address(router), USDC_100K); 58 | FRAX.approve(address(router), TOKEN_100K); 59 | router.addLiquidity( 60 | address(FRAX), 61 | address(USDC), 62 | true, 63 | TOKEN_100K, 64 | USDC_100K, 65 | TOKEN_100K, 66 | USDC_100K, 67 | address(owner), 68 | block.timestamp 69 | ); 70 | USDC.approve(address(router), USDC_100K); 71 | FRAX.approve(address(router), TOKEN_100K); 72 | router.addLiquidity( 73 | address(FRAX), 74 | address(USDC), 75 | false, 76 | TOKEN_100K, 77 | USDC_100K, 78 | TOKEN_100K, 79 | USDC_100K, 80 | address(owner), 81 | block.timestamp 82 | ); 83 | DAI.approve(address(router), TOKEN_100M); 84 | FRAX.approve(address(router), TOKEN_100M); 85 | router.addLiquidity( 86 | address(FRAX), 87 | address(DAI), 88 | true, 89 | TOKEN_100M, 90 | TOKEN_100M, 91 | 0, 92 | 0, 93 | address(owner), 94 | block.timestamp 95 | ); 96 | } 97 | 98 | function routerPool1GetAmountsOutAndSwapExactTokensForTokens() public { 99 | routerAddLiquidity(); 100 | 101 | IRouter.Route[] memory routes = new IRouter.Route[](1); 102 | routes[0] = IRouter.Route(address(USDC), address(FRAX), true, address(0)); 103 | 104 | assertEq(router.getAmountsOut(USDC_1, routes)[1], pool.getAmountOut(USDC_1, address(USDC))); 105 | 106 | uint256[] memory asserted_output = router.getAmountsOut(USDC_1, routes); 107 | USDC.approve(address(router), USDC_1); 108 | router.swapExactTokensForTokens(USDC_1, asserted_output[1], routes, address(owner), block.timestamp); 109 | skip(1801); 110 | vm.roll(block.number + 1); 111 | USDC.approve(address(router), USDC_1); 112 | router.swapExactTokensForTokens(USDC_1, 0, routes, address(owner), block.timestamp); 113 | skip(1801); 114 | vm.roll(block.number + 1); 115 | USDC.approve(address(router), USDC_1); 116 | router.swapExactTokensForTokens(USDC_1, 0, routes, address(owner), block.timestamp); 117 | skip(1801); 118 | vm.roll(block.number + 1); 119 | USDC.approve(address(router), USDC_1); 120 | router.swapExactTokensForTokens(USDC_1, 0, routes, address(owner), block.timestamp); 121 | address poolFees = pool.poolFees(); 122 | assertEq(USDC.balanceOf(poolFees), 400); 123 | uint256 b = USDC.balanceOf(address(owner)); 124 | pool.claimFees(); 125 | assertGt(USDC.balanceOf(address(owner)), b); 126 | } 127 | 128 | function testOracle() public { 129 | routerPool1GetAmountsOutAndSwapExactTokensForTokens(); 130 | 131 | assertEq(pool.quote(address(USDC), 1e9, 1), 999999494004424240546); 132 | assertEq(pool.quote(address(FRAX), 1e21, 1), 999999506); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /test/PoolFactory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "./BaseTest.sol"; 5 | 6 | contract PoolFactoryTest is BaseTest { 7 | function testCannotSetFeeManagerIfNotFeeManager() public { 8 | vm.prank(address(owner2)); 9 | vm.expectRevert(IPoolFactory.NotFeeManager.selector); 10 | factory.setFeeManager(address(owner)); 11 | } 12 | 13 | function testCannotSetFeeManagerToZeroAddress() public { 14 | vm.prank(factory.feeManager()); 15 | vm.expectRevert(IPoolFactory.ZeroAddress.selector); 16 | factory.setFeeManager(address(0)); 17 | } 18 | 19 | function testFeeManagerCanSetFeeManager() public { 20 | factory.setFeeManager(address(owner2)); 21 | assertEq(factory.feeManager(), address(owner2)); 22 | } 23 | 24 | function testCannotSetPauserIfNotPauser() public { 25 | vm.prank(address(owner2)); 26 | vm.expectRevert(IPoolFactory.NotPauser.selector); 27 | factory.setPauser(address(owner2)); 28 | } 29 | 30 | function testCannotSetPauserToZeroAddress() public { 31 | vm.prank(factory.pauser()); 32 | vm.expectRevert(IPoolFactory.ZeroAddress.selector); 33 | factory.setPauser(address(0)); 34 | } 35 | 36 | function testPauserCanSetPauser() public { 37 | factory.setPauser(address(owner2)); 38 | assertEq(factory.pauser(), address(owner2)); 39 | } 40 | 41 | function testCannotPauseIfNotPauser() public { 42 | vm.prank(address(owner2)); 43 | vm.expectRevert(IPoolFactory.NotPauser.selector); 44 | factory.setPauseState(true); 45 | } 46 | 47 | function testPauserCanPause() public { 48 | assertEq(factory.isPaused(), false); 49 | factory.setPauseState(true); 50 | assertEq(factory.isPaused(), true); 51 | } 52 | 53 | function testCannotChangeFeesIfNotFeeManager() public { 54 | vm.prank(address(owner2)); 55 | vm.expectRevert(IPoolFactory.NotFeeManager.selector); 56 | factory.setFee(true, 2); 57 | 58 | vm.prank(address(owner2)); 59 | vm.expectRevert(IPoolFactory.NotFeeManager.selector); 60 | factory.setCustomFee(address(pool), 5); 61 | } 62 | 63 | function testCannotSetFeeAboveMax() public { 64 | vm.expectRevert(IPoolFactory.FeeTooHigh.selector); 65 | factory.setFee(true, 301); // 301 bps = 3.01% 66 | 67 | vm.expectRevert(IPoolFactory.FeeTooHigh.selector); 68 | factory.setCustomFee(address(pool), 301); // 301 bps = 3.01% 69 | } 70 | 71 | function testCannotSetZeroFee() public { 72 | vm.expectRevert(IPoolFactory.ZeroFee.selector); 73 | factory.setFee(true, 0); 74 | } 75 | 76 | function testFeeManagerCanSetMaxValues() public { 77 | // Can set to 420 to indicate 0% fee 78 | factory.setCustomFee(address(pool), 420); 79 | assertEq(factory.getFee(address(pool), true), 0); 80 | // Can set to 1% 81 | factory.setCustomFee(address(pool), 100); 82 | assertEq(factory.getFee(address(pool), true), 100); 83 | assertEq(factory.getFee(address(pool), false), 100); 84 | // does not impact regular fee of other pool 85 | assertEq(factory.getFee(address(pool2), true), 5); 86 | assertEq(factory.getFee(address(pool2), false), 30); 87 | 88 | factory.setFee(true, 100); 89 | assertEq(factory.getFee(address(pool2), true), 100); 90 | assertEq(factory.getFee(address(pool2), false), 30); 91 | factory.setFee(false, 100); 92 | assertEq(factory.getFee(address(pool2), false), 100); 93 | 94 | factory.setCustomFee(address(pool), 420); 95 | assertEq(factory.getFee(address(pool), true), 0); 96 | } 97 | 98 | function testSetCustomFee() external { 99 | // differentiate fees for stable / non-stable 100 | factory.setFee(true, 42); 101 | factory.setFee(false, 69); 102 | 103 | // pool does not have custom fees- return fee correlating to boolean 104 | assertEq(factory.getFee(address(pool), true), 42); 105 | assertEq(factory.getFee(address(pool), false), 69); 106 | 107 | factory.setCustomFee(address(pool), 11); 108 | assertEq(factory.getFee(address(pool), true), 11); 109 | assertEq(factory.getFee(address(pool), false), 11); 110 | 111 | // setting custom fee back to 0 gives default stable / non-stable fees 112 | factory.setCustomFee(address(pool), 0); 113 | assertEq(factory.getFee(address(pool), true), 42); 114 | assertEq(factory.getFee(address(pool), false), 69); 115 | 116 | // setting custom fee to 420 indicates there is 0% fee for the pool 117 | factory.setCustomFee(address(pool), 420); 118 | assertEq(factory.getFee(address(pool), true), 0); 119 | assertEq(factory.getFee(address(pool), false), 0); 120 | } 121 | 122 | function testCannotSetCustomFeeForNonExistentPool() external { 123 | vm.expectRevert(IPoolFactory.InvalidPool.selector); 124 | factory.setCustomFee(address(1), 5); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /test/PoolFees.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "./BaseTest.sol"; 5 | 6 | contract PoolFeesTest is BaseTest { 7 | function _setUp() public override { 8 | factory.setFee(true, 2); // 2 bps = 0.02% 9 | } 10 | 11 | function testSwapAndClaimFees() public { 12 | IRouter.Route[] memory routes = new IRouter.Route[](1); 13 | routes[0] = IRouter.Route(address(USDC), address(FRAX), true, address(0)); 14 | 15 | assertEq(router.getAmountsOut(USDC_1, routes)[1], pool.getAmountOut(USDC_1, address(USDC))); 16 | 17 | uint256[] memory assertedOutput = router.getAmountsOut(USDC_1, routes); 18 | USDC.approve(address(router), USDC_1); 19 | router.swapExactTokensForTokens(USDC_1, assertedOutput[1], routes, address(owner), block.timestamp); 20 | skip(1801); 21 | vm.roll(block.number + 1); 22 | address poolFees = pool.poolFees(); 23 | assertEq(USDC.balanceOf(poolFees), 200); // 0.01% -> 0.02% 24 | uint256 b = USDC.balanceOf(address(owner)); 25 | pool.claimFees(); 26 | assertGt(USDC.balanceOf(address(owner)), b); 27 | } 28 | 29 | function testFeeManagerCanChangeFeesAndClaim() public { 30 | factory.setFee(true, 3); // 3 bps = 0.03% 31 | 32 | IRouter.Route[] memory routes = new IRouter.Route[](1); 33 | routes[0] = IRouter.Route(address(USDC), address(FRAX), true, address(0)); 34 | 35 | assertEq(router.getAmountsOut(USDC_1, routes)[1], pool.getAmountOut(USDC_1, address(USDC))); 36 | 37 | uint256[] memory assertedOutput = router.getAmountsOut(USDC_1, routes); 38 | 39 | USDC.approve(address(router), USDC_1); 40 | router.swapExactTokensForTokens(USDC_1, assertedOutput[1], routes, address(owner), block.timestamp); 41 | 42 | skip(1801); 43 | vm.roll(block.number + 1); 44 | address poolFees = pool.poolFees(); 45 | assertEq(USDC.balanceOf(poolFees), 300); 46 | uint256 b = USDC.balanceOf(address(owner)); 47 | pool.claimFees(); 48 | assertGt(USDC.balanceOf(address(owner)), b); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/VeArtProxy.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "./BaseTest.sol"; 5 | 6 | contract VeArtProxyTest is BaseTest { 7 | uint256 tokenId; 8 | 9 | /** 10 | Assumptions 11 | * 1 <= seed <= 1000 12 | * 4 <= lineCount <= 32 (8 digits or 10M aero) - must be multiple of 4 13 | * viewbox is (0, 0) to (4000, 4000) 14 | */ 15 | 16 | function _setUp() public override { 17 | // create tokenId 18 | skipAndRoll(1 hours); 19 | AERO.approve(address(escrow), TOKEN_1); 20 | tokenId = escrow.createLock(TOKEN_1, MAXTIME); 21 | } 22 | 23 | function _setupFuzz( 24 | uint256 lineCount, 25 | uint256 seed1, 26 | uint256 seed2, 27 | uint256 seed3 28 | ) internal view returns (VeArtProxy.Config memory cfg) { 29 | lineCount = bound(lineCount, 4, 32); 30 | vm.assume(lineCount % 4 == 0); 31 | 32 | seed1 = bound(seed1, 1, 999); 33 | seed2 = bound(seed2, 1, 999); 34 | seed3 = bound(seed3, 1, 999); 35 | 36 | cfg.maxLines = int256(lineCount); 37 | cfg.seed1 = int256(seed1); 38 | cfg.seed2 = int256(seed2); 39 | cfg.seed3 = int256(seed3); 40 | } 41 | 42 | function testFuzz_drawTwoStripes(uint256 lineCount, uint256 seed1, uint256 seed2, uint256 seed3) external { 43 | bool lineAppears; 44 | VeArtProxy.Config memory cfg = _setupFuzz(lineCount, seed1, seed2, seed3); 45 | 46 | for (int256 l = 0; l < cfg.maxLines; l++) { 47 | VeArtProxy.Point[100] memory Line = artProxy.twoStripes(cfg, l); 48 | 49 | for (uint256 i = 0; i < 100; i++) { 50 | VeArtProxy.Point memory pt = Line[i]; 51 | if (pt.x < 4001 && pt.y < 4001) { 52 | lineAppears = true; 53 | break; 54 | } 55 | } 56 | if (lineAppears) break; 57 | } 58 | assertTrue(lineAppears); 59 | } 60 | 61 | function testFuzz_drawCircles(uint256 lineCount, uint256 seed1, uint256 seed2, uint256 seed3) external { 62 | bool lineAppears; 63 | VeArtProxy.Config memory cfg = _setupFuzz(lineCount, seed1, seed2, seed3); 64 | 65 | for (int256 l = 0; l < cfg.maxLines; l++) { 66 | VeArtProxy.Point[100] memory Line = artProxy.circles(cfg, l); 67 | 68 | for (uint256 i = 0; i < 100; i++) { 69 | VeArtProxy.Point memory pt = Line[i]; 70 | if (pt.x < 4001 && pt.y < 4001) { 71 | lineAppears = true; 72 | break; 73 | } 74 | } 75 | if (lineAppears) break; 76 | } 77 | assertTrue(lineAppears); 78 | } 79 | 80 | function testFuzz_drawInterlockingCircles(uint256 lineCount, uint256 seed1, uint256 seed2) external { 81 | bool lineAppears; 82 | VeArtProxy.Config memory cfg = _setupFuzz(lineCount, seed1, seed2, 1); 83 | 84 | for (int256 l = 0; l < cfg.maxLines; l++) { 85 | VeArtProxy.Point[100] memory Line = artProxy.interlockingCircles(cfg, l); 86 | 87 | for (uint256 i = 0; i < 100; i++) { 88 | VeArtProxy.Point memory pt = Line[i]; 89 | if (pt.x < 4001 && pt.y < 4001) { 90 | lineAppears = true; 91 | break; 92 | } 93 | } 94 | if (lineAppears) break; 95 | } 96 | assertTrue(lineAppears); 97 | } 98 | 99 | function testFuzz_drawCorners(uint256 lineCount, uint256 seed1) external { 100 | bool lineAppears; 101 | VeArtProxy.Config memory cfg = _setupFuzz(lineCount, seed1, 1, 1); 102 | 103 | for (int256 l = 0; l < cfg.maxLines; l++) { 104 | VeArtProxy.Point[100] memory Line = artProxy.corners(cfg, l); 105 | 106 | for (uint256 i = 0; i < 100; i++) { 107 | VeArtProxy.Point memory pt = Line[i]; 108 | if (pt.x < 4001 && pt.y < 4001) { 109 | lineAppears = true; 110 | break; 111 | } 112 | } 113 | if (lineAppears) break; 114 | } 115 | assertTrue(lineAppears); 116 | } 117 | 118 | function testFuzz_drawCurves(uint256 lineCount, uint256 seed1) external { 119 | bool lineAppears; 120 | VeArtProxy.Config memory cfg = _setupFuzz(lineCount, seed1, 1, 1); 121 | 122 | for (int256 l = 0; l < cfg.maxLines; l++) { 123 | VeArtProxy.Point[100] memory Line = artProxy.curves(cfg, l); 124 | 125 | for (uint256 i = 0; i < 100; i++) { 126 | VeArtProxy.Point memory pt = Line[i]; 127 | if (pt.x < 4001 && pt.y < 4001) { 128 | lineAppears = true; 129 | break; 130 | } 131 | } 132 | if (lineAppears) break; 133 | } 134 | assertTrue(lineAppears); 135 | } 136 | 137 | function testFuzz_drawSpiral(uint256 lineCount, uint256 seed1, uint256 seed2) external { 138 | bool lineAppears; 139 | VeArtProxy.Config memory cfg = _setupFuzz(lineCount, seed1, seed2, 1); 140 | 141 | for (int256 l = 0; l < cfg.maxLines; l++) { 142 | VeArtProxy.Point[100] memory Line = artProxy.spiral(cfg, l); 143 | 144 | for (uint256 i = 0; i < 100; i++) { 145 | VeArtProxy.Point memory pt = Line[i]; 146 | if (pt.x < 4001 && pt.y < 4001) { 147 | lineAppears = true; 148 | break; 149 | } 150 | } 151 | if (lineAppears) break; 152 | } 153 | assertTrue(lineAppears); 154 | } 155 | 156 | function testFuzz_drawExplosion(uint256 lineCount, uint256 seed1, uint256 seed2, uint256 seed3) external { 157 | bool lineAppears; 158 | VeArtProxy.Config memory cfg = _setupFuzz(lineCount, seed1, seed2, seed3); 159 | 160 | for (int256 l = 0; l < cfg.maxLines; l++) { 161 | VeArtProxy.Point[100] memory Line = artProxy.explosion(cfg, l); 162 | 163 | for (uint256 i = 0; i < 100; i++) { 164 | VeArtProxy.Point memory pt = Line[i]; 165 | if (pt.x < 4001 && pt.y < 4001) { 166 | lineAppears = true; 167 | break; 168 | } 169 | } 170 | if (lineAppears) break; 171 | } 172 | assertTrue(lineAppears); 173 | } 174 | 175 | function testFuzz_drawWormhole(uint256 lineCount, uint256 seed1, uint256 seed2) external { 176 | bool lineAppears; 177 | VeArtProxy.Config memory cfg = _setupFuzz(lineCount, seed1, seed2, 1); 178 | 179 | for (int256 l = 0; l < cfg.maxLines; l++) { 180 | VeArtProxy.Point[100] memory Line = artProxy.wormhole(cfg, l); 181 | 182 | for (uint256 i = 0; i < 100; i++) { 183 | VeArtProxy.Point memory pt = Line[i]; 184 | if (pt.x < 4001 && pt.y < 4001) { 185 | lineAppears = true; 186 | break; 187 | } 188 | } 189 | if (lineAppears) break; 190 | } 191 | assertTrue(lineAppears); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /test/e2e/ExtendedBaseTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import "../BaseTest.sol"; 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | /// @dev Contains helpful functions for end to end testing 8 | abstract contract ExtendedBaseTest is BaseTest { 9 | // precision used in calculating rewards 10 | // 1e12 relative precision implies acceptable error of 1e-6 * expected value 11 | // e.g. if we expect 1e18, precision of 1e12 means we will accept values of 12 | // 1e18 +- (1e6 * 1e12 / 1e18) 13 | uint256 public immutable PRECISION = 1e12; 14 | uint256 public immutable MAX_TIME = 4 * 365 * 86400; 15 | 16 | function _createBribeWithAmount(BribeVotingReward _bribeVotingReward, address _token, uint256 _amount) internal { 17 | IERC20(_token).approve(address(_bribeVotingReward), _amount); 18 | _bribeVotingReward.notifyRewardAmount(address(_token), _amount); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/utils/ERC2771Helper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "@opengsn/contracts/src/forwarder/Forwarder.sol"; 6 | 7 | // from https://github.com/corpus-io/tokenize.it-smart-contracts/blob/main/test/resources/ERC2771Helper.sol 8 | 9 | contract ERC2771Helper is Test { 10 | using ECDSA for bytes32; // for verify with var.recover() 11 | 12 | /** 13 | @notice register domain separator and return the domain separator 14 | @dev can only be used when testing with forge, as it uses cheatcodes. For some reason, the forwarder contracts do not return the domain separator, which is fixed here. 15 | */ 16 | function registerDomain( 17 | Forwarder forwarder, 18 | string calldata domainName, 19 | string calldata version 20 | ) public returns (bytes32) { 21 | // https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator 22 | // use chainId, address, name for proper implementation. 23 | // opengsn suggests different contents: https://docs.opengsn.org/soldoc/contracts/forwarder/iforwarder.html#registerdomainseparator-string-name-string-version 24 | vm.recordLogs(); 25 | forwarder.registerDomainSeparator(domainName, version); // simply uses address string as name 26 | 27 | Vm.Log[] memory logs = vm.getRecordedLogs(); 28 | // the next line extracts the domain separator from the event emitted by the forwarder 29 | bytes32 domainSeparator = logs[0].topics[1]; // internally, the forwarder calls this domainHash in registerDomainSeparator. But expects is as domainSeparator in execute(). 30 | require(forwarder.domains(domainSeparator), "Registering failed"); 31 | return domainSeparator; 32 | } 33 | 34 | /** 35 | @notice register request type, e.g. which function to call and which parameters to expect 36 | @dev return the request type 37 | */ 38 | function registerRequestType( 39 | Forwarder forwarder, 40 | string calldata functionName, 41 | string calldata functionParameters 42 | ) public returns (bytes32) { 43 | vm.recordLogs(); 44 | forwarder.registerRequestType(functionName, functionParameters); 45 | Vm.Log[] memory logs = vm.getRecordedLogs(); 46 | // the next line extracts the request type from the event emitted by the forwarder 47 | bytes32 requestType = logs[0].topics[1]; 48 | require(forwarder.typeHashes(requestType), "Registering failed"); 49 | return requestType; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/utils/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | /// @dev MockERC20 contract for testing use only 7 | contract MockERC20 is ERC20 { 8 | uint8 private _decimals; 9 | 10 | constructor(string memory name_, string memory symbol_, uint256 decimals_) ERC20(name_, symbol_) { 11 | _decimals = uint8(decimals_); 12 | } 13 | 14 | function decimals() public view virtual override returns (uint8) { 15 | return _decimals; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/utils/MockERC20WithTransferFee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | /// @dev MockERC20 contract where there is a fee in transfer for testing use only 7 | contract MockERC20WithTransferFee is ERC20 { 8 | uint8 private _decimals; 9 | uint256 public fee = 69; 10 | 11 | constructor(string memory name_, string memory symbol_, uint256 decimals_) ERC20(name_, symbol_) { 12 | _decimals = uint8(decimals_); 13 | } 14 | 15 | function decimals() public view virtual override returns (uint8) { 16 | return _decimals; 17 | } 18 | 19 | function _afterTokenTransfer(address from, address to, uint256 amount) internal override { 20 | // only do fees if not in a _mint or _burn 21 | if (from == address(0) || to == address(0)) return; 22 | 23 | if (amount > fee) { 24 | _burn(to, fee); 25 | } 26 | } 27 | 28 | function mint(address to, uint256 amount) external { 29 | _mint(to, amount); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/utils/MockWETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IWETH} from "contracts/interfaces/IWETH.sol"; 5 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | /// @dev Modified lightly from live deployed WETH contract as WETH9 is not a standard ERC20 8 | contract MockWETH is IWETH { 9 | string public name = "Wrapped Ether"; 10 | string public symbol = "WETH"; 11 | uint8 public decimals = 18; 12 | 13 | event Deposit(address indexed dst, uint256 wad); 14 | event Withdrawal(address indexed src, uint256 wad); 15 | 16 | mapping(address => uint256) public balanceOf; 17 | mapping(address => mapping(address => uint256)) public allowance; 18 | 19 | receive() external payable { 20 | deposit(); 21 | } 22 | 23 | function deposit() public payable { 24 | balanceOf[msg.sender] += msg.value; 25 | emit Deposit(msg.sender, msg.value); 26 | } 27 | 28 | function withdraw(uint256 wad) public { 29 | require(balanceOf[msg.sender] >= wad); 30 | balanceOf[msg.sender] -= wad; 31 | payable(msg.sender).transfer(wad); 32 | emit Withdrawal(msg.sender, wad); 33 | } 34 | 35 | function totalSupply() public view returns (uint256) { 36 | return address(this).balance; 37 | } 38 | 39 | function approve(address guy, uint256 wad) public returns (bool) { 40 | allowance[msg.sender][guy] = wad; 41 | return true; 42 | } 43 | 44 | function transfer(address dst, uint256 wad) public returns (bool) { 45 | return transferFrom(msg.sender, dst, wad); 46 | } 47 | 48 | function transferFrom(address src, address dst, uint256 wad) public returns (bool) { 49 | require(balanceOf[src] >= wad); 50 | 51 | if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { 52 | require(allowance[src][msg.sender] >= wad); 53 | allowance[src][msg.sender] -= wad; 54 | } 55 | 56 | balanceOf[src] -= wad; 57 | balanceOf[dst] += wad; 58 | 59 | return true; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/utils/SigUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | contract SigUtils { 5 | bytes32 internal DOMAIN_SEPARATOR; 6 | 7 | constructor(bytes32 _DOMAIN_SEPARATOR) { 8 | DOMAIN_SEPARATOR = _DOMAIN_SEPARATOR; 9 | } 10 | 11 | // keccak256("Delegation(uint256 delegator,uint256 delegatee,uint256 nonce,uint256 expiry)"); 12 | bytes32 public constant DELEGATION_TYPEHASH = 0x9947d5709c1682eaa3946b2d84115c9c0d1c946b149d76e69b457458b42ea29e; 13 | 14 | struct Delegation { 15 | uint256 delegator; 16 | uint256 delegatee; 17 | uint256 nonce; 18 | uint256 deadline; 19 | } 20 | 21 | /// @dev Computes the hash of a permit 22 | function getStructHash(Delegation memory _delegation) internal pure returns (bytes32) { 23 | return 24 | keccak256( 25 | abi.encode( 26 | DELEGATION_TYPEHASH, 27 | _delegation.delegator, 28 | _delegation.delegatee, 29 | _delegation.nonce, 30 | _delegation.deadline 31 | ) 32 | ); 33 | } 34 | 35 | /// @dev Computes the hash of the fully encoded EIP-712 message for the domain, 36 | /// which can be used to recover the signer 37 | function getTypedDataHash(Delegation memory _delegation) public view returns (bytes32) { 38 | return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, getStructHash(_delegation))); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/utils/TestOwner.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.19; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {Gauge} from "contracts/gauges/Gauge.sol"; 6 | 7 | contract TestOwner { 8 | /*////////////////////////////////////////////////////////////// 9 | IERC20 10 | //////////////////////////////////////////////////////////////*/ 11 | 12 | function approve(address _token, address _spender, uint256 _amount) public { 13 | IERC20(_token).approve(_spender, _amount); 14 | } 15 | 16 | function transfer(address _token, address _to, uint256 _amount) public { 17 | IERC20(_token).transfer(_to, _amount); 18 | } 19 | 20 | /*////////////////////////////////////////////////////////////// 21 | Gauge 22 | //////////////////////////////////////////////////////////////*/ 23 | 24 | function getGaugeReward(address _gauge, address _account) public { 25 | Gauge(_gauge).getReward(_account); 26 | } 27 | 28 | function deposit(address _gauge, uint256 _amount) public { 29 | Gauge(_gauge).deposit(_amount); 30 | } 31 | 32 | function withdrawGauge(address _gauge, uint256 _amount) public { 33 | Gauge(_gauge).withdraw(_amount); 34 | } 35 | 36 | receive() external payable {} 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "CommonJS", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "resolveJsonModule": true 9 | }, 10 | "files": [ 11 | "./hardhat.config.ts" 12 | ] 13 | } --------------------------------------------------------------------------------