├── .eslintignore ├── .eslintrc.yaml ├── .gitignore ├── .prettierignore ├── .prettierrc.yaml ├── .solhint.json ├── .solhintignore ├── README.md ├── contracts ├── Aura.sol ├── AuraBalRewardPool.sol ├── AuraClaimZap.sol ├── AuraLocker.sol ├── AuraMath.sol ├── AuraMerkleDrop.sol ├── AuraMinter.sol ├── AuraPenaltyForwarder.sol ├── AuraStakingProxy.sol ├── AuraVestedEscrow.sol ├── BalLiquidityProvider.sol ├── ClaimFeesHelper.sol ├── CrvDepositorWrapper.sol ├── ExtraRewardsDistributor.sol ├── Interfaces.sol └── mocks │ ├── MockAuraMath.sol │ ├── MockBalInvestor.sol │ ├── MockVoteStorage.sol │ ├── balancer │ ├── MockBalancerPool.sol │ ├── MockBalancerPoolToken.sol │ ├── MockBalancerVault.sol │ ├── MockFeeDistro.sol │ ├── MockInvestmentPoolFactory.sol │ ├── MockStablePoolFactory.sol │ └── MockWeightedPool2TokensFactory.sol │ └── curve │ ├── MockCurveGauge.sol │ ├── MockCurveMinter.sol │ ├── MockCurveVoteEscrow.sol │ ├── MockERC20.sol │ ├── MockVoting.sol │ └── MockWalletChecker.sol ├── convex-platform └── contracts │ ├── contracts │ ├── ArbitartorVault.sol │ ├── BaseRewardPool.sol │ ├── BaseRewardPool4626.sol │ ├── Booster.sol │ ├── BoosterOwner.sol │ ├── ConvexMasterChef.sol │ ├── CrvDepositor.sol │ ├── DepositToken.sol │ ├── ExtraRewardStashV3.sol │ ├── Interfaces.sol │ ├── PoolManagerProxy.sol │ ├── PoolManagerSecondaryProxy.sol │ ├── PoolManagerV3.sol │ ├── ProxyFactory.sol │ ├── RewardFactory.sol │ ├── RewardHook.sol │ ├── StashFactoryV2.sol │ ├── TokenFactory.sol │ ├── VirtualBalanceRewardPool.sol │ ├── VoterProxy.sol │ ├── cCrv.sol │ └── interfaces │ │ ├── BoringMath.sol │ │ ├── IERC20Metadata.sol │ │ ├── IERC4626.sol │ │ ├── IGaugeController.sol │ │ ├── IProxyFactory.sol │ │ ├── IRewardHook.sol │ │ ├── IRewarder.sol │ │ └── MathUtil.sol │ └── package.json ├── discord-export ├── Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html ├── Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files │ ├── 0-EB806.png │ ├── 0ab6fddedfdca4dee0b54ab41ab4faad-C5611.png │ ├── 1-B2132.png │ ├── 1f389-5C738.svg │ ├── 1f3c1-445DC.svg │ ├── 1f3c6-621A1.svg │ ├── 1f43a-EB486.svg │ ├── 1f440-6C64D.svg │ ├── 1f448-3DF32.svg │ ├── 1f449-25CFB.svg │ ├── 1f44b-8A059.svg │ ├── 1f44c-59547.svg │ ├── 1f44d-27259.svg │ ├── 1f48c-A96CC.svg │ ├── 1f49c-71A75.svg │ ├── 1f4aa-2FD27.svg │ ├── 1f4af-4CFF5.svg │ ├── 1f4b8-E3468.svg │ ├── 1f4c6-44E30.svg │ ├── 1f4dc-AC641.svg │ ├── 1f50d-195C0.svg │ ├── 1f512-24F85.svg │ ├── 1f604-BF863.svg │ ├── 1f60d-BEAFF.svg │ ├── 1f626-91074.svg │ ├── 1f642-83E8A.svg │ ├── 1f64c-7C820.svg │ ├── 1f64f-22B8D.svg │ ├── 1f680-A35CE.svg │ ├── 1f6a8-A8AB3.svg │ ├── 1f7e2-FC8EC.svg │ ├── 1f911-F346C.svg │ ├── 1f91e-2A114.svg │ ├── 1f972-F415D.svg │ ├── 1f9be-DDCD0.svg │ ├── 1f9d1-5BC80.svg │ ├── 1fac2-960B6.svg │ ├── 20c8931564dd11b412a4fecc64bf9451-26B4C.png │ ├── 23a7e58bdc28d6ad048874c84ad5fdb1-DE264.png │ ├── 2665-8CDCE.svg │ ├── 2696-15F4A.svg │ ├── 2a9faff195fe333526cfe6ae6fce1420-927E9.png │ ├── 423a533afdf07948f23af9a1ebceec37-235E9.png │ ├── 5-C387C │ ├── 5-E9BDB.png │ ├── 568d22b97293cdd2d9b7006198d6adbc-9738F.png │ ├── 5960c5502dd09d4875482231f43edbc5-F05FC.png │ ├── 67594ee4b4d1fc03bca468327a0d145b-0C614.png │ ├── 781415a7ef0ff20e2f8e85348fbd64c1-5F911.png │ ├── 7fcd80e4d3c60a600235b03c75f682a9-75033.png │ ├── 83ffc16bfc2260f3df5a5a3ce44e59ab2c5de5e3_2-4FEEE.jpeg │ ├── 851893157188599838-C23B5.png │ ├── 851893826846457866-1AD3D.png │ ├── 851893827089727568-5FD38.png │ ├── 851893827315826708-F59C0.png │ ├── 910676187288846397-518CD.png │ ├── 970d2e2f00cd7ef2134a1a3f21326349-B9EEF.png │ ├── C4-banner-7C19B.png │ ├── FSB_4THVcAAXrSe-D0ED9.jpg │ ├── Screenshot_20220523_113543_com.android.chr-1C166.jpg │ ├── TKrF4ZUY_400x400-5F957.jpg │ ├── a3543456a09e49cb364c91b15541774a-203F3.png │ ├── ae70f07a06c1c7e983291bb14a1bed7f-B8F4A.png │ ├── apple-touch-icon-192x192-E344C.png │ ├── b07292365785e0b7bd0dab7dd7d9e09f-62888.png │ ├── c9cb30134c634c9e02d0c64df4922803-1AAF2.png │ ├── eecc78c5-e4c8-4746-b464-3f4a2332c79b-1832D │ ├── fab1b54617157de0fdc01d6d30bc68b4-901C1.png │ ├── ggsans-italic-400-E988B.woff2 │ ├── ggsans-italic-500-0777F.woff2 │ ├── ggsans-italic-600-CB411.woff2 │ ├── ggsans-italic-700-891AC.woff2 │ ├── ggsans-italic-800-D36B0.woff2 │ ├── ggsans-normal-400-1456D.woff2 │ ├── ggsans-normal-500-89CE5.woff2 │ ├── ggsans-normal-600-C1EA8.woff2 │ ├── ggsans-normal-700-1949A.woff2 │ ├── ggsans-normal-800-58487.woff2 │ ├── highlight.min-D8D27.js │ ├── lottie.min-99657.js │ ├── solarized-dark.min-BA98F.css │ └── unknown-D6C62.png ├── Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt └── Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files │ ├── 5-C387C │ ├── 83ffc16bfc2260f3df5a5a3ce44e59ab2c5de5e3_2-4FEEE.jpeg │ ├── C4-banner-7C19B.png │ ├── FSB_4THVcAAXrSe-D0ED9.jpg │ ├── Screenshot_20220523_113543_com.android.chr-1C166.jpg │ ├── eecc78c5-e4c8-4746-b464-3f4a2332c79b-1832D │ └── unknown-D6C62.png ├── fork-test-output.txt ├── hardhat-cvx.config.ts ├── hardhat-fork.config.ts ├── hardhat.config.ts ├── index.ts ├── package.json ├── scripts ├── deployMocks.ts └── deploySystem.ts ├── tasks ├── deploy │ ├── index.ts │ └── mainnet-config.ts ├── index.ts └── utils │ ├── deploy-utils.ts │ ├── etherscan.ts │ ├── index.ts │ ├── networkAddressFactory.ts │ ├── signerFactory.ts │ └── tokens.ts ├── test-fork ├── BalEthSwap.spec.ts └── FullDeployment.spec.ts ├── test-output.txt ├── test-utils ├── assertions.ts ├── constants.ts ├── fork.ts ├── index.ts ├── math.ts ├── merkle.ts ├── regex.ts └── time.ts ├── test ├── Aura.spec.ts ├── AuraBalRewardPool.spec.ts ├── AuraClaimZap.spec.ts ├── AuraLocker.spec.ts ├── AuraLockerBalances.spec.ts ├── AuraMath.spec.ts ├── AuraMerkleDrop.spec.ts ├── AuraMinter.spec.ts ├── AuraPenaltyForwarder.spec.ts ├── AuraStakingProxy.spec.ts ├── AuraVestedEscrow.spec.ts ├── BalInvestor.spec.ts ├── BalLiquidityProvider.spec.ts ├── BaseRewardPool4626.spec.ts ├── Booster.spec.ts ├── ConvexMasterChef.spec.ts ├── CrvDepositor.spec.ts ├── ExtraRewardsDistributor.spec.ts ├── PoolManager.spec.ts ├── VoterProxy.spec.ts └── auraLockerBalanceData.json ├── tsconfig.json ├── types ├── common.ts └── index.ts └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | # directories 2 | .yarn/ 3 | **/.coverage_artifacts 4 | **/.coverage_cache 5 | **/.coverage_contracts 6 | **/artifacts 7 | **/build 8 | **/cache 9 | **/coverage 10 | **/dist 11 | **/node_modules 12 | **/types 13 | 14 | # files 15 | *.env 16 | *.log 17 | .pnp.* 18 | coverage.json 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | 24 | #convex 25 | **/convex-platform/**/* -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | extends: 2 | - "eslint:recommended" 3 | - "plugin:@typescript-eslint/eslint-recommended" 4 | - "plugin:@typescript-eslint/recommended" 5 | - "prettier" 6 | parser: "@typescript-eslint/parser" 7 | parserOptions: 8 | project: "tsconfig.json" 9 | plugins: 10 | - "@typescript-eslint" 11 | root: true 12 | rules: 13 | "@typescript-eslint/no-floating-promises": 14 | - error 15 | - ignoreIIFE: true 16 | ignoreVoid: true 17 | "@typescript-eslint/no-inferrable-types": "off" 18 | "@typescript-eslint/no-unused-vars": 19 | - error 20 | - argsIgnorePattern: "_" 21 | varsIgnorePattern: "_" 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_* 2 | # directories 3 | .yarn/* 4 | !.yarn/patches 5 | !.yarn/releases 6 | !.yarn/plugins 7 | !.yarn/sdks 8 | !.yarn/versions 9 | **/artifacts 10 | **/build 11 | **/cache 12 | /coverage 13 | **/.coverage_artifacts 14 | **/.coverage_cache 15 | **/.coverage_contracts 16 | **/dist 17 | **/node_modules 18 | 19 | # files 20 | *.env 21 | *.log 22 | .pnp.* 23 | coverage.json 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | contracts.json 28 | 29 | # types 30 | types/generated/**/* 31 | _flat 32 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # directories 2 | .yarn/ 3 | **/.coverage_artifacts 4 | **/.coverage_cache 5 | **/.coverage_contracts 6 | **/artifacts 7 | **/build 8 | **/cache 9 | **/coverage 10 | **/dist 11 | **/node_modules 12 | **/types 13 | 14 | # files 15 | *.env 16 | *.log 17 | .pnp.* 18 | coverage.json 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | #Convex files 24 | convex-platform/contracts 25 | convex-platform/README.md -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | arrowParens: avoid 2 | bracketSpacing: true 3 | endOfLine: auto 4 | printWidth: 120 5 | singleQuote: false 6 | tabWidth: 4 7 | trailingComma: all 8 | 9 | overrides: 10 | - files: "*.sol" 11 | options: 12 | tabWidth: 4 13 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "code-complexity": ["error", 8], 6 | "compiler-version": ["error", ">=0.8.4"], 7 | "const-name-snakecase": "off", 8 | "constructor-syntax": "error", 9 | "func-visibility": ["error", { "ignoreConstructors": true }], 10 | "max-line-length": ["error", 120], 11 | "not-rely-on-time": "off", 12 | "prettier/prettier": [ 13 | "error", 14 | { 15 | "endOfLine": "auto" 16 | } 17 | ], 18 | "reason-string": ["warn", { "maxLength": 64 }] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | # directories 2 | **/artifacts 3 | **/node_modules 4 | contracts/mocks 5 | -------------------------------------------------------------------------------- /contracts/Aura.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.11; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | import { ERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/ERC20.sol"; 6 | import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; 7 | import { Address } from "@openzeppelin/contracts-0.8/utils/Address.sol"; 8 | import { AuraMath } from "./AuraMath.sol"; 9 | 10 | interface IStaker { 11 | function operator() external view returns (address); 12 | } 13 | 14 | /** 15 | * @title AuraToken 16 | * @notice Basically an ERC20 with minting functionality operated by the "operator" of the VoterProxy (Booster). 17 | * @dev The minting schedule is based on the amount of CRV earned through staking and is 18 | * distirbuted along a supply curve (cliffs etc). Fork of ConvexToken. 19 | */ 20 | contract AuraToken is ERC20 { 21 | using SafeERC20 for IERC20; 22 | using Address for address; 23 | using AuraMath for uint256; 24 | 25 | address public operator; 26 | address public immutable vecrvProxy; 27 | 28 | uint256 public constant EMISSIONS_MAX_SUPPLY = 5e25; // 50m 29 | uint256 public constant totalCliffs = 500; 30 | uint256 public immutable reductionPerCliff; 31 | 32 | address public minter; 33 | uint256 private minterMinted = type(uint256).max; 34 | 35 | /* ========== EVENTS ========== */ 36 | 37 | event Initialised(); 38 | event OperatorChanged(address indexed previousOperator, address indexed newOperator); 39 | 40 | /** 41 | * @param _proxy CVX VoterProxy 42 | * @param _nameArg Token name 43 | * @param _symbolArg Token symbol 44 | */ 45 | constructor( 46 | address _proxy, 47 | string memory _nameArg, 48 | string memory _symbolArg 49 | ) ERC20(_nameArg, _symbolArg) { 50 | operator = msg.sender; 51 | vecrvProxy = _proxy; 52 | reductionPerCliff = EMISSIONS_MAX_SUPPLY.div(totalCliffs); 53 | } 54 | 55 | /** 56 | * @dev Initialise and mints initial supply of tokens. 57 | * @param _to Target address to mint. 58 | * @param _amount Amount of tokens to mint. 59 | * @param _minter The minter address. 60 | */ 61 | function init( 62 | address _to, 63 | uint256 _amount, 64 | address _minter 65 | ) external { 66 | require(msg.sender == operator, "Only operator"); 67 | require(totalSupply() == 0, "Only once"); 68 | require(_amount > 0, "Must mint something"); 69 | require(_minter != address(0), "Invalid minter"); 70 | 71 | _mint(_to, _amount); 72 | updateOperator(); 73 | minter = _minter; 74 | minterMinted = 0; 75 | 76 | emit Initialised(); 77 | } 78 | 79 | /** 80 | * @dev This can be called if the operator of the voterProxy somehow changes. 81 | */ 82 | function updateOperator() public { 83 | address newOperator = IStaker(vecrvProxy).operator(); 84 | emit OperatorChanged(operator, newOperator); 85 | operator = newOperator; 86 | } 87 | 88 | /** 89 | * @dev Mints AURA to a given user based on the BAL supply schedule. 90 | */ 91 | function mint(address _to, uint256 _amount) external { 92 | require(totalSupply() != 0, "Not initialised"); 93 | 94 | if (msg.sender != operator) { 95 | // dont error just return. if a shutdown happens, rewards on old system 96 | // can still be claimed, just wont mint cvx 97 | return; 98 | } 99 | 100 | // e.g. emissionsMinted = 6e25 - 5e25 - 0 = 1e25; 101 | uint256 emissionsMinted = totalSupply() - EMISSIONS_MAX_SUPPLY - minterMinted; 102 | // e.g. reductionPerCliff = 5e25 / 500 = 1e23 103 | // e.g. cliff = 1e25 / 1e23 = 100 104 | uint256 cliff = emissionsMinted.div(reductionPerCliff); 105 | 106 | // e.g. 100 < 500 107 | if (cliff < totalCliffs) { 108 | // e.g. (new) reduction = (500 - 100) * 2.5 + 700 = 1700; 109 | // e.g. (new) reduction = (500 - 250) * 2.5 + 700 = 1325; 110 | // e.g. (new) reduction = (500 - 400) * 2.5 + 700 = 950; 111 | uint256 reduction = totalCliffs.sub(cliff).mul(5).div(2).add(700); 112 | // e.g. (new) amount = 1e19 * 1700 / 500 = 34e18; 113 | // e.g. (new) amount = 1e19 * 1325 / 500 = 26.5e18; 114 | // e.g. (new) amount = 1e19 * 950 / 500 = 19e17; 115 | uint256 amount = _amount.mul(reduction).div(totalCliffs); 116 | // e.g. amtTillMax = 5e25 - 1e25 = 4e25 117 | uint256 amtTillMax = EMISSIONS_MAX_SUPPLY.sub(emissionsMinted); 118 | if (amount > amtTillMax) { 119 | amount = amtTillMax; 120 | } 121 | _mint(_to, amount); 122 | } 123 | } 124 | 125 | /** 126 | * @dev Allows minter to mint to a specific address 127 | */ 128 | function minterMint(address _to, uint256 _amount) external { 129 | require(msg.sender == minter, "Only minter"); 130 | minterMinted += _amount; 131 | _mint(_to, _amount); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /contracts/AuraMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.11; 3 | 4 | /// @notice A library for performing overflow-/underflow-safe math, 5 | /// updated with awesomeness from of DappHub (https://github.com/dapphub/ds-math). 6 | library AuraMath { 7 | /** 8 | * @dev Returns the smallest of two numbers. 9 | */ 10 | function min(uint256 a, uint256 b) internal pure returns (uint256) { 11 | return a < b ? a : b; 12 | } 13 | 14 | function add(uint256 a, uint256 b) internal pure returns (uint256 c) { 15 | c = a + b; 16 | } 17 | 18 | function sub(uint256 a, uint256 b) internal pure returns (uint256 c) { 19 | c = a - b; 20 | } 21 | 22 | function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { 23 | c = a * b; 24 | } 25 | 26 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 27 | return a / b; 28 | } 29 | 30 | /** 31 | * @dev Returns the average of two numbers. The result is rounded towards 32 | * zero. 33 | */ 34 | function average(uint256 a, uint256 b) internal pure returns (uint256) { 35 | // (a + b) / 2 can overflow, so we distribute. 36 | return (a / 2) + (b / 2) + (((a % 2) + (b % 2)) / 2); 37 | } 38 | 39 | function to224(uint256 a) internal pure returns (uint224 c) { 40 | require(a <= type(uint224).max, "AuraMath: uint224 Overflow"); 41 | c = uint224(a); 42 | } 43 | 44 | function to128(uint256 a) internal pure returns (uint128 c) { 45 | require(a <= type(uint128).max, "AuraMath: uint128 Overflow"); 46 | c = uint128(a); 47 | } 48 | 49 | function to112(uint256 a) internal pure returns (uint112 c) { 50 | require(a <= type(uint112).max, "AuraMath: uint112 Overflow"); 51 | c = uint112(a); 52 | } 53 | 54 | function to96(uint256 a) internal pure returns (uint96 c) { 55 | require(a <= type(uint96).max, "AuraMath: uint96 Overflow"); 56 | c = uint96(a); 57 | } 58 | 59 | function to32(uint256 a) internal pure returns (uint32 c) { 60 | require(a <= type(uint32).max, "AuraMath: uint32 Overflow"); 61 | c = uint32(a); 62 | } 63 | } 64 | 65 | /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint32. 66 | library AuraMath32 { 67 | function sub(uint32 a, uint32 b) internal pure returns (uint32 c) { 68 | c = a - b; 69 | } 70 | } 71 | 72 | /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint112. 73 | library AuraMath112 { 74 | function add(uint112 a, uint112 b) internal pure returns (uint112 c) { 75 | c = a + b; 76 | } 77 | 78 | function sub(uint112 a, uint112 b) internal pure returns (uint112 c) { 79 | c = a - b; 80 | } 81 | } 82 | 83 | /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint224. 84 | library AuraMath224 { 85 | function add(uint224 a, uint224 b) internal pure returns (uint224 c) { 86 | c = a + b; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /contracts/AuraMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.11; 3 | 4 | import { Ownable } from "@openzeppelin/contracts-0.8/access/Ownable.sol"; 5 | import { AuraToken } from "./Aura.sol"; 6 | 7 | /** 8 | * @title AuraMinter 9 | * @notice Wraps the AuraToken minterMint function and protects from inflation until 10 | * 4 years have passed. 11 | * @dev Ownership initially owned by the DAO, but likely transferred to smart contract 12 | * wrapper or additional value system at some stage as directed by token holders. 13 | */ 14 | contract AuraMinter is Ownable { 15 | /// @dev Aura token 16 | AuraToken public immutable aura; 17 | /// @dev Timestamp upon which minting will be possible 18 | uint256 public immutable inflationProtectionTime; 19 | 20 | constructor(address _aura, address _dao) Ownable() { 21 | aura = AuraToken(_aura); 22 | _transferOwnership(_dao); 23 | inflationProtectionTime = block.timestamp + 156 weeks; 24 | } 25 | 26 | /** 27 | * @dev Mint function allows the owner of the contract to inflate AURA post protection time 28 | * @param _to Recipient address 29 | * @param _amount Amount of tokens 30 | */ 31 | function mint(address _to, uint256 _amount) external onlyOwner { 32 | require(block.timestamp > inflationProtectionTime, "Inflation protected for now"); 33 | aura.minterMint(_to, _amount); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/AuraPenaltyForwarder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.11; 3 | 4 | import { IExtraRewardsDistributor } from "./Interfaces.sol"; 5 | import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 6 | import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | /** 9 | * @title AuraPenaltyForwarder 10 | * @dev Receives a given token and forwards it on to a distribution contract. Used during 11 | * the bootstrapping period to forward AURA rewards to the extra rewards distributor. 12 | */ 13 | contract AuraPenaltyForwarder { 14 | using SafeERC20 for IERC20; 15 | 16 | IExtraRewardsDistributor public immutable distributor; 17 | IERC20 public immutable token; 18 | 19 | uint256 public immutable distributionDelay; 20 | uint256 public lastDistribution; 21 | 22 | event Forwarded(uint256 amount); 23 | 24 | /** 25 | * @dev During deployment approves the distributor to spend all tokens 26 | * @param _distributor Contract that will distribute tokens 27 | * @param _token Token to be distributed 28 | * @param _delay Delay between each distribution trigger 29 | */ 30 | constructor( 31 | address _distributor, 32 | address _token, 33 | uint256 _delay 34 | ) { 35 | distributor = IExtraRewardsDistributor(_distributor); 36 | token = IERC20(_token); 37 | distributionDelay = _delay; 38 | 39 | lastDistribution = block.timestamp; 40 | 41 | token.safeApprove(address(distributor), type(uint256).max); 42 | } 43 | 44 | /** 45 | * @dev Forwards the complete balance of token in this contract to the distributor 46 | */ 47 | function forward() public { 48 | require(block.timestamp > lastDistribution + distributionDelay, "!elapsed"); 49 | lastDistribution = block.timestamp; 50 | 51 | uint256 bal = token.balanceOf(address(this)); 52 | require(bal > 0, "!empty"); 53 | 54 | distributor.addReward(address(token), bal); 55 | 56 | emit Forwarded(bal); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/BalLiquidityProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.11; 3 | 4 | import { IVault } from "./Interfaces.sol"; 5 | import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 6 | import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | /** 9 | * @title BalLiquidityProvider 10 | * @notice Provides initial liquidity to a Balancer pool on behalf of a given DAO 11 | */ 12 | contract BalLiquidityProvider { 13 | using SafeERC20 for IERC20; 14 | 15 | IERC20 public immutable startToken; 16 | IERC20 public immutable pairToken; 17 | uint256 public minPairAmount; 18 | 19 | address private immutable provider; 20 | address public immutable dao; 21 | 22 | IVault public immutable bVault; 23 | 24 | event LiquidityProvided(uint256[] input, uint256 output); 25 | event MinPairAmountChanged(uint256 oldMinPairAmount, uint256 newMinPairAmount); 26 | 27 | constructor( 28 | address _startToken, 29 | address _pairToken, 30 | uint256 _minPairAmount, 31 | address _dao, 32 | address _bVault 33 | ) { 34 | startToken = IERC20(_startToken); 35 | pairToken = IERC20(_pairToken); 36 | minPairAmount = _minPairAmount; 37 | provider = msg.sender; 38 | dao = _dao; 39 | bVault = IVault(_bVault); 40 | } 41 | 42 | /** 43 | * @dev Provides liquidity on behalf of the dao, in a non-custodial manner. 44 | * Has protections in place to ensure that no erroneous liquidity data gets added. 45 | */ 46 | function provideLiquidity(bytes32 _poolId, IVault.JoinPoolRequest memory _request) public { 47 | require(msg.sender == provider, "!auth"); 48 | require(_request.assets.length == 2 && _request.maxAmountsIn.length == 2, "!valid"); 49 | require(pairToken.balanceOf(address(this)) > minPairAmount, "!minLiq"); 50 | 51 | for (uint256 i = 0; i < 2; i++) { 52 | address asset = address(_request.assets[i]); 53 | require(asset == address(startToken) || asset == address(pairToken), "!asset"); 54 | 55 | IERC20 tkn = IERC20(asset); 56 | uint256 bal = tkn.balanceOf(address(this)); 57 | require(bal > 0 && bal == _request.maxAmountsIn[i], "!bal"); 58 | 59 | tkn.safeApprove(address(bVault), 0); 60 | tkn.safeApprove(address(bVault), bal); 61 | } 62 | 63 | (address pool, ) = bVault.getPool(_poolId); 64 | uint256 supplyBefore = IERC20(pool).totalSupply(); 65 | require(supplyBefore == 0, "!init"); 66 | 67 | bVault.joinPool(_poolId, address(this), dao, _request); 68 | 69 | uint256 balAfter = IERC20(pool).balanceOf(dao); 70 | require(balAfter > 0, "!mint"); 71 | 72 | emit LiquidityProvided(_request.maxAmountsIn, balAfter); 73 | } 74 | 75 | /** 76 | * @dev Allows the DAO to change the minimum amount of the pair token that must be added as liquidity 77 | */ 78 | function changeMinPairAmount(uint256 _newAmount) external { 79 | require(msg.sender == dao, "!auth"); 80 | emit MinPairAmountChanged(minPairAmount, _newAmount); 81 | minPairAmount = _newAmount; 82 | } 83 | 84 | /** 85 | * @dev Rescues a given token from the contract. 86 | * Only provider or DAO can call this function. 87 | */ 88 | function rescueToken(address _erc20) external { 89 | require(msg.sender == provider || msg.sender == dao, "!auth"); 90 | IERC20 tkn = IERC20(_erc20); 91 | tkn.safeTransfer(dao, tkn.balanceOf(address(this))); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /contracts/ClaimFeesHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.11; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | import { IFeeDistributor } from "./mocks/balancer/MockFeeDistro.sol"; 6 | 7 | interface IBooster { 8 | function earmarkFees(address _feeDistro) external returns (bool); 9 | } 10 | 11 | /** 12 | * @title ClaimFeesHelper 13 | * @author ConvexFinance 14 | * @notice Claim vecrv fees and distribute 15 | * @dev Allows anyone to call `claimFees` that will basically collect any 3crv and distribute to cvxCrv 16 | * via the booster. 17 | */ 18 | contract ClaimFeesHelper { 19 | IBooster public immutable booster; 20 | address public immutable voterProxy; 21 | 22 | mapping(address => uint256) public lastTokenTimes; 23 | IFeeDistributor public feeDistro; 24 | 25 | /** 26 | * @param _booster Booster.sol, e.g. 0xF403C135812408BFbE8713b5A23a04b3D48AAE31 27 | * @param _voterProxy CVX VoterProxy e.g. 0x989AEb4d175e16225E39E87d0D97A3360524AD80 28 | * @param _feeDistro FeeDistro e.g. 0xA464e6DCda8AC41e03616F95f4BC98a13b8922Dc 29 | */ 30 | constructor( 31 | address _booster, 32 | address _voterProxy, 33 | address _feeDistro 34 | ) { 35 | booster = IBooster(_booster); 36 | voterProxy = _voterProxy; 37 | feeDistro = IFeeDistributor(_feeDistro); 38 | } 39 | 40 | /** 41 | * @dev Claims fees from fee claimer, and pings the booster to distribute 42 | */ 43 | function claimFees(IERC20 _token) external { 44 | uint256 tokenTime = feeDistro.getTokenTimeCursor(_token); 45 | require(tokenTime > lastTokenTimes[address(_token)], "not time yet"); 46 | 47 | uint256 bal = IERC20(_token).balanceOf(voterProxy); 48 | feeDistro.claimToken(voterProxy, _token); 49 | 50 | // Loop through until something is transferred 51 | while (IERC20(_token).balanceOf(voterProxy) <= bal) { 52 | feeDistro.claimToken(voterProxy, _token); 53 | } 54 | 55 | booster.earmarkFees(address(_token)); 56 | lastTokenTimes[address(_token)] = tokenTime; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/CrvDepositorWrapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; 6 | import { IVault, IPriceOracle, ICrvDepositorWrapper, IAsset } from "./Interfaces.sol"; 7 | import { IVault } from "./Interfaces.sol"; 8 | 9 | interface ICrvDepositor { 10 | function depositFor( 11 | address to, 12 | uint256 _amount, 13 | bool _lock, 14 | address _stakeAddress 15 | ) external; 16 | } 17 | 18 | /** 19 | * @title BalInvestor 20 | * @notice Deposits $BAL into a BAL/WETH BPT. Hooks into TWAP to determine minOut. 21 | * @dev Abstract contract for depositing BAL -> balBPT -> auraBAL via crvDepositor 22 | */ 23 | abstract contract BalInvestor { 24 | using SafeERC20 for IERC20; 25 | 26 | IVault public immutable BALANCER_VAULT; 27 | address public immutable BAL; 28 | address public immutable WETH; 29 | address public immutable BALANCER_POOL_TOKEN; 30 | bytes32 public immutable BAL_ETH_POOL_ID; 31 | 32 | constructor( 33 | IVault _balancerVault, 34 | address _bal, 35 | address _weth, 36 | bytes32 _balETHPoolId 37 | ) { 38 | ( 39 | address poolAddress, /* */ 40 | 41 | ) = _balancerVault.getPool(_balETHPoolId); 42 | require(poolAddress != address(0), "!poolAddress"); 43 | 44 | BALANCER_VAULT = _balancerVault; 45 | BAL = _bal; 46 | WETH = _weth; 47 | BALANCER_POOL_TOKEN = poolAddress; 48 | BAL_ETH_POOL_ID = _balETHPoolId; 49 | } 50 | 51 | function _setApprovals() internal { 52 | IERC20(WETH).safeApprove(address(BALANCER_VAULT), type(uint256).max); 53 | IERC20(BAL).safeApprove(address(BALANCER_VAULT), type(uint256).max); 54 | } 55 | 56 | function _getBptPrice() internal view returns (uint256) { 57 | IPriceOracle.OracleAverageQuery[] memory queries = new IPriceOracle.OracleAverageQuery[](1); 58 | 59 | queries[0].variable = IPriceOracle.Variable.BPT_PRICE; 60 | queries[0].secs = 3600; // last hour 61 | queries[0].ago = 0; // now 62 | 63 | // Gets the balancer time weighted average price denominated in BAL 64 | return IPriceOracle(BALANCER_POOL_TOKEN).getTimeWeightedAverage(queries)[0]; 65 | } 66 | 67 | function _getMinOut(uint256 amount, uint256 minOutBps) internal view returns (uint256) { 68 | // Gets the balancer time weighted average price denominated in BAL 69 | // e.g. if 1 BAL == 0.4 BPT, bptOraclePrice == 2.5 70 | uint256 bptOraclePrice = _getBptPrice(); 71 | // e.g. minOut = (((100e18 * 1e18) / 2.5e18) * 9980) / 10000; 72 | // e.g. minout = 39.92e18 73 | uint256 minOut = (((amount * 1e18) / bptOraclePrice) * minOutBps) / 10000; 74 | return minOut; 75 | } 76 | 77 | function _investBalToPool(uint256 amount, uint256 minOut) internal { 78 | IERC20(BAL).safeTransferFrom(msg.sender, address(this), amount); 79 | IAsset[] memory assets = new IAsset[](2); 80 | assets[0] = IAsset(BAL); 81 | assets[1] = IAsset(WETH); 82 | uint256[] memory maxAmountsIn = new uint256[](2); 83 | maxAmountsIn[0] = amount; 84 | maxAmountsIn[1] = 0; 85 | 86 | BALANCER_VAULT.joinPool( 87 | BAL_ETH_POOL_ID, 88 | address(this), 89 | address(this), 90 | IVault.JoinPoolRequest( 91 | assets, 92 | maxAmountsIn, 93 | abi.encode(IVault.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, maxAmountsIn, minOut), 94 | false // Don't use internal balances 95 | ) 96 | ); 97 | } 98 | } 99 | 100 | /** 101 | * @title CrvDepositorWrapper 102 | * @notice Converts BAL -> balBPT and then wraps to auraBAL via the crvDepositor 103 | */ 104 | contract CrvDepositorWrapper is ICrvDepositorWrapper, BalInvestor { 105 | address public immutable crvDeposit; 106 | 107 | constructor( 108 | address _crvDeposit, 109 | IVault _balancerVault, 110 | address _bal, 111 | address _weth, 112 | bytes32 _balETHPoolId 113 | ) BalInvestor(_balancerVault, _bal, _weth, _balETHPoolId) { 114 | crvDeposit = _crvDeposit; 115 | } 116 | 117 | function setApprovals() external { 118 | _setApprovals(); 119 | require(IERC20(BALANCER_POOL_TOKEN).approve(crvDeposit, type(uint256).max), "!approval"); 120 | } 121 | 122 | /** 123 | * @dev Gets minimum output based on BPT oracle price 124 | * @param _amount Units of BAL to deposit 125 | * @param _outputBps Multiplier where 100% == 10000, 99.5% == 9950 and 98% == 9800 126 | * @return minOut Units of BPT to expect as output 127 | */ 128 | function getMinOut(uint256 _amount, uint256 _outputBps) external view returns (uint256) { 129 | return _getMinOut(_amount, _outputBps); 130 | } 131 | 132 | function deposit( 133 | uint256 _amount, 134 | uint256 _minOut, 135 | bool _lock, 136 | address _stakeAddress 137 | ) external { 138 | _investBalToPool(_amount, _minOut); 139 | uint256 bptBalance = IERC20(BALANCER_POOL_TOKEN).balanceOf(address(this)); 140 | ICrvDepositor(crvDeposit).depositFor(msg.sender, bptBalance, _lock, _stakeAddress); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /contracts/Interfaces.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | pragma abicoder v2; 4 | 5 | interface IPriceOracle { 6 | struct OracleAverageQuery { 7 | Variable variable; 8 | uint256 secs; 9 | uint256 ago; 10 | } 11 | enum Variable { 12 | PAIR_PRICE, 13 | BPT_PRICE, 14 | INVARIANT 15 | } 16 | 17 | function getTimeWeightedAverage(OracleAverageQuery[] memory queries) 18 | external 19 | view 20 | returns (uint256[] memory results); 21 | } 22 | 23 | interface IVault { 24 | enum PoolSpecialization { 25 | GENERAL, 26 | MINIMAL_SWAP_INFO, 27 | TWO_TOKEN 28 | } 29 | enum JoinKind { 30 | INIT, 31 | EXACT_TOKENS_IN_FOR_BPT_OUT, 32 | TOKEN_IN_FOR_EXACT_BPT_OUT, 33 | ALL_TOKENS_IN_FOR_EXACT_BPT_OUT 34 | } 35 | 36 | enum SwapKind { 37 | GIVEN_IN, 38 | GIVEN_OUT 39 | } 40 | 41 | struct SingleSwap { 42 | bytes32 poolId; 43 | SwapKind kind; 44 | IAsset assetIn; 45 | IAsset assetOut; 46 | uint256 amount; 47 | bytes userData; 48 | } 49 | 50 | struct FundManagement { 51 | address sender; 52 | bool fromInternalBalance; 53 | address payable recipient; 54 | bool toInternalBalance; 55 | } 56 | 57 | struct JoinPoolRequest { 58 | IAsset[] assets; 59 | uint256[] maxAmountsIn; 60 | bytes userData; 61 | bool fromInternalBalance; 62 | } 63 | 64 | function getPool(bytes32 poolId) external view returns (address, PoolSpecialization); 65 | 66 | function getPoolTokens(bytes32 poolId) 67 | external 68 | view 69 | returns ( 70 | address[] memory tokens, 71 | uint256[] memory balances, 72 | uint256 lastChangeBlock 73 | ); 74 | 75 | function joinPool( 76 | bytes32 poolId, 77 | address sender, 78 | address recipient, 79 | JoinPoolRequest memory request 80 | ) external payable; 81 | 82 | function swap( 83 | SingleSwap memory singleSwap, 84 | FundManagement memory funds, 85 | uint256 limit, 86 | uint256 deadline 87 | ) external returns (uint256 amountCalculated); 88 | 89 | function exitPool( 90 | bytes32 poolId, 91 | address sender, 92 | address payable recipient, 93 | ExitPoolRequest memory request 94 | ) external; 95 | 96 | struct ExitPoolRequest { 97 | IAsset[] assets; 98 | uint256[] minAmountsOut; 99 | bytes userData; 100 | bool toInternalBalance; 101 | } 102 | enum ExitKind { 103 | EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, 104 | EXACT_BPT_IN_FOR_TOKENS_OUT, 105 | BPT_IN_FOR_EXACT_TOKENS_OUT, 106 | MANAGEMENT_FEE_TOKENS_OUT // for ManagedPool 107 | } 108 | } 109 | 110 | interface IAsset { 111 | // solhint-disable-previous-line no-empty-blocks 112 | } 113 | 114 | interface IAuraLocker { 115 | function lock(address _account, uint256 _amount) external; 116 | 117 | function checkpointEpoch() external; 118 | 119 | function epochCount() external view returns (uint256); 120 | 121 | function balanceAtEpochOf(uint256 _epoch, address _user) external view returns (uint256 amount); 122 | 123 | function totalSupplyAtEpoch(uint256 _epoch) external view returns (uint256 supply); 124 | 125 | function queueNewRewards(uint256 _rewards) external; 126 | 127 | function notifyRewardAmount(address _rewardsToken, uint256 reward) external; 128 | 129 | function getReward(address _account, bool _stake) external; 130 | 131 | function getReward(address _account) external; 132 | } 133 | 134 | interface IExtraRewardsDistributor { 135 | function addReward(address _token, uint256 _amount) external; 136 | } 137 | 138 | interface ICrvDepositorWrapper { 139 | function getMinOut(uint256, uint256) external view returns (uint256); 140 | 141 | function deposit( 142 | uint256, 143 | uint256, 144 | bool, 145 | address _stakeAddress 146 | ) external; 147 | } 148 | -------------------------------------------------------------------------------- /contracts/mocks/MockAuraMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.11; 3 | 4 | import { AuraMath, AuraMath32, AuraMath112, AuraMath224 } from "../AuraMath.sol"; 5 | 6 | // solhint-disable func-name-mixedcase 7 | contract MockAuraMath { 8 | constructor() {} 9 | 10 | function AuraMath_min(uint256 a, uint256 b) external pure returns (uint256) { 11 | return AuraMath.min(a, b); 12 | } 13 | 14 | function AuraMath_add(uint256 a, uint256 b) external pure returns (uint256) { 15 | return AuraMath.add(a, b); 16 | } 17 | 18 | function AuraMath_sub(uint256 a, uint256 b) external pure returns (uint256) { 19 | return AuraMath.sub(a, b); 20 | } 21 | 22 | function AuraMath_mul(uint256 a, uint256 b) external pure returns (uint256) { 23 | return AuraMath.mul(a, b); 24 | } 25 | 26 | function AuraMath_div(uint256 a, uint256 b) external pure returns (uint256) { 27 | return AuraMath.div(a, b); 28 | } 29 | 30 | function AuraMath_average(uint256 a, uint256 b) external pure returns (uint256) { 31 | return AuraMath.average(a, b); 32 | } 33 | 34 | function AuraMath_to224(uint256 a) external pure returns (uint224) { 35 | return AuraMath.to224(a); 36 | } 37 | 38 | function AuraMath_to128(uint256 a) external pure returns (uint128) { 39 | return AuraMath.to128(a); 40 | } 41 | 42 | function AuraMath_to112(uint256 a) external pure returns (uint112) { 43 | return AuraMath.to112(a); 44 | } 45 | 46 | function AuraMath_to96(uint256 a) external pure returns (uint96) { 47 | return AuraMath.to96(a); 48 | } 49 | 50 | function AuraMath_to32(uint256 a) external pure returns (uint32) { 51 | return AuraMath.to32(a); 52 | } 53 | 54 | function AuraMath32_sub(uint32 a, uint32 b) external pure returns (uint32) { 55 | return AuraMath32.sub(a, b); 56 | } 57 | 58 | function AuraMath112_add(uint112 a, uint112 b) external pure returns (uint112) { 59 | return AuraMath112.add(a, b); 60 | } 61 | 62 | function AuraMath112_sub(uint112 a, uint112 b) external pure returns (uint112) { 63 | return AuraMath112.sub(a, b); 64 | } 65 | 66 | function AuraMath224_add(uint224 a, uint224 b) external pure returns (uint224) { 67 | return AuraMath224.add(a, b); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contracts/mocks/MockBalInvestor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | import "../CrvDepositorWrapper.sol"; 6 | 7 | contract MockBalInvestor is BalInvestor { 8 | constructor( 9 | IVault _balancerVault, 10 | address _bal, 11 | address _weth, 12 | bytes32 _balETHPoolId 13 | ) BalInvestor(_balancerVault, _bal, _weth, _balETHPoolId) {} 14 | 15 | function approveToken() external { 16 | _setApprovals(); 17 | } 18 | 19 | function getBptPrice() external view returns (uint256) { 20 | return _getBptPrice(); 21 | } 22 | 23 | function getMinOut(uint256 _amount, uint256 _outputBps) public view returns (uint256) { 24 | return _getMinOut(_amount, _outputBps); 25 | } 26 | 27 | function addBalToPool(uint256 amount, uint256 _minOut) external { 28 | _investBalToPool(amount, _minOut); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/mocks/MockVoteStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | contract MockVoteStorage { 5 | struct Vote { 6 | uint256 timestamp; 7 | uint256 choice; 8 | string version; 9 | string space; 10 | string voteType; 11 | } 12 | 13 | mapping(string => Vote) public proposals; 14 | 15 | function setProposal( 16 | uint256 choice, 17 | uint256 timestamp, 18 | string memory version, 19 | string memory proposal, 20 | string memory space, 21 | string memory voteType 22 | ) external { 23 | Vote memory vote = Vote(timestamp, choice, version, space, voteType); 24 | proposals[proposal] = vote; 25 | } 26 | 27 | function hash(string memory proposal) public view returns (bytes32) { 28 | Vote memory vote = proposals[proposal]; 29 | 30 | // prettier-ignore 31 | return hashStr(string(abi.encodePacked( 32 | "{", 33 | '"version":"', vote.version, '",', 34 | '"timestamp":"', uint2str(vote.timestamp), '",', 35 | '"space":"', vote.space, '",', 36 | '"type":"', vote.voteType, '",', 37 | payloadStr(proposal, vote.choice), 38 | "}" 39 | ))); 40 | } 41 | 42 | function payloadStr(string memory proposal, uint256 choice) internal pure returns (string memory) { 43 | // prettier-ignore 44 | return string(abi.encodePacked( 45 | '"payload":', "{", 46 | '"proposal":', '"', proposal, '",', 47 | '"choice":', uint2str(choice), "," 48 | '"metadata":', '"{}"', 49 | "}" 50 | )); 51 | } 52 | 53 | function hashStr(string memory str) internal pure returns (bytes32) { 54 | return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", uint2str(bytes(str).length), str)); 55 | } 56 | 57 | function uint2str(uint256 _i) internal pure returns (string memory _uintAsString) { 58 | if (_i == 0) { 59 | return "0"; 60 | } 61 | uint256 j = _i; 62 | uint256 len; 63 | while (j != 0) { 64 | len++; 65 | j /= 10; 66 | } 67 | bytes memory bstr = new bytes(len); 68 | uint256 k = len; 69 | while (_i != 0) { 70 | k = k - 1; 71 | uint8 temp = (48 + uint8(_i - (_i / 10) * 10)); 72 | bytes1 b1 = bytes1(temp); 73 | bstr[k] = b1; 74 | _i /= 10; 75 | } 76 | return string(bstr); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/mocks/balancer/MockBalancerPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | 6 | interface IBalancerPool { 7 | function getPoolId() external view returns (bytes32); 8 | 9 | function getNormalizedWeights() external view returns (uint256[] memory); 10 | 11 | function getSwapEnabled() external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/mocks/balancer/MockBalancerPoolToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts-0.8/token/ERC20/ERC20.sol"; 5 | 6 | contract MockBalancerPoolToken is ERC20("MockBPT", "MockBPT") { 7 | struct OracleAverageQuery { 8 | Variable variable; 9 | uint256 secs; 10 | uint256 ago; 11 | } 12 | 13 | enum Variable { 14 | PAIR_PRICE, 15 | BPT_PRICE, 16 | INVARIANT 17 | } 18 | 19 | uint8 dec; 20 | 21 | uint256 public price; 22 | 23 | constructor( 24 | uint8 _decimals, 25 | address _initialRecipient, 26 | uint256 _initialMint 27 | ) { 28 | dec = _decimals; 29 | _mint(_initialRecipient, _initialMint * (10**uint256(_decimals))); 30 | } 31 | 32 | function mint(address to, uint256 amount) external { 33 | _mint(to, amount); 34 | } 35 | 36 | function setPrice(uint256 _price) external { 37 | price = _price; 38 | } 39 | 40 | function getTimeWeightedAverage(OracleAverageQuery[] memory) external view returns (uint256[] memory) { 41 | uint256[] memory results = new uint256[](1); 42 | results[0] = price; 43 | return results; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/mocks/balancer/MockBalancerVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | import "./MockBalancerPoolToken.sol"; 6 | import "../../Interfaces.sol"; 7 | 8 | interface IBalancerVault { 9 | function joinPool( 10 | bytes32 poolId, 11 | address sender, 12 | address recipient, 13 | JoinPoolRequest memory request 14 | ) external payable; 15 | 16 | struct JoinPoolRequest { 17 | address[] assets; 18 | uint256[] maxAmountsIn; 19 | bytes userData; 20 | bool fromInternalBalance; 21 | } 22 | } 23 | 24 | contract MockBalancerVault { 25 | address public pool; 26 | 27 | address public poolToken; 28 | 29 | address public tokenA; 30 | 31 | address public tokenB; 32 | 33 | constructor(address _poolToken) { 34 | poolToken = _poolToken; 35 | } 36 | 37 | function setTokens(address _tokenA, address _tokenB) external { 38 | tokenA = _tokenA; 39 | tokenB = _tokenB; 40 | } 41 | 42 | function getPool(bytes32) external view returns (address, IVault.PoolSpecialization) { 43 | return (poolToken, IVault.PoolSpecialization.GENERAL); 44 | } 45 | 46 | function joinPool( 47 | bytes32, /* poolId */ 48 | address, /* sender */ 49 | address recipient, 50 | IVault.JoinPoolRequest memory request 51 | ) external payable { 52 | uint256 amount = request.maxAmountsIn[0]; 53 | uint256 price = MockBalancerPoolToken(poolToken).price(); 54 | MockBalancerPoolToken(poolToken).mint(recipient, (amount * 1e18) / price); 55 | } 56 | 57 | function swap( 58 | IVault.SingleSwap memory singleSwap, 59 | IVault.FundManagement memory funds, 60 | uint256, /* limit */ 61 | uint256 /* deadline */ 62 | ) external returns (uint256 amountCalculated) { 63 | require(address(singleSwap.assetOut) == tokenA || address(singleSwap.assetOut) == tokenB, "!token"); 64 | 65 | if (address(singleSwap.assetOut) == tokenA) { 66 | // send tokenA 67 | IERC20(tokenB).transferFrom(funds.sender, address(this), singleSwap.amount); 68 | IERC20(tokenA).transfer(funds.recipient, singleSwap.amount); 69 | } else if (address(singleSwap.assetOut) == tokenB) { 70 | // send tokenB 71 | IERC20(tokenA).transferFrom(funds.sender, address(this), singleSwap.amount); 72 | IERC20(tokenB).transfer(funds.recipient, singleSwap.amount); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /contracts/mocks/balancer/MockFeeDistro.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | 6 | interface IFeeDistributor { 7 | function claimToken(address user, IERC20 token) external returns (uint256); 8 | 9 | function claimTokens(address user, IERC20[] calldata tokens) external returns (uint256[] memory); 10 | 11 | function getTokenTimeCursor(IERC20 token) external view returns (uint256); 12 | } 13 | 14 | // @dev - Must be funded by transferring crv to this contract post deployment, as opposed to minting directly 15 | contract MockFeeDistributor is IFeeDistributor { 16 | mapping(address => uint256) private tokenRates; 17 | 18 | constructor(address[] memory _tokens, uint256[] memory _rates) { 19 | for (uint256 i = 0; i < _tokens.length; i++) { 20 | tokenRates[_tokens[i]] = _rates[i]; 21 | } 22 | } 23 | 24 | function claimToken(address user, IERC20 token) external returns (uint256) { 25 | return _claimToken(user, token); 26 | } 27 | 28 | function _claimToken(address user, IERC20 token) internal returns (uint256) { 29 | uint256 rate = tokenRates[address(token)]; 30 | if (rate > 0) { 31 | token.transfer(user, rate); 32 | } 33 | return rate; 34 | } 35 | 36 | function claimTokens(address user, IERC20[] calldata tokens) external returns (uint256[] memory) { 37 | uint256[] memory rates = new uint256[](tokens.length); 38 | for (uint256 i = 0; i < tokens.length; i++) { 39 | rates[i] = _claimToken(user, tokens[i]); 40 | } 41 | return rates; 42 | } 43 | 44 | function getTokenTimeCursor(IERC20 token) external view returns (uint256) { 45 | return 1; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/mocks/balancer/MockInvestmentPoolFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | 6 | interface IInvestmentPoolFactory { 7 | function create( 8 | string memory name, 9 | string memory symbol, 10 | IERC20[] memory tokens, 11 | uint256[] memory weights, 12 | uint256 swapFeePercentage, 13 | address owner, 14 | bool swapEnabledOnStart, 15 | uint256 managementSwapFeePercentage 16 | ) external returns (address); 17 | } 18 | 19 | interface IInvestmentPool { 20 | function setSwapEnabled(bool swapEnabled) external; 21 | 22 | function updateWeightsGradually( 23 | uint256 startTime, 24 | uint256 endTime, 25 | uint256[] memory endWeights 26 | ) external; 27 | } 28 | -------------------------------------------------------------------------------- /contracts/mocks/balancer/MockStablePoolFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | 6 | interface IStablePoolFactory { 7 | function create( 8 | string memory name, 9 | string memory symbol, 10 | IERC20[] memory tokens, 11 | uint256 amplificationParameter, 12 | uint256 swapFeePercentage, 13 | address owner 14 | ) external returns (address); 15 | } 16 | -------------------------------------------------------------------------------- /contracts/mocks/balancer/MockWeightedPool2TokensFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | 6 | interface IWeightedPool2TokensFactory { 7 | function create( 8 | string memory name, 9 | string memory symbol, 10 | IERC20[] memory tokens, 11 | uint256[] memory weights, 12 | uint256 swapFeePercentage, 13 | bool oracleEnabled, 14 | address owner 15 | ) external returns (address); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/mocks/curve/MockCurveGauge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts-0.8/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 6 | 7 | contract MockCurveGauge is ERC20 { 8 | address public lp_token; 9 | 10 | // V2 gauge 11 | address[] public reward_tokens; 12 | 13 | constructor( 14 | string memory _name, 15 | string memory _symbol, 16 | address _lptoken, 17 | address[] memory _rewardTokens 18 | ) ERC20(_name, _symbol) { 19 | lp_token = _lptoken; 20 | reward_tokens = _rewardTokens; 21 | } 22 | 23 | function deposit(uint256 amount) external { 24 | _mint(msg.sender, amount); 25 | IERC20(lp_token).transferFrom(msg.sender, address(this), amount); 26 | } 27 | 28 | function withdraw(uint256 amount) external { 29 | _burn(msg.sender, amount); 30 | IERC20(lp_token).transfer(msg.sender, amount); 31 | } 32 | 33 | function claim_rewards() external { 34 | uint256 amount = balanceOf(msg.sender); 35 | 36 | for (uint256 i = 0; i < reward_tokens.length; i++) { 37 | IERC20(reward_tokens[i]).transfer(msg.sender, amount); 38 | } 39 | } 40 | 41 | function claimable_reward(address, address) external view returns (uint256) { 42 | return 0; 43 | } 44 | 45 | function deposit_reward_token(address, uint256) external {} 46 | 47 | function add_reward(address _reward_token, address _distributor) external {} 48 | } 49 | -------------------------------------------------------------------------------- /contracts/mocks/curve/MockCurveMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | 6 | interface IMinter { 7 | function mint(address) external; 8 | } 9 | 10 | // @dev - Must be funded by transferring crv to this contract post deployment, as opposed to minting directly 11 | contract MockCurveMinter is IMinter { 12 | IERC20 public immutable crv; 13 | uint256 public rate; 14 | 15 | constructor(address _crv, uint256 _rate) { 16 | crv = IERC20(_crv); 17 | rate = _rate; 18 | } 19 | 20 | function mint(address) external { 21 | crv.transfer(msg.sender, rate); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/mocks/curve/MockCurveVoteEscrow.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts-0.8/token/ERC20/ERC20.sol"; 6 | import "./MockWalletChecker.sol"; 7 | 8 | contract MockCurveVoteEscrow is ERC20("MockVE", "MockVE") { 9 | address public smart_wallet_checker; 10 | 11 | address public token; 12 | 13 | mapping(address => uint256) public lockAmounts; 14 | 15 | mapping(address => uint256) public lockTimes; 16 | 17 | uint256 public constant MAX_LEN = 365 days; 18 | 19 | constructor(address _smart_wallet_checker, address _token) { 20 | smart_wallet_checker = _smart_wallet_checker; 21 | token = _token; 22 | } 23 | 24 | function transfer( 25 | address, /* recipient */ 26 | uint256 /* amount */ 27 | ) public virtual override returns (bool) { 28 | revert("Not transferrable"); 29 | } 30 | 31 | function transferFrom( 32 | address, /* sender */ 33 | address, /* recipient */ 34 | uint256 /* amount */ 35 | ) public virtual override returns (bool) { 36 | revert("Not transferrable"); 37 | } 38 | 39 | function create_lock(uint256 amount, uint256 unlockTime) external { 40 | require(MockWalletChecker(smart_wallet_checker).check(msg.sender), "!contracts"); 41 | require(lockAmounts[msg.sender] == 0, "Withdraw old tokens first"); 42 | require(unlockTime < block.timestamp + MAX_LEN, "Lock too long"); 43 | require(amount > 0, "!amount"); 44 | 45 | lockAmounts[msg.sender] = amount; 46 | lockTimes[msg.sender] = unlockTime; 47 | 48 | IERC20(token).transferFrom(msg.sender, address(this), amount); 49 | _mint(msg.sender, amount); 50 | } 51 | 52 | function increase_amount(uint256 amount) external { 53 | require(lockAmounts[msg.sender] > 0, "Must have a lock"); 54 | require(lockTimes[msg.sender] > block.timestamp, "Current lock expired"); 55 | require(amount > 0, "!amount"); 56 | lockAmounts[msg.sender] += amount; 57 | 58 | IERC20(token).transferFrom(msg.sender, address(this), amount); 59 | _mint(msg.sender, amount); 60 | } 61 | 62 | function increase_unlock_time(uint256 time) external { 63 | require(lockAmounts[msg.sender] > 0, "Must have a lock"); 64 | require(lockTimes[msg.sender] > block.timestamp, "Current lock expired"); 65 | require(time > lockTimes[msg.sender], "Future time must be greater"); 66 | require(time < block.timestamp + MAX_LEN, "Lock too long"); 67 | lockTimes[msg.sender] = time; 68 | } 69 | 70 | function withdraw() external { 71 | require(lockTimes[msg.sender] < block.timestamp, "!unlocked"); 72 | 73 | uint256 amount = balanceOf(msg.sender); 74 | 75 | lockAmounts[msg.sender] = 0; 76 | lockTimes[msg.sender] = 0; 77 | 78 | IERC20(token).transfer(msg.sender, amount); 79 | _burn(msg.sender, amount); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /contracts/mocks/curve/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts-0.8/token/ERC20/ERC20.sol"; 5 | 6 | contract MockERC20 is ERC20 { 7 | uint8 dec; 8 | 9 | constructor( 10 | string memory _name, 11 | string memory _symbol, 12 | uint8 _decimals, 13 | address _initialRecipient, 14 | uint256 _initialMint 15 | ) ERC20(_name, _symbol) { 16 | dec = _decimals; 17 | _mint(_initialRecipient, _initialMint * (10**uint256(_decimals))); 18 | } 19 | 20 | function decimals() public view override returns (uint8) { 21 | return dec; 22 | } 23 | 24 | function mint(uint256 amount) public { 25 | _mint(msg.sender, amount); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/mocks/curve/MockVoting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | contract MockVoting { 5 | mapping(address => uint256) public gaugeWeights; 6 | 7 | mapping(uint256 => uint256) public votesFor; 8 | 9 | mapping(uint256 => uint256) public votesAgainst; 10 | 11 | struct VotedSlope { 12 | uint256 slope; 13 | uint256 power; 14 | uint256 end; 15 | } 16 | 17 | function vote( 18 | uint256 voteId, 19 | bool support, 20 | bool 21 | ) external { 22 | if (support) { 23 | votesFor[voteId]++; 24 | } else { 25 | votesAgainst[voteId]++; 26 | } 27 | } 28 | 29 | function vote_for_gauge_weights(address gauge, uint256 weight) external { 30 | gaugeWeights[gauge] += weight; 31 | } 32 | 33 | function get_gauge_weight(address gauge) external view returns (uint256) { 34 | return gaugeWeights[gauge]; 35 | } 36 | 37 | function vote_user_slopes(address user, address gauge) external view returns (VotedSlope memory) { 38 | return VotedSlope(0, 0, 0); 39 | } 40 | 41 | // Total vote power used by user 42 | function vote_user_power(address user) external view returns (uint256) { 43 | return 0; 44 | } 45 | 46 | // Last user vote's timestamp for each gauge address 47 | function last_user_vote(address user, address gauge) external view returns (uint256) { 48 | return 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/mocks/curve/MockWalletChecker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | contract MockWalletChecker { 5 | mapping(address => bool) public wallets; 6 | 7 | function approveWallet(address wallet) external { 8 | wallets[wallet] = true; 9 | } 10 | 11 | function check(address wallet) external view returns (bool) { 12 | return wallets[wallet]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/ArbitartorVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "./Interfaces.sol"; 5 | import "@openzeppelin/contracts-0.6/math/SafeMath.sol"; 6 | import "@openzeppelin/contracts-0.6/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts-0.6/utils/Address.sol"; 8 | import "@openzeppelin/contracts-0.6/token/ERC20/SafeERC20.sol"; 9 | 10 | /** 11 | * @title ArbitratorVault 12 | * @author ConvexFinance 13 | * @notice Hold extra reward tokens on behalf of pools that have the same token as a reward (e.g. stkAAVE fro multiple aave pools) 14 | * @dev Sits on top of the STASH to basically handle the re-distribution of rewards to multiple stashes. 15 | * Because anyone can call gauge.claim_rewards(address) for the convex staking contract, rewards 16 | * could be forced to the wrong pool. Hold tokens here and distribute fairly(or at least more fairly), 17 | * to both pools at a later timing. 18 | */ 19 | contract ArbitratorVault{ 20 | using SafeERC20 for IERC20; 21 | using Address for address; 22 | using SafeMath for uint256; 23 | 24 | address public operator; 25 | address public immutable depositor; 26 | 27 | 28 | /** 29 | * @param _depositor Booster address 30 | */ 31 | constructor(address _depositor)public 32 | { 33 | operator = msg.sender; 34 | depositor = _depositor; 35 | } 36 | 37 | function setOperator(address _op) external { 38 | require(msg.sender == operator, "!auth"); 39 | operator = _op; 40 | } 41 | 42 | /** 43 | * @notice Permissioned fn to distribute any accrued rewards to a relevant stash 44 | * @dev Only called by operator: ConvexMultisig 45 | */ 46 | function distribute(address _token, uint256[] calldata _toPids, uint256[] calldata _amounts) external { 47 | require(msg.sender == operator, "!auth"); 48 | 49 | for(uint256 i = 0; i < _toPids.length; i++){ 50 | //get stash from pid 51 | (,,,,address stashAddress,bool shutdown) = IDeposit(depositor).poolInfo(_toPids[i]); 52 | 53 | //if sent to a shutdown pool, could get trapped 54 | require(shutdown==false,"pool closed"); 55 | 56 | //transfer 57 | IERC20(_token).safeTransfer(stashAddress, _amounts[i]); 58 | } 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/DepositToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "./Interfaces.sol"; 5 | import "@openzeppelin/contracts-0.6/math/SafeMath.sol"; 6 | import "@openzeppelin/contracts-0.6/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts-0.6/utils/Address.sol"; 8 | import "@openzeppelin/contracts-0.6/token/ERC20/SafeERC20.sol"; 9 | import "@openzeppelin/contracts-0.6/token/ERC20/ERC20.sol"; 10 | 11 | 12 | /** 13 | * @title DepositToken 14 | * @author ConvexFinance 15 | * @notice Simply creates a token that can be minted and burned from the operator 16 | */ 17 | contract DepositToken is ERC20 { 18 | using SafeERC20 for IERC20; 19 | using Address for address; 20 | using SafeMath for uint256; 21 | 22 | address public operator; 23 | 24 | /** 25 | * @param _operator Booster 26 | * @param _lptoken Underlying LP token for deposits 27 | * @param _namePostfix Postfixes lpToken name 28 | * @param _symbolPrefix Prefixed lpToken symbol 29 | */ 30 | constructor( 31 | address _operator, 32 | address _lptoken, 33 | string memory _namePostfix, 34 | string memory _symbolPrefix 35 | ) 36 | public 37 | ERC20( 38 | string( 39 | abi.encodePacked(ERC20(_lptoken).name(), _namePostfix) 40 | ), 41 | string(abi.encodePacked(_symbolPrefix, ERC20(_lptoken).symbol())) 42 | ) 43 | { 44 | operator = _operator; 45 | } 46 | 47 | function mint(address _to, uint256 _amount) external { 48 | require(msg.sender == operator, "!authorized"); 49 | 50 | _mint(_to, _amount); 51 | } 52 | 53 | function burn(address _from, uint256 _amount) external { 54 | require(msg.sender == operator, "!authorized"); 55 | 56 | _burn(_from, _amount); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/PoolManagerProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "./Interfaces.sol"; 5 | 6 | /** 7 | * @title PoolManagerProxy 8 | * @author ConvexFinance 9 | * @notice Immutable pool manager proxy to enforce that there are no multiple pools of the same gauge 10 | * as well as new lp tokens are not gauge tokens 11 | * @dev Called by PoolManagerShutdownProxy 12 | */ 13 | contract PoolManagerProxy{ 14 | 15 | address public immutable pools; 16 | address public owner; 17 | address public operator; 18 | 19 | /** 20 | * @param _pools Contract can call addPool currently Booster 21 | * @param _owner Contract owner currently multisig 22 | */ 23 | constructor( 24 | address _pools, 25 | address _owner 26 | ) public { 27 | pools = _pools; 28 | owner = _owner; 29 | operator = msg.sender; 30 | } 31 | 32 | modifier onlyOwner() { 33 | require(owner == msg.sender, "!owner"); 34 | _; 35 | } 36 | 37 | modifier onlyOperator() { 38 | require(operator == msg.sender, "!op"); 39 | _; 40 | } 41 | 42 | //set owner - only OWNER 43 | function setOwner(address _owner) external onlyOwner{ 44 | owner = _owner; 45 | } 46 | 47 | //set operator - only OWNER 48 | function setOperator(address _operator) external onlyOwner{ 49 | operator = _operator; 50 | } 51 | 52 | // sealed to be immutable 53 | // function revertControl() external{ 54 | // } 55 | 56 | //shutdown a pool - only OPERATOR 57 | function shutdownPool(uint256 _pid) external onlyOperator returns(bool){ 58 | return IPools(pools).shutdownPool(_pid); 59 | } 60 | 61 | /** 62 | * @notice Add pool to system 63 | * @dev Only callable by the operator looks up the gauge from the gaugeMap in Booster to ensure 64 | * it hasn't already been added 65 | */ 66 | function addPool(address _lptoken, address _gauge, uint256 _stashVersion) external onlyOperator returns(bool){ 67 | 68 | require(_gauge != address(0),"gauge is 0"); 69 | require(_lptoken != address(0),"lp token is 0"); 70 | 71 | //check if a pool with this gauge already exists 72 | bool gaugeExists = IPools(pools).gaugeMap(_gauge); 73 | require(!gaugeExists, "already registered gauge"); 74 | 75 | //must also check that the lp token is not a registered gauge 76 | //because curve gauges are tokenized 77 | gaugeExists = IPools(pools).gaugeMap(_lptoken); 78 | require(!gaugeExists, "already registered lptoken"); 79 | 80 | return IPools(pools).addPool(_lptoken,_gauge,_stashVersion); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/PoolManagerSecondaryProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "./Interfaces.sol"; 5 | import "./interfaces/IGaugeController.sol"; 6 | import "@openzeppelin/contracts-0.6/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts-0.6/math/SafeMath.sol"; 8 | 9 | /** 10 | * @title PoolManagerSecondaryProxy 11 | * @author ConvexFinance 12 | * @notice Basically a PoolManager that has a better shutdown and calls addPool on PoolManagerProxy. 13 | * Immutable pool manager proxy to enforce that when a pool is shutdown, the proper number 14 | * of lp tokens are returned to the booster contract for withdrawal. 15 | */ 16 | contract PoolManagerSecondaryProxy{ 17 | using SafeMath for uint256; 18 | 19 | address public immutable gaugeController; 20 | address public immutable pools; 21 | address public immutable booster; 22 | address public owner; 23 | address public operator; 24 | bool public isShutdown; 25 | 26 | mapping(address => bool) public usedMap; 27 | 28 | /** 29 | * @param _gaugeController Curve Gauge controller (0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB) 30 | * @param _pools PoolManagerProxy (0x5F47010F230cE1568BeA53a06eBAF528D05c5c1B) 31 | * @param _booster Booster 32 | * @param _owner Executoor 33 | */ 34 | constructor( 35 | address _gaugeController, 36 | address _pools, 37 | address _booster, 38 | address _owner 39 | ) public { 40 | gaugeController = _gaugeController; 41 | pools = _pools; 42 | booster = _booster; 43 | owner = _owner; 44 | operator = msg.sender; 45 | } 46 | 47 | modifier onlyOwner() { 48 | require(owner == msg.sender, "!owner"); 49 | _; 50 | } 51 | 52 | modifier onlyOperator() { 53 | require(operator == msg.sender, "!op"); 54 | _; 55 | } 56 | 57 | //set owner - only OWNER 58 | function setOwner(address _owner) external onlyOwner{ 59 | owner = _owner; 60 | } 61 | 62 | //set operator - only OWNER 63 | function setOperator(address _operator) external onlyOwner{ 64 | operator = _operator; 65 | } 66 | 67 | //manual set an address to used state 68 | function setUsedAddress(address[] memory usedList) external onlyOwner{ 69 | for(uint i=0; i < usedList.length; i++){ 70 | usedMap[usedList[i]] = true; 71 | } 72 | } 73 | 74 | //shutdown pool management and disallow new pools. change is immutable 75 | function shutdownSystem() external onlyOwner{ 76 | isShutdown = true; 77 | } 78 | 79 | /** 80 | * @notice Shutdown a pool - only OPERATOR 81 | * @dev Shutdowns a pool and ensures all the LP tokens are properly 82 | * withdrawn to the Booster contract 83 | */ 84 | function shutdownPool(uint256 _pid) external onlyOperator returns(bool){ 85 | //get pool info 86 | (address lptoken, address depositToken,,,,bool isshutdown) = IPools(booster).poolInfo(_pid); 87 | require(!isshutdown, "already shutdown"); 88 | 89 | //shutdown pool and get before and after amounts 90 | uint256 beforeBalance = IERC20(lptoken).balanceOf(booster); 91 | IPools(pools).shutdownPool(_pid); 92 | uint256 afterBalance = IERC20(lptoken).balanceOf(booster); 93 | 94 | //check that proper amount of tokens were withdrawn(will also fail if already shutdown) 95 | require( afterBalance.sub(beforeBalance) >= IERC20(depositToken).totalSupply(), "supply mismatch"); 96 | 97 | return true; 98 | } 99 | 100 | //add a new pool if it has weight on the gauge controller - only OPERATOR 101 | function addPool(address _lptoken, address _gauge, uint256 _stashVersion) external onlyOperator returns(bool){ 102 | //check that the pool as weight 103 | uint256 weight = IGaugeController(gaugeController).get_gauge_weight(_gauge); 104 | require(weight > 0, "must have weight"); 105 | 106 | return _addPool(_lptoken, _gauge, _stashVersion); 107 | } 108 | 109 | //force add a new pool, but only for addresses that have never been used before - only OPERATOR 110 | function forceAddPool(address _lptoken, address _gauge, uint256 _stashVersion) external onlyOperator returns(bool){ 111 | require(!usedMap[_lptoken] && !usedMap[_gauge], "cant force used pool"); 112 | 113 | return _addPool(_lptoken, _gauge, _stashVersion); 114 | } 115 | 116 | //internal add pool and updated used list 117 | function _addPool(address _lptoken, address _gauge, uint256 _stashVersion) internal returns(bool){ 118 | require(!isShutdown, "shutdown"); 119 | 120 | usedMap[_lptoken] = true; 121 | usedMap[_gauge] = true; 122 | 123 | return IPools(pools).addPool(_lptoken,_gauge,_stashVersion); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/PoolManagerV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "./Interfaces.sol"; 5 | import "./interfaces/IGaugeController.sol"; 6 | 7 | /** 8 | * @title PoolManagerV3 9 | * @author ConvexFinance 10 | * @notice Pool Manager v3 11 | * PoolManagerV3 calls addPool on PoolManagerShutdownProxy which calls 12 | * addPool on PoolManagerProxy which calls addPool on Booster. 13 | * PoolManager-ception 14 | * @dev Add pools to the Booster contract 15 | */ 16 | contract PoolManagerV3{ 17 | 18 | address public immutable pools; 19 | address public immutable gaugeController; 20 | address public operator; 21 | 22 | bool public protectAddPool; 23 | 24 | /** 25 | * @param _pools Currently PoolManagerSecondaryProxy 26 | * @param _gaugeController Curve gauge controller e.g: (0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB) 27 | * @param _operator Convex multisig 28 | */ 29 | constructor( 30 | address _pools, 31 | address _gaugeController, 32 | address _operator 33 | ) public { 34 | pools = _pools; 35 | gaugeController = _gaugeController; 36 | operator = _operator; 37 | protectAddPool = true; 38 | } 39 | 40 | function setOperator(address _operator) external { 41 | require(msg.sender == operator, "!auth"); 42 | operator = _operator; 43 | } 44 | 45 | /** 46 | * @notice set if addPool is only callable by operator 47 | */ 48 | function setProtectPool(bool _protectAddPool) external { 49 | require(msg.sender == operator, "!auth"); 50 | protectAddPool = _protectAddPool; 51 | } 52 | 53 | /** 54 | * @notice Add a new curve pool to the system. (default stash to v3) 55 | */ 56 | function addPool(address _gauge) external returns(bool){ 57 | _addPool(_gauge,3); 58 | return true; 59 | } 60 | 61 | /** 62 | * @notice Add a new curve pool to the system 63 | */ 64 | function addPool(address _gauge, uint256 _stashVersion) external returns(bool){ 65 | _addPool(_gauge,_stashVersion); 66 | return true; 67 | } 68 | 69 | function _addPool(address _gauge, uint256 _stashVersion) internal{ 70 | if(protectAddPool) { 71 | require(msg.sender == operator, "!auth"); 72 | } 73 | //get lp token from gauge 74 | address lptoken = ICurveGauge(_gauge).lp_token(); 75 | 76 | //gauge/lptoken address checks will happen in the next call 77 | IPools(pools).addPool(lptoken,_gauge,_stashVersion); 78 | } 79 | 80 | function forceAddPool(address _lptoken, address _gauge, uint256 _stashVersion) external returns(bool){ 81 | require(msg.sender==operator, "!auth"); 82 | 83 | //force add pool without weight checks (can only be used on new token and gauge addresses) 84 | return IPools(pools).forceAddPool(_lptoken, _gauge, _stashVersion); 85 | } 86 | 87 | function shutdownPool(uint256 _pid) external returns(bool){ 88 | require(msg.sender==operator, "!auth"); 89 | 90 | IPools(pools).shutdownPool(_pid); 91 | return true; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/ProxyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | /** 5 | * @title ProxyFactory 6 | * @author Optionality 7 | * @notice Simply clones a smart contract at a given address 8 | * @dev Original code https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol 9 | */ 10 | contract ProxyFactory { 11 | function clone(address target) external returns (address result) { 12 | bytes20 targetBytes = bytes20(target); 13 | assembly { 14 | let clone := mload(0x40) 15 | mstore( 16 | clone, 17 | 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000 18 | ) 19 | mstore(add(clone, 0x14), targetBytes) 20 | mstore( 21 | add(clone, 0x28), 22 | 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 23 | ) 24 | result := create(0, clone, 0x37) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/RewardFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "./Interfaces.sol"; 5 | import "./BaseRewardPool4626.sol"; 6 | import "./VirtualBalanceRewardPool.sol"; 7 | import "@openzeppelin/contracts-0.6/math/SafeMath.sol"; 8 | import "@openzeppelin/contracts-0.6/token/ERC20/IERC20.sol"; 9 | import "@openzeppelin/contracts-0.6/utils/Address.sol"; 10 | import "@openzeppelin/contracts-0.6/token/ERC20/SafeERC20.sol"; 11 | 12 | 13 | /** 14 | * @title RewardFactory 15 | * @author ConvexFinance 16 | * @notice Used to deploy reward pools when a new pool is added to the Booster 17 | * contract. This contract deploys two types of reward pools: 18 | * - BaseRewardPool handles CRV rewards for guages 19 | * - VirtualBalanceRewardPool for extra rewards 20 | */ 21 | contract RewardFactory { 22 | using Address for address; 23 | 24 | address public immutable operator; 25 | address public immutable crv; 26 | 27 | mapping (address => bool) private rewardAccess; 28 | mapping(address => uint256[]) public rewardActiveList; 29 | 30 | 31 | event RewardPoolCreated(address rewardPool, uint256 _pid, address depositToken); 32 | event TokenRewardPoolCreated(address rewardPool, address token, address mainRewards, address operator); 33 | 34 | event AccessChanged(address stash, bool hasAccess); 35 | 36 | /** 37 | * @param _operator Contract operator is Booster 38 | * @param _crv CRV token address 39 | */ 40 | constructor(address _operator, address _crv) public { 41 | operator = _operator; 42 | crv = _crv; 43 | } 44 | 45 | //stash contracts need access to create new Virtual balance pools for extra gauge incentives(ex. snx) 46 | function setAccess(address _stash, bool _status) external{ 47 | require(msg.sender == operator, "!auth"); 48 | rewardAccess[_stash] = _status; 49 | 50 | emit AccessChanged(_stash, _status); 51 | } 52 | 53 | /** 54 | * @notice Create a Managed Reward Pool to handle distribution of all crv mined in a pool 55 | */ 56 | function CreateCrvRewards(uint256 _pid, address _depositToken, address _lptoken) external returns (address) { 57 | require(msg.sender == operator, "!auth"); 58 | 59 | //operator = booster(deposit) contract so that new crv can be added and distributed 60 | //reward manager = this factory so that extra incentive tokens(ex. snx) can be linked to the main managed reward pool 61 | BaseRewardPool4626 rewardPool = new BaseRewardPool4626(_pid,_depositToken,crv,operator, address(this), _lptoken); 62 | 63 | emit RewardPoolCreated(address(rewardPool), _pid, _depositToken); 64 | return address(rewardPool); 65 | } 66 | 67 | /** 68 | * @notice Create a virtual balance reward pool that mimics the balance of a pool's main reward contract 69 | * used for extra incentive tokens(ex. snx) as well as vecrv fees 70 | */ 71 | function CreateTokenRewards(address _token, address _mainRewards, address _operator) external returns (address) { 72 | require(msg.sender == operator || rewardAccess[msg.sender] == true, "!auth"); 73 | 74 | //create new pool, use main pool for balance lookup 75 | VirtualBalanceRewardPool rewardPool = new VirtualBalanceRewardPool(_mainRewards,_token,_operator); 76 | address rAddress = address(rewardPool); 77 | //add the new pool to main pool's list of extra rewards, assuming this factory has "reward manager" role 78 | IRewards(_mainRewards).addExtraReward(rAddress); 79 | 80 | emit TokenRewardPoolCreated(rAddress, _token, _mainRewards, _operator); 81 | //return new pool's address 82 | return rAddress; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/RewardHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "@openzeppelin/contracts-0.6/utils/Address.sol"; 5 | import "@openzeppelin/contracts-0.6/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts-0.6/token/ERC20/SafeERC20.sol"; 7 | import "@openzeppelin/contracts-0.6/math/SafeMath.sol"; 8 | 9 | 10 | /** 11 | * @title RewardHook 12 | * @author ConvexFinance 13 | * @notice Example Reward hook for stash 14 | * @dev ExtraRewardStash contracts call this hook if it is set. This hook 15 | * can be used to pull rewards during a claim. For example pulling 16 | * rewards from master chef. 17 | */ 18 | contract RewardHook{ 19 | using SafeERC20 for IERC20; 20 | using Address for address; 21 | using SafeMath for uint256; 22 | 23 | 24 | address public immutable stash; 25 | address public immutable rewardToken; 26 | 27 | 28 | /** 29 | * @param _stash Address of the reward stash 30 | * @param _reward Reward token 31 | */ 32 | constructor(address _stash, address _reward) public { 33 | stash = _stash; 34 | rewardToken = _reward; 35 | } 36 | 37 | 38 | /** 39 | * @dev Called when claimRewards is called in ExtraRewardStash can implement 40 | * logic to pull rewards i.e from a master chef contract. This is just an example 41 | * and assumes rewards are just sent directly to this hook contract 42 | */ 43 | function onRewardClaim() external{ 44 | 45 | //get balance 46 | uint256 bal = IERC20(rewardToken).balanceOf(address(this)); 47 | 48 | //send 49 | IERC20(rewardToken).safeTransfer(stash,bal); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/StashFactoryV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "./Interfaces.sol"; 5 | import "./interfaces/IProxyFactory.sol"; 6 | import "@openzeppelin/contracts-0.6/math/SafeMath.sol"; 7 | import "@openzeppelin/contracts-0.6/token/ERC20/IERC20.sol"; 8 | import "@openzeppelin/contracts-0.6/utils/Address.sol"; 9 | import "@openzeppelin/contracts-0.6/token/ERC20/SafeERC20.sol"; 10 | 11 | /** 12 | * @title StashFactoryV2 13 | * @author ConvexFinance 14 | * @notice Factory to deploy reward stash contracts that handle extra rewards 15 | */ 16 | contract StashFactoryV2 { 17 | using Address for address; 18 | 19 | bytes4 private constant rewarded_token = 0x16fa50b1; //rewarded_token() 20 | bytes4 private constant reward_tokens = 0x54c49fe9; //reward_tokens(uint256) 21 | bytes4 private constant rewards_receiver = 0x01ddabf1; //rewards_receiver(address) 22 | 23 | address public immutable operator; 24 | address public immutable rewardFactory; 25 | address public immutable proxyFactory; 26 | 27 | address public v1Implementation; 28 | address public v2Implementation; 29 | address public v3Implementation; 30 | 31 | event StashCreated(address stash, uint256 stashVersion); 32 | 33 | /** 34 | * @param _operator Operator is Booster 35 | * @param _rewardFactory Factory that creates reward contract that are 36 | * VirtualBalanceRewardPool's used for extra pool rewards 37 | * @param _proxyFactory Deploy proxies with stash implementation 38 | */ 39 | constructor(address _operator, address _rewardFactory, address _proxyFactory) public { 40 | operator = _operator; 41 | rewardFactory = _rewardFactory; 42 | proxyFactory = _proxyFactory; 43 | } 44 | 45 | function setImplementation(address _v1, address _v2, address _v3) external{ 46 | require(msg.sender == IDeposit(operator).owner(),"!auth"); 47 | 48 | v1Implementation = _v1; 49 | v2Implementation = _v2; 50 | v3Implementation = _v3; 51 | } 52 | 53 | //Create a stash contract for the given gauge. 54 | //function calls are different depending on the version of curve gauges so determine which stash type is needed 55 | function CreateStash(uint256 _pid, address _gauge, address _staker, uint256 _stashVersion) external returns(address){ 56 | require(msg.sender == operator, "!authorized"); 57 | 58 | if(_stashVersion == uint256(3) && IsV3(_gauge)){ 59 | //v3 60 | require(v3Implementation!=address(0),"0 impl"); 61 | address stash = IProxyFactory(proxyFactory).clone(v3Implementation); 62 | IStash(stash).initialize(_pid,operator,_staker,_gauge,rewardFactory); 63 | emit StashCreated(stash, _stashVersion); 64 | return stash; 65 | }else if(_stashVersion == uint256(1) && IsV1(_gauge)){ 66 | //v1 67 | require(v1Implementation!=address(0),"0 impl"); 68 | address stash = IProxyFactory(proxyFactory).clone(v1Implementation); 69 | IStash(stash).initialize(_pid,operator,_staker,_gauge,rewardFactory); 70 | emit StashCreated(stash, _stashVersion); 71 | return stash; 72 | }else if(_stashVersion == uint256(2) && !IsV3(_gauge) && IsV2(_gauge)){ 73 | //v2 74 | require(v2Implementation!=address(0),"0 impl"); 75 | address stash = IProxyFactory(proxyFactory).clone(v2Implementation); 76 | IStash(stash).initialize(_pid,operator,_staker,_gauge,rewardFactory); 77 | emit StashCreated(stash, _stashVersion); 78 | return stash; 79 | } 80 | bool isV1 = IsV1(_gauge); 81 | bool isV2 = IsV2(_gauge); 82 | bool isV3 = IsV3(_gauge); 83 | require(!isV1 && !isV2 && !isV3,"stash version mismatch"); 84 | return address(0); 85 | } 86 | 87 | function IsV1(address _gauge) private returns(bool){ 88 | bytes memory data = abi.encode(rewarded_token); 89 | (bool success,) = _gauge.call(data); 90 | return success; 91 | } 92 | 93 | function IsV2(address _gauge) private returns(bool){ 94 | bytes memory data = abi.encodeWithSelector(reward_tokens,uint256(0)); 95 | (bool success,) = _gauge.call(data); 96 | return success; 97 | } 98 | 99 | function IsV3(address _gauge) private returns(bool){ 100 | bytes memory data = abi.encodeWithSelector(rewards_receiver,address(0)); 101 | (bool success,) = _gauge.call(data); 102 | return success; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/TokenFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "./Interfaces.sol"; 5 | import "./DepositToken.sol"; 6 | import "@openzeppelin/contracts-0.6/math/SafeMath.sol"; 7 | import "@openzeppelin/contracts-0.6/token/ERC20/IERC20.sol"; 8 | import "@openzeppelin/contracts-0.6/utils/Address.sol"; 9 | import "@openzeppelin/contracts-0.6/token/ERC20/SafeERC20.sol"; 10 | 11 | /** 12 | * @title TokenFactory 13 | * @author ConvexFinance 14 | * @notice Token factory used to create Deposit Tokens. These are the tokenized 15 | * pool deposit tokens e.g cvx3crv 16 | */ 17 | contract TokenFactory { 18 | using Address for address; 19 | 20 | address public immutable operator; 21 | string public namePostfix; 22 | string public symbolPrefix; 23 | 24 | event DepositTokenCreated(address token, address lpToken); 25 | 26 | /** 27 | * @param _operator Operator is Booster 28 | * @param _namePostfix Postfixes lpToken name 29 | * @param _symbolPrefix Prefixed lpToken symbol 30 | */ 31 | constructor( 32 | address _operator, 33 | string memory _namePostfix, 34 | string memory _symbolPrefix 35 | ) public { 36 | operator = _operator; 37 | namePostfix = _namePostfix; 38 | symbolPrefix = _symbolPrefix; 39 | } 40 | 41 | function CreateDepositToken(address _lptoken) external returns(address){ 42 | require(msg.sender == operator, "!authorized"); 43 | 44 | DepositToken dtoken = new DepositToken(operator,_lptoken,namePostfix,symbolPrefix); 45 | emit DepositTokenCreated(address(dtoken), _lptoken); 46 | return address(dtoken); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/cCrv.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "./Interfaces.sol"; 5 | import "@openzeppelin/contracts-0.6/math/SafeMath.sol"; 6 | import "@openzeppelin/contracts-0.6/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts-0.6/utils/Address.sol"; 8 | import "@openzeppelin/contracts-0.6/token/ERC20/SafeERC20.sol"; 9 | import "@openzeppelin/contracts-0.6/token/ERC20/ERC20.sol"; 10 | 11 | 12 | /** 13 | * @title cvxCrvToken 14 | * @author ConvexFinance 15 | * @notice Dumb ERC20 token that allows the operator (crvDepositor) to mint and burn tokens 16 | */ 17 | contract cvxCrvToken is ERC20 { 18 | using SafeERC20 for IERC20; 19 | using Address for address; 20 | using SafeMath for uint256; 21 | 22 | address public operator; 23 | 24 | constructor(string memory _nameArg, string memory _symbolArg) 25 | public 26 | ERC20( 27 | _nameArg, 28 | _symbolArg 29 | ) 30 | { 31 | operator = msg.sender; 32 | } 33 | 34 | /** 35 | * @notice Allows the initial operator (deployer) to set the operator. 36 | * Note - crvDepositor has no way to change this back, so it's effectively immutable 37 | */ 38 | function setOperator(address _operator) external { 39 | require(msg.sender == operator, "!auth"); 40 | operator = _operator; 41 | } 42 | 43 | /** 44 | * @notice Allows the crvDepositor to mint 45 | */ 46 | function mint(address _to, uint256 _amount) external { 47 | require(msg.sender == operator, "!authorized"); 48 | 49 | _mint(_to, _amount); 50 | } 51 | 52 | /** 53 | * @notice Allows the crvDepositor to burn 54 | */ 55 | function burn(address _from, uint256 _amount) external { 56 | require(msg.sender == operator, "!authorized"); 57 | 58 | _burn(_from, _amount); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/interfaces/BoringMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | /// @notice A library for performing overflow-/underflow-safe math, 5 | /// updated with awesomeness from of DappHub (https://github.com/dapphub/ds-math). 6 | library BoringMath { 7 | function add(uint256 a, uint256 b) internal pure returns (uint256 c) { 8 | require((c = a + b) >= b, "BoringMath: Add Overflow"); 9 | } 10 | 11 | function sub(uint256 a, uint256 b) internal pure returns (uint256 c) { 12 | require((c = a - b) <= a, "BoringMath: Underflow"); 13 | } 14 | 15 | function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { 16 | require(b == 0 || (c = a * b) / b == a, "BoringMath: Mul Overflow"); 17 | } 18 | 19 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 20 | require(b > 0, "BoringMath: division by zero"); 21 | return a / b; 22 | } 23 | 24 | function to128(uint256 a) internal pure returns (uint128 c) { 25 | require(a <= uint128(-1), "BoringMath: uint128 Overflow"); 26 | c = uint128(a); 27 | } 28 | 29 | function to64(uint256 a) internal pure returns (uint64 c) { 30 | require(a <= uint64(-1), "BoringMath: uint64 Overflow"); 31 | c = uint64(a); 32 | } 33 | 34 | function to32(uint256 a) internal pure returns (uint32 c) { 35 | require(a <= uint32(-1), "BoringMath: uint32 Overflow"); 36 | c = uint32(a); 37 | } 38 | 39 | function to40(uint256 a) internal pure returns (uint40 c) { 40 | require(a <= uint40(-1), "BoringMath: uint40 Overflow"); 41 | c = uint40(a); 42 | } 43 | 44 | function to112(uint256 a) internal pure returns (uint112 c) { 45 | require(a <= uint112(-1), "BoringMath: uint112 Overflow"); 46 | c = uint112(a); 47 | } 48 | 49 | function to224(uint256 a) internal pure returns (uint224 c) { 50 | require(a <= uint224(-1), "BoringMath: uint224 Overflow"); 51 | c = uint224(a); 52 | } 53 | 54 | function to208(uint256 a) internal pure returns (uint208 c) { 55 | require(a <= uint208(-1), "BoringMath: uint208 Overflow"); 56 | c = uint208(a); 57 | } 58 | 59 | function to216(uint256 a) internal pure returns (uint216 c) { 60 | require(a <= uint216(-1), "BoringMath: uint216 Overflow"); 61 | c = uint216(a); 62 | } 63 | } 64 | 65 | /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint128. 66 | library BoringMath128 { 67 | function add(uint128 a, uint128 b) internal pure returns (uint128 c) { 68 | require((c = a + b) >= b, "BoringMath: Add Overflow"); 69 | } 70 | 71 | function sub(uint128 a, uint128 b) internal pure returns (uint128 c) { 72 | require((c = a - b) <= a, "BoringMath: Underflow"); 73 | } 74 | } 75 | 76 | /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint64. 77 | library BoringMath64 { 78 | function add(uint64 a, uint64 b) internal pure returns (uint64 c) { 79 | require((c = a + b) >= b, "BoringMath: Add Overflow"); 80 | } 81 | 82 | function sub(uint64 a, uint64 b) internal pure returns (uint64 c) { 83 | require((c = a - b) <= a, "BoringMath: Underflow"); 84 | } 85 | } 86 | 87 | /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint32. 88 | library BoringMath32 { 89 | function add(uint32 a, uint32 b) internal pure returns (uint32 c) { 90 | require((c = a + b) >= b, "BoringMath: Add Overflow"); 91 | } 92 | 93 | function sub(uint32 a, uint32 b) internal pure returns (uint32 c) { 94 | require((c = a - b) <= a, "BoringMath: Underflow"); 95 | } 96 | 97 | function mul(uint32 a, uint32 b) internal pure returns (uint32 c) { 98 | require(b == 0 || (c = a * b) / b == a, "BoringMath: Mul Overflow"); 99 | } 100 | 101 | function div(uint32 a, uint32 b) internal pure returns (uint32) { 102 | require(b > 0, "BoringMath: division by zero"); 103 | return a / b; 104 | } 105 | } 106 | 107 | 108 | /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint112. 109 | library BoringMath112 { 110 | function add(uint112 a, uint112 b) internal pure returns (uint112 c) { 111 | require((c = a + b) >= b, "BoringMath: Add Overflow"); 112 | } 113 | 114 | function sub(uint112 a, uint112 b) internal pure returns (uint112 c) { 115 | require((c = a - b) <= a, "BoringMath: Underflow"); 116 | } 117 | 118 | function mul(uint112 a, uint112 b) internal pure returns (uint112 c) { 119 | require(b == 0 || (c = a * b) / b == a, "BoringMath: Mul Overflow"); 120 | } 121 | 122 | function div(uint112 a, uint112 b) internal pure returns (uint112) { 123 | require(b > 0, "BoringMath: division by zero"); 124 | return a / b; 125 | } 126 | } 127 | 128 | /// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint224. 129 | library BoringMath224 { 130 | function add(uint224 a, uint224 b) internal pure returns (uint224 c) { 131 | require((c = a + b) >= b, "BoringMath: Add Overflow"); 132 | } 133 | 134 | function sub(uint224 a, uint224 b) internal pure returns (uint224 c) { 135 | require((c = a - b) <= a, "BoringMath: Underflow"); 136 | } 137 | 138 | function mul(uint224 a, uint224 b) internal pure returns (uint224 c) { 139 | require(b == 0 || (c = a * b) / b == a, "BoringMath: Mul Overflow"); 140 | } 141 | 142 | function div(uint224 a, uint224 b) internal pure returns (uint224) { 143 | require(b > 0, "BoringMath: division by zero"); 144 | return a / b; 145 | } 146 | } -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/interfaces/IERC20Metadata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts-0.6/token/ERC20/IERC20.sol"; 5 | 6 | /** 7 | * @dev Interface for the optional metadata functions from the ERC20 standard. 8 | * 9 | * _Available since v4.1._ 10 | */ 11 | interface IERC20Metadata is IERC20 { 12 | /** 13 | * @dev Returns the name of the token. 14 | */ 15 | function name() external view returns (string memory); 16 | 17 | /** 18 | * @dev Returns the symbol of the token. 19 | */ 20 | function symbol() external view returns (string memory); 21 | 22 | /** 23 | * @dev Returns the decimals places of the token. 24 | */ 25 | function decimals() external view returns (uint8); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/interfaces/IGaugeController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.6.12; 4 | 5 | interface IGaugeController { 6 | function get_gauge_weight(address _gauge) external view returns(uint256); 7 | function vote_user_slopes(address,address) external view returns(uint256,uint256,uint256);//slope,power,end 8 | function vote_for_gauge_weights(address,uint256) external; 9 | function add_gauge(address,int128,uint256) external; 10 | } -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/interfaces/IProxyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | interface IProxyFactory { 5 | function clone(address _target) external returns(address); 6 | } -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/interfaces/IRewardHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.6.12; 4 | 5 | interface IRewardHook { 6 | function onRewardClaim() external; 7 | } -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/interfaces/IRewarder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.6.12; 4 | import "@openzeppelin/contracts-0.6/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts-0.6/token/ERC20/SafeERC20.sol"; 6 | 7 | interface IRewarder { 8 | using SafeERC20 for IERC20; 9 | function onReward(uint256 pid, address user, address recipient, uint256 sushiAmount, uint256 newLpAmount) external; 10 | function pendingTokens(uint256 pid, address user, uint256 sushiAmount) external view returns (IERC20[] memory, uint256[] memory); 11 | } -------------------------------------------------------------------------------- /convex-platform/contracts/contracts/interfaces/MathUtil.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | /** 5 | * @dev Standard math utilities missing in the Solidity language. 6 | */ 7 | library MathUtil { 8 | /** 9 | * @dev Returns the smallest of two numbers. 10 | */ 11 | function min(uint256 a, uint256 b) internal pure returns (uint256) { 12 | return a < b ? a : b; 13 | } 14 | } -------------------------------------------------------------------------------- /convex-platform/contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "deploy": "truffle migrate --network ganachecli --skip-dry-run", 4 | "compile": "truffle compile" 5 | }, 6 | "dependencies": { 7 | "@openzeppelin/contracts-0.6": "npm:@openzeppelin/contracts@3.4.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/0-EB806.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/0-EB806.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/0ab6fddedfdca4dee0b54ab41ab4faad-C5611.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/0ab6fddedfdca4dee0b54ab41ab4faad-C5611.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1-B2132.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1-B2132.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f389-5C738.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f3c1-445DC.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f3c6-621A1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f43a-EB486.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f440-6C64D.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f448-3DF32.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f449-25CFB.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f44b-8A059.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f44c-59547.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f44d-27259.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f48c-A96CC.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f49c-71A75.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f4aa-2FD27.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f4af-4CFF5.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f4b8-E3468.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f4c6-44E30.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f4dc-AC641.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f50d-195C0.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f512-24F85.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f604-BF863.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f60d-BEAFF.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f626-91074.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f642-83E8A.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f64c-7C820.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f64f-22B8D.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f680-A35CE.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f6a8-A8AB3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f7e2-FC8EC.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f911-F346C.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f91e-2A114.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f972-F415D.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f9be-DDCD0.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1f9d1-5BC80.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/1fac2-960B6.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/20c8931564dd11b412a4fecc64bf9451-26B4C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/20c8931564dd11b412a4fecc64bf9451-26B4C.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/23a7e58bdc28d6ad048874c84ad5fdb1-DE264.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/23a7e58bdc28d6ad048874c84ad5fdb1-DE264.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/2665-8CDCE.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/2696-15F4A.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/2a9faff195fe333526cfe6ae6fce1420-927E9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/2a9faff195fe333526cfe6ae6fce1420-927E9.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/423a533afdf07948f23af9a1ebceec37-235E9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/423a533afdf07948f23af9a1ebceec37-235E9.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/5-C387C: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/5-C387C -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/5-E9BDB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/5-E9BDB.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/568d22b97293cdd2d9b7006198d6adbc-9738F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/568d22b97293cdd2d9b7006198d6adbc-9738F.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/5960c5502dd09d4875482231f43edbc5-F05FC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/5960c5502dd09d4875482231f43edbc5-F05FC.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/67594ee4b4d1fc03bca468327a0d145b-0C614.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/67594ee4b4d1fc03bca468327a0d145b-0C614.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/781415a7ef0ff20e2f8e85348fbd64c1-5F911.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/781415a7ef0ff20e2f8e85348fbd64c1-5F911.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/7fcd80e4d3c60a600235b03c75f682a9-75033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/7fcd80e4d3c60a600235b03c75f682a9-75033.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/83ffc16bfc2260f3df5a5a3ce44e59ab2c5de5e3_2-4FEEE.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/83ffc16bfc2260f3df5a5a3ce44e59ab2c5de5e3_2-4FEEE.jpeg -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/851893157188599838-C23B5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/851893157188599838-C23B5.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/851893826846457866-1AD3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/851893826846457866-1AD3D.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/851893827089727568-5FD38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/851893827089727568-5FD38.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/851893827315826708-F59C0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/851893827315826708-F59C0.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/910676187288846397-518CD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/910676187288846397-518CD.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/970d2e2f00cd7ef2134a1a3f21326349-B9EEF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/970d2e2f00cd7ef2134a1a3f21326349-B9EEF.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/C4-banner-7C19B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/C4-banner-7C19B.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/FSB_4THVcAAXrSe-D0ED9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/FSB_4THVcAAXrSe-D0ED9.jpg -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/Screenshot_20220523_113543_com.android.chr-1C166.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/Screenshot_20220523_113543_com.android.chr-1C166.jpg -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/TKrF4ZUY_400x400-5F957.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/TKrF4ZUY_400x400-5F957.jpg -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/a3543456a09e49cb364c91b15541774a-203F3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/a3543456a09e49cb364c91b15541774a-203F3.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ae70f07a06c1c7e983291bb14a1bed7f-B8F4A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ae70f07a06c1c7e983291bb14a1bed7f-B8F4A.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/apple-touch-icon-192x192-E344C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/apple-touch-icon-192x192-E344C.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/b07292365785e0b7bd0dab7dd7d9e09f-62888.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/b07292365785e0b7bd0dab7dd7d9e09f-62888.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/c9cb30134c634c9e02d0c64df4922803-1AAF2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/c9cb30134c634c9e02d0c64df4922803-1AAF2.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/eecc78c5-e4c8-4746-b464-3f4a2332c79b-1832D: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/eecc78c5-e4c8-4746-b464-3f4a2332c79b-1832D -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/fab1b54617157de0fdc01d6d30bc68b4-901C1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/fab1b54617157de0fdc01d6d30bc68b4-901C1.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-italic-400-E988B.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-italic-400-E988B.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-italic-500-0777F.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-italic-500-0777F.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-italic-600-CB411.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-italic-600-CB411.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-italic-700-891AC.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-italic-700-891AC.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-italic-800-D36B0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-italic-800-D36B0.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-normal-400-1456D.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-normal-400-1456D.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-normal-500-89CE5.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-normal-500-89CE5.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-normal-600-C1EA8.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-normal-600-C1EA8.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-normal-700-1949A.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-normal-700-1949A.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-normal-800-58487.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/ggsans-normal-800-58487.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/solarized-dark.min-BA98F.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:.5em;background:#002b36;color:#839496}.hljs-comment,.hljs-quote{color:#586e75}.hljs-keyword,.hljs-selector-tag,.hljs-addition{color:#859900}.hljs-number,.hljs-string,.hljs-meta .hljs-meta-string,.hljs-literal,.hljs-doctag,.hljs-regexp{color:#2aa198}.hljs-title,.hljs-section,.hljs-name,.hljs-selector-id,.hljs-selector-class{color:#268bd2}.hljs-attribute,.hljs-attr,.hljs-variable,.hljs-template-variable,.hljs-class .hljs-title,.hljs-type{color:#b58900}.hljs-symbol,.hljs-bullet,.hljs-subst,.hljs-meta,.hljs-meta .hljs-keyword,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-link{color:#cb4b16}.hljs-built_in,.hljs-deletion{color:#dc322f}.hljs-formula{background:#073642}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/unknown-D6C62.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].html_Files/unknown-D6C62.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/5-C387C: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/5-C387C -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/83ffc16bfc2260f3df5a5a3ce44e59ab2c5de5e3_2-4FEEE.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/83ffc16bfc2260f3df5a5a3ce44e59ab2c5de5e3_2-4FEEE.jpeg -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/C4-banner-7C19B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/C4-banner-7C19B.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/FSB_4THVcAAXrSe-D0ED9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/FSB_4THVcAAXrSe-D0ED9.jpg -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/Screenshot_20220523_113543_com.android.chr-1C166.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/Screenshot_20220523_113543_com.android.chr-1C166.jpg -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/eecc78c5-e4c8-4746-b464-3f4a2332c79b-1832D: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/eecc78c5-e4c8-4746-b464-3f4a2332c79b-1832D -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/unknown-D6C62.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-05-aura/e64e72526db40f6a1b5c7ebe36e17cbb71dc9b64/discord-export/Code4rena - ARCHIVE-Q2-2022 - ☑aura-may11 [973316471793025054].txt_Files/unknown-D6C62.png -------------------------------------------------------------------------------- /hardhat-cvx.config.ts: -------------------------------------------------------------------------------- 1 | import hardhatConfig from "./hardhat.config"; 2 | 3 | export default { 4 | ...hardhatConfig, 5 | defaultNetwork: "hardhat", 6 | gasReporter: { 7 | ...hardhatConfig.gasReporter, 8 | src: "./contracts", 9 | }, 10 | paths: { 11 | ...hardhatConfig.paths, 12 | sources: "./convex-platform/contracts/contracts", 13 | }, 14 | solidity: { 15 | version: "0.6.12", 16 | settings: { 17 | optimizer: { 18 | enabled: true, 19 | runs: 200, 20 | }, 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /hardhat-fork.config.ts: -------------------------------------------------------------------------------- 1 | import hardhatConfig from "./hardhat.config"; 2 | 3 | export default { 4 | ...hardhatConfig, 5 | networks: { 6 | ...hardhatConfig.networks, 7 | hardhat: { 8 | allowUnlimitedContractSize: false, 9 | forking: { 10 | url: process.env.NODE_URL || "", 11 | }, 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomiclabs/hardhat-ethers"; 2 | import "@nomiclabs/hardhat-waffle"; 3 | import "@typechain/hardhat"; 4 | import "hardhat-gas-reporter"; 5 | import "solidity-coverage"; 6 | import "@nomiclabs/hardhat-etherscan"; 7 | 8 | import { resolve } from "path"; 9 | 10 | import { config as dotenvConfig } from "dotenv"; 11 | import { HardhatUserConfig } from "hardhat/config"; 12 | 13 | dotenvConfig({ path: resolve(__dirname, "./.env") }); 14 | 15 | const chainIds = { 16 | goerli: 5, 17 | hardhat: 31337, 18 | kovan: 42, 19 | mainnet: 1, 20 | rinkeby: 4, 21 | ropsten: 3, 22 | }; 23 | 24 | const compilerSettings = { 25 | metadata: { 26 | // Not including the metadata hash 27 | // https://github.com/paulrberg/solidity-template/issues/31 28 | bytecodeHash: "none", 29 | }, 30 | // Disable the optimizer when debugging 31 | // https://hardhat.org/hardhat-network/#solidity-optimizer-support 32 | optimizer: { 33 | enabled: true, 34 | runs: 800, 35 | }, 36 | }; 37 | 38 | const config: HardhatUserConfig = { 39 | defaultNetwork: "hardhat", 40 | gasReporter: { 41 | currency: "USD", 42 | enabled: true, 43 | excludeContracts: [], 44 | src: "./contracts", 45 | }, 46 | networks: { 47 | hardhat: { 48 | chainId: chainIds.hardhat, 49 | allowUnlimitedContractSize: true, 50 | }, 51 | mainnet: { 52 | url: process.env.NODE_URL || "", 53 | }, 54 | kovan: { 55 | url: process.env.NODE_URL || "", 56 | gasPrice: 3000000000, 57 | }, 58 | forking: { 59 | url: process.env.NODE_URL || "", 60 | }, 61 | rinkeby: { url: process.env.NODE_URL || "", gasPrice: 3000000000 }, 62 | }, 63 | paths: { 64 | artifacts: "./artifacts", 65 | cache: "./cache", 66 | sources: "./contracts", 67 | tests: "./test", 68 | }, 69 | solidity: { 70 | compilers: [ 71 | { 72 | version: "0.6.12", 73 | settings: compilerSettings, 74 | }, 75 | { 76 | version: "0.8.11", 77 | settings: compilerSettings, 78 | }, 79 | ], 80 | }, 81 | typechain: { 82 | outDir: "types/generated", 83 | target: "ethers-v5", 84 | }, 85 | etherscan: { 86 | apiKey: process.env.ETHERSCAN_KEY, 87 | }, 88 | }; 89 | 90 | export default config; 91 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | // Export functions from the package/module 2 | export * from "./test-utils"; 3 | 4 | // Export types 5 | export * from "./types"; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aura-contracts-lite", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "compile": "yarn compile:cvx && yarn compile:aura && yarn run compile:ts", 7 | "compile:aura": "yarn hardhat compile --config hardhat.config.ts", 8 | "compile:cvx": "yarn hardhat compile --config hardhat-cvx.config.ts", 9 | "compile:ts": "npx tsc", 10 | "lint": "yarn lint:sol && yarn lint:ts && yarn prettier:check", 11 | "lint:sol": "solhint --config ./.solhint.json \"contracts/**/*.sol\"", 12 | "lint:ts": "eslint --config ./.eslintrc.yaml --ignore-path ./.eslintignore --ext .js,.ts .", 13 | "prettier": "prettier --config ./.prettierrc.yaml --write \"**/*.{js,json,md,sol,ts}\"", 14 | "prettier:check": "prettier --check --config ./.prettierrc.yaml \"**/*.{js,json,md,sol,ts}\"", 15 | "test": "hardhat test", 16 | "test:fork": "yarn hardhat --config hardhat-fork.config.ts test", 17 | "test:fork:all": "yarn hardhat --config hardhat-fork.config.ts test ./test-fork/*.spec.ts", 18 | "typechain": "yarn hardhat typechain" 19 | }, 20 | "author": "", 21 | "license": "ISC", 22 | "devDependencies": { 23 | "@balancer-labs/balancer-js": "^1.0.1", 24 | "@codechecks/client": "^0.1.12", 25 | "@commitlint/cli": "^14.1.0", 26 | "@commitlint/config-conventional": "^14.1.0", 27 | "@ethersproject/abi": "5.5.0", 28 | "@ethersproject/abstract-signer": "5.5.0", 29 | "@ethersproject/bignumber": "5.5.0", 30 | "@ethersproject/bytes": "5.5.0", 31 | "@ethersproject/hash": "5.5.0", 32 | "@ethersproject/providers": "5.5.0", 33 | "@nomiclabs/hardhat-ethers": "2.0.5", 34 | "@nomiclabs/hardhat-etherscan": "^3.0.3", 35 | "@nomiclabs/hardhat-waffle": "^2.0.1", 36 | "@openzeppelin/contracts-0.6": "npm:@openzeppelin/contracts@3.4.0", 37 | "@openzeppelin/contracts-0.8": "npm:@openzeppelin/contracts@4.4.2", 38 | "@snapshot-labs/snapshot.js": "^0.3.53", 39 | "@typechain/ethers-v5": "^8.0.5", 40 | "@typechain/hardhat": "^3.0.0", 41 | "@types/chai": "^4.2.22", 42 | "@types/fs-extra": "^9.0.13", 43 | "@types/mocha": "^9.0.0", 44 | "@types/node": "^16.11.10", 45 | "@typescript-eslint/eslint-plugin": "^5.4.0", 46 | "@typescript-eslint/parser": "^5.4.0", 47 | "chai": "^4.3.4", 48 | "commitizen": "^4.2.4", 49 | "cross-env": "^7.0.3", 50 | "cz-conventional-changelog": "^3.3.0", 51 | "diff2html": "^3.4.16", 52 | "diffchecker": "^0.2.8", 53 | "dotenv": "^10.0.0", 54 | "eslint": "^8.3.0", 55 | "eslint-config-prettier": "^8.3.0", 56 | "ethereum-waffle": "^3.4.0", 57 | "ethers": "5.5.4", 58 | "fs-extra": "^10.0.0", 59 | "hardhat": "2.9.2", 60 | "hardhat-gas-reporter": "^1.0.4", 61 | "husky": "^7.0.4", 62 | "koa": "^2.13.4", 63 | "lint-staged": "^11.2.6", 64 | "merkletreejs": "^0.2.31", 65 | "mocha": "^9.1.3", 66 | "node-jq": "^2.3.3", 67 | "pinst": "^2.1.6", 68 | "prettier": "^2.6.2", 69 | "prettier-plugin-solidity": "^1.0.0-beta.19", 70 | "shelljs": "^0.8.4", 71 | "shx": "^0.3.3", 72 | "solhint": "^3.3.7", 73 | "solhint-plugin-prettier": "^0.0.5", 74 | "solidity-coverage": "^0.7.17", 75 | "ts-generator": "^0.1.1", 76 | "ts-node": "^10.4.0", 77 | "typechain": "^6.0.5", 78 | "typescript": "^4.5.2" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tasks/deploy/index.ts: -------------------------------------------------------------------------------- 1 | import "./mainnet-config"; 2 | -------------------------------------------------------------------------------- /tasks/index.ts: -------------------------------------------------------------------------------- 1 | import "./deploy"; 2 | -------------------------------------------------------------------------------- /tasks/utils/deploy-utils.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import { Contract, ContractFactory, ContractReceipt, ContractTransaction, Overrides } from "ethers"; 3 | import { formatUnits } from "@ethersproject/units"; 4 | import { ExtSystemConfig } from "../../scripts/deploySystem"; 5 | 6 | export const deployContract = async ( 7 | hre: HardhatRuntimeEnvironment, 8 | contractFactory: ContractFactory, 9 | contractName = "Contract", 10 | constructorArgs: Array = [], 11 | overrides: Overrides = {}, 12 | debug = true, 13 | waitForBlocks = undefined, 14 | ): Promise => { 15 | const contract = (await contractFactory.deploy(...constructorArgs, overrides)) as T; 16 | if (debug) { 17 | console.log( 18 | `\nDeploying ${contractName} contract with hash ${contract.deployTransaction.hash} from ${ 19 | contract.deployTransaction.from 20 | } with gas price ${contract.deployTransaction.gasPrice?.toNumber() || 0 / 1e9} Gwei`, 21 | ); 22 | } 23 | const receipt = await contract.deployTransaction.wait(waitForBlocks); 24 | const txCost = receipt.gasUsed.mul(contract.deployTransaction.gasPrice || 0); 25 | const abiEncodedConstructorArgs = contract.interface.encodeDeploy(constructorArgs); 26 | 27 | if (debug) { 28 | console.log( 29 | `\nDeployed ${contractName} to ${contract.address} in block ${receipt.blockNumber}, using ${ 30 | receipt.gasUsed 31 | } gas costing ${formatUnits(txCost)} ETH`, 32 | ); 33 | console.log(`ABI encoded args: ${abiEncodedConstructorArgs.slice(2)}`); 34 | } 35 | 36 | // await verifyEtherscan(hre, { 37 | // address: contract.address, 38 | // constructorArguments: constructorArgs, 39 | // }); 40 | 41 | return contract; 42 | }; 43 | 44 | export const logTxDetails = async (tx: ContractTransaction, method: string): Promise => { 45 | console.log( 46 | `Sent ${method} transaction with hash ${tx.hash} from ${tx.from} with gas price ${ 47 | tx.gasPrice?.toNumber() || 0 / 1e9 48 | } Gwei`, 49 | ); 50 | const receipt = await tx.wait(); 51 | 52 | // Calculate tx cost in Wei 53 | const txCost = receipt.gasUsed.mul(tx.gasPrice ?? 0); 54 | console.log( 55 | `Processed ${method} tx in block ${receipt.blockNumber}, using ${receipt.gasUsed} gas costing ${formatUnits( 56 | txCost, 57 | )} ETH`, 58 | ); 59 | 60 | return receipt; 61 | }; 62 | 63 | export function logExtSystem(system: ExtSystemConfig) { 64 | const keys = Object.keys(system); 65 | console.log(`\n~~~~~~~~~~~~~~~~~~~~~~~~~~~`); 66 | console.log(`~~~~~~~ EXT SYSTEM ~~~~~~~`); 67 | console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~\n`); 68 | keys.map(k => { 69 | console.log(`${k}:\t${system[k]}`); 70 | }); 71 | } 72 | 73 | export function logContracts(contracts: { [key: string]: { address: string } }) { 74 | const keys = Object.keys(contracts); 75 | console.log(`\n~~~~~~~~~~~~~~~~~~~~~~~~~~~`); 76 | console.log(`~~~~ SYSTEM DEPLOYMENT ~~~~`); 77 | console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~\n`); 78 | keys.map(k => { 79 | if (Array.isArray(contracts[k])) { 80 | console.log(`${k}:\t[${(contracts[k] as any as [{ address: string }]).map(i => i.address)}]`); 81 | } else { 82 | console.log(`${k}:\t${contracts[k].address}`); 83 | } 84 | }); 85 | } 86 | 87 | export async function waitForTx( 88 | tx: ContractTransaction, 89 | debug = false, 90 | waitForBlocks = undefined, 91 | ): Promise { 92 | const receipt = await tx.wait(waitForBlocks); 93 | if (debug) { 94 | console.log(`\nTRANSACTION: ${receipt.transactionHash}`); 95 | console.log(`to:: ${tx.to}`); 96 | console.log(`txData:: ${tx.data}`); 97 | } 98 | return receipt; 99 | } 100 | -------------------------------------------------------------------------------- /tasks/utils/etherscan.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | 3 | interface VerifyEtherscan { 4 | address: string; 5 | contract?: string; 6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types 7 | constructorArguments?: any[]; 8 | libraries?: { 9 | [libraryName: string]: string; 10 | }; 11 | } 12 | 13 | export const verifyEtherscan = async (hre: HardhatRuntimeEnvironment, contract: VerifyEtherscan): Promise => { 14 | if ( 15 | hre.network.name == "mainnet" && 16 | !["tasks-fork.config.ts", "hardhat-fork.config.ts"].includes(hre.hardhatArguments.config) 17 | ) { 18 | console.log(`About to verify ${contract.address} on Etherscan`); 19 | await hre.run("verify:verify", contract); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /tasks/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./signerFactory"; 2 | export * from "./deploy-utils"; 3 | export * from "./tokens"; 4 | export * from "./etherscan"; 5 | -------------------------------------------------------------------------------- /tasks/utils/signerFactory.ts: -------------------------------------------------------------------------------- 1 | import { Signer, Wallet } from "ethers"; 2 | import { ethereumAddress, privateKey } from "../../test-utils/regex"; 3 | import { impersonate } from "../../test-utils/fork"; 4 | import { Account } from "types"; 5 | import { getChain, getChainAddress, HardhatRuntime, resolveAddress } from "./networkAddressFactory"; 6 | 7 | let signerInstance: Signer; 8 | 9 | export const getSigner = async (hre: HardhatRuntime = {}, useCache = true, key?: string): Promise => { 10 | // If already initiated a signer, just return the singleton instance 11 | if (useCache && signerInstance) return signerInstance; 12 | 13 | const pk = key || process.env.PRIVATE_KEY; 14 | if (pk) { 15 | if (!pk.match(privateKey)) { 16 | throw Error(`Invalid format of private key`); 17 | } 18 | const wallet = new Wallet(pk, hre.ethers.provider); 19 | console.log(`Using signer ${await wallet.getAddress()} from private key`); 20 | return wallet; 21 | } 22 | 23 | // If connecting to a forked chain 24 | if (["tasks-fork.config.ts", "hardhat-fork.config.ts"].includes(hre?.hardhatArguments.config)) { 25 | const chain = getChain(hre); 26 | // If IMPERSONATE environment variable has been set 27 | if (process.env.IMPERSONATE) { 28 | let address = process.env.IMPERSONATE; 29 | if (!address.match(ethereumAddress)) { 30 | address = resolveAddress(process.env.IMPERSONATE, chain); 31 | if (!address) 32 | throw Error(`Environment variable IMPERSONATE is an invalid Ethereum address or contract name`); 33 | } 34 | console.log(`Impersonating account ${address} from IMPERSONATE environment variable`); 35 | signerInstance = await impersonate(address); 36 | return signerInstance; 37 | } 38 | const address = getChainAddress("Deployer", chain); 39 | if (address) { 40 | console.log(`Impersonating account ${address} resolved from "Deployer"`); 41 | signerInstance = await impersonate(address); 42 | return signerInstance; 43 | } 44 | // Return a random account with no Ether 45 | signerInstance = Wallet.createRandom().connect(hre.ethers.provider); 46 | console.log(`Impersonating random account ${await signerInstance.getAddress()}`); 47 | return signerInstance; 48 | } 49 | 50 | // Return a random account with no Ether. 51 | // This is typically used for readonly tasks. eg reports 52 | signerInstance = Wallet.createRandom().connect(hre.ethers.provider); 53 | return signerInstance; 54 | }; 55 | 56 | export const getSignerAccount = async (hre: HardhatRuntime = {}): Promise => { 57 | const signer = await getSigner(hre); 58 | return { 59 | signer, 60 | address: await signer.getAddress(), 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /tasks/utils/tokens.ts: -------------------------------------------------------------------------------- 1 | export enum Chain { 2 | mainnet, 3 | rinkeby, 4 | kovan, 5 | local, 6 | } 7 | 8 | export interface Token { 9 | symbol: string; 10 | address: string; 11 | chain: Chain; 12 | decimals: number; 13 | } 14 | 15 | export const assetAddressTypes = ["address"] as const; 16 | export type AssetAddressTypes = typeof assetAddressTypes[number]; 17 | 18 | export const tokens: Array = []; 19 | -------------------------------------------------------------------------------- /test-fork/BalEthSwap.spec.ts: -------------------------------------------------------------------------------- 1 | import { simpleToExactAmount } from "../test-utils/math"; 2 | import hre, { ethers, network } from "hardhat"; 3 | import { expect } from "chai"; 4 | import { MockBalInvestor, MockBalInvestor__factory, ERC20__factory, ERC20 } from "../types/generated"; 5 | import { deployContract } from "../tasks/utils"; 6 | import { impersonateAccount, fullScale } from "../test-utils"; 7 | import { Signer } from "ethers"; 8 | 9 | const debug = false; 10 | 11 | const ALCHEMY_API_KEY = process.env.NODE_URL; 12 | 13 | const BALWhale = "0xff052381092420b7f24cc97fded9c0c17b2cbbb9"; 14 | 15 | describe("TestBalEth", () => { 16 | let testEthBal: MockBalInvestor; 17 | let balToken: ERC20; 18 | let signer: Signer; 19 | 20 | const amount = ethers.utils.parseEther("100"); 21 | 22 | before(async () => { 23 | await network.provider.request({ 24 | method: "hardhat_reset", 25 | params: [ 26 | { 27 | forking: { 28 | jsonRpcUrl: ALCHEMY_API_KEY, 29 | blockNumber: 14370000, 30 | }, 31 | }, 32 | ], 33 | }); 34 | 35 | await impersonateAccount(BALWhale); 36 | 37 | signer = await ethers.getSigner(BALWhale); 38 | 39 | const poolId = "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014"; 40 | const vault = "0xba12222222228d8ba445958a75a0704d566bf2c8"; 41 | const weth = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 42 | const bal = "0xba100000625a3754423978a60c9317c58a424e3D"; 43 | 44 | balToken = ERC20__factory.connect(bal, signer); 45 | 46 | testEthBal = await deployContract( 47 | hre, 48 | new MockBalInvestor__factory(signer), 49 | "testEthBal", 50 | [vault, bal, weth, poolId], 51 | {}, 52 | debug, 53 | ); 54 | }); 55 | 56 | describe("join BAL:ETH 80/20 pool with BAL", () => { 57 | it("transfer BAL to contract", async () => { 58 | const tx = await balToken.approve(testEthBal.address, amount); 59 | await tx.wait(); 60 | }); 61 | 62 | it("add BAL to pool", async () => { 63 | const bptAddress = await testEthBal.BALANCER_POOL_TOKEN(); 64 | const bpt = ERC20__factory.connect(bptAddress, signer); 65 | 66 | const bptBalanceBefore = await bpt.balanceOf(testEthBal.address); 67 | 68 | let tx = await testEthBal.approveToken(); 69 | await tx.wait(); 70 | 71 | const minOut = await testEthBal.getMinOut(amount, 9980); 72 | tx = await testEthBal.addBalToPool(amount.toString(), minOut); 73 | await tx.wait(); 74 | 75 | const bptBalanceAfter = await bpt.balanceOf(testEthBal.address); 76 | const bptBalanceDelta = bptBalanceAfter.sub(bptBalanceBefore); 77 | 78 | const bptPrice = await testEthBal.getBptPrice(); 79 | 80 | const bptBalValue = bptPrice.mul(bptBalanceDelta).div(fullScale); 81 | const minAmount = amount.mul("9950").div("10000"); 82 | expect(bptBalValue).gt(minAmount); 83 | }); 84 | 85 | it("fails if incorrect minout passed", async () => { 86 | const tx = await balToken.approve(testEthBal.address, amount); 87 | await tx.wait(); 88 | 89 | let minOut = await testEthBal.getMinOut(amount, 10005); 90 | 91 | await expect(testEthBal.addBalToPool(amount.toString(), minOut)).to.be.revertedWith("BAL#208"); 92 | 93 | minOut = await testEthBal.getMinOut(amount, 9980); 94 | 95 | await testEthBal.addBalToPool(amount.toString(), minOut); 96 | }); 97 | 98 | it("fails if slippage not met (large deposit)", async () => { 99 | const tx = await balToken.approve(testEthBal.address, simpleToExactAmount(1, 24)); 100 | await tx.wait(); 101 | 102 | const minOut = await testEthBal.getMinOut(simpleToExactAmount(1, 24), 9980); 103 | 104 | await expect(testEthBal.addBalToPool(simpleToExactAmount(1, 24), minOut)).to.be.revertedWith("BAL#208"); 105 | }); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /test-utils/assertions.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { BN, simpleToExactAmount } from "./math"; 3 | import { fullScale } from "./constants"; 4 | 5 | /** 6 | * Convenience method to assert that two BN.js instances are within 100 units of each other. 7 | * @param actual The BN.js instance you received 8 | * @param expected The BN.js amount you expected to receive, allowing a varience of +/- 10 units 9 | */ 10 | export const assertBNClose = ( 11 | actual: BN | string, 12 | expected: BN, 13 | variance: BN | number = BN.from(10), 14 | reason: string = null, 15 | ): void => { 16 | const actualBN = BN.from(actual); 17 | const actualDelta = actualBN.gt(expected) ? actualBN.sub(expected) : expected.sub(actualBN); 18 | 19 | const str = reason ? `\n\tReason: ${reason}\n\t${actualBN.toString()} vs ${expected.toString()}` : ""; 20 | assert.ok( 21 | actualBN.gte(expected.sub(variance)), 22 | `Number is too small to be close (Delta between actual and expected is ${actualDelta.toString()}, but variance was only ${variance.toString()}${str}`, 23 | ); 24 | assert.ok( 25 | actualBN.lte(expected.add(variance)), 26 | `Number is too large to be close (Delta between actual and expected is ${actualDelta.toString()}, but variance was only ${variance.toString()})${str}`, 27 | ); 28 | }; 29 | 30 | /** 31 | * Convenience method to assert that two BN.js instances are within 100 units of each other. 32 | * @param actual The BN.js instance you received 33 | * @param expected The BN.js amount you expected to receive, allowing a varience of +/- 10 units 34 | */ 35 | export const assertBNClosePercent = (a: BN, b: BN, variance: string | number = "0.02", reason: string = null): void => { 36 | if (a.eq(b)) return; 37 | const varianceBN = simpleToExactAmount(variance.toString().substr(0, 6), 16); 38 | const diff = a.sub(b).abs().mul(2).mul(fullScale).div(a.add(b)); 39 | const str = reason ? `\n\tReason: ${reason}\n\t${a.toString()} vs ${b.toString()}` : ""; 40 | assert.ok( 41 | diff.lte(varianceBN), 42 | `Numbers exceed ${variance}% diff (Delta between a and b is ${diff.toString()}%, but variance was only ${varianceBN.toString()})${str}`, 43 | ); 44 | }; 45 | 46 | /** 47 | * Convenience method to assert that one BN.js instance is GTE the other 48 | * @param actual The BN.js instance you received 49 | * @param expected The operant to compare against 50 | */ 51 | export const assertBnGte = (actual: BN, comparison: BN): void => { 52 | assert.ok( 53 | actual.gte(comparison), 54 | `Number must be GTE comparitor, got: ${actual.toString()}; comparitor: ${comparison.toString()}`, 55 | ); 56 | }; 57 | 58 | /** 59 | * Convenience method to assert that one BN.js number is eq to, or greater than an expected value by some small amount 60 | * @param actual The BN.js instance you received 61 | * @param equator The BN.js to equate to 62 | * @param maxActualShouldExceedExpected Upper limit for the growth 63 | * @param mustBeGreater Fail if the operands are equal 64 | */ 65 | export const assertBNSlightlyGT = ( 66 | actual: BN, 67 | equator: BN, 68 | maxActualShouldExceedExpected = BN.from(100), 69 | mustBeGreater = false, 70 | reason: string = null, 71 | ): void => { 72 | const actualDelta = actual.gt(equator) ? actual.sub(equator) : equator.sub(actual); 73 | 74 | const str = reason ? `\n\t${reason}\n\t${actual.toString()} vs ${equator.toString()}` : ""; 75 | 76 | assert.ok( 77 | mustBeGreater ? actual.gt(equator) : actual.gte(equator), 78 | `Actual value should be greater than the expected value ${str}`, 79 | ); 80 | assert.ok( 81 | actual.lte(equator.add(maxActualShouldExceedExpected)), 82 | `Actual value should not exceed ${maxActualShouldExceedExpected.toString()} units greater than expected. Variance was ${actualDelta.toString()} ${str}`, 83 | ); 84 | }; 85 | 86 | /** 87 | * Convenience method to assert that one BN.js number is eq to, or greater than an expected value by some small amount 88 | * @param actual The BN.js instance you received 89 | * @param equator The BN.js to equate to 90 | * @param maxActualShouldExceedExpected Percentage amount of increase, as a string (1% = 1) 91 | * @param mustBeGreater Fail if the operands are equal 92 | */ 93 | export const assertBNSlightlyGTPercent = ( 94 | actual: BN, 95 | equator: BN, 96 | maxPercentIncrease = "0.1", 97 | mustBeGreater = false, 98 | ): void => { 99 | const maxIncreaseBN = simpleToExactAmount(maxPercentIncrease, 16); 100 | const maxIncreaseUnits = equator.mul(maxIncreaseBN).div(fullScale); 101 | // const actualDelta = actual.gt(equator) ? actual.sub(equator) : equator.sub(actual); 102 | 103 | assert.ok( 104 | mustBeGreater ? actual.gt(equator) : actual.gte(equator), 105 | `Actual value should be greater than the expected value`, 106 | ); 107 | assert.ok( 108 | actual.lte(equator.add(maxIncreaseUnits)), 109 | `Actual value should not exceed ${maxPercentIncrease}% greater than expected`, 110 | ); 111 | }; 112 | -------------------------------------------------------------------------------- /test-utils/constants.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | import { BigNumber as BN } from "ethers"; 3 | 4 | export const fullScale: BN = BN.from(10).pow(18); 5 | 6 | export const DEFAULT_DECIMALS = 18; 7 | 8 | export const DEAD_ADDRESS = "0x0000000000000000000000000000000000000001"; 9 | export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; 10 | export const ZERO_KEY = "0x0000000000000000000000000000000000000000000000000000000000000000"; 11 | 12 | export const MAX_UINT256 = BN.from(2).pow(256).sub(1); 13 | export const MAX_INT128 = BN.from(2).pow(127).sub(1); 14 | export const MIN_INT128 = BN.from(2).pow(127).mul(-1); 15 | 16 | export const ZERO = BN.from(0); 17 | export const ONE_MIN = BN.from(60); 18 | export const TEN_MINS = BN.from(60 * 10); 19 | export const ONE_HOUR = BN.from(60 * 60); 20 | export const ONE_DAY = BN.from(60 * 60 * 24); 21 | export const FIVE_DAYS = BN.from(60 * 60 * 24 * 5); 22 | export const TEN_DAYS = BN.from(60 * 60 * 24 * 10); 23 | export const ONE_WEEK = BN.from(60 * 60 * 24 * 7); 24 | export const ONE_YEAR = BN.from(60 * 60 * 24 * 365); 25 | -------------------------------------------------------------------------------- /test-utils/fork.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from "ethers"; 2 | import { Account } from "types"; 3 | 4 | // impersonates a specific account 5 | export const impersonate = async (addr: string, fund = true): Promise => { 6 | // Dynamic import hardhat module to avoid importing while hardhat config is being defined. 7 | // The error this avoids is: 8 | // Error HH9: Error while loading Hardhat's configuration. 9 | // You probably tried to import the "hardhat" module from your config or a file imported from it. 10 | // This is not possible, as Hardhat can't be initialized while its config is being defined. 11 | const { network, ethers } = await import("hardhat"); 12 | await network.provider.request({ 13 | method: "hardhat_impersonateAccount", 14 | params: [addr], 15 | }); 16 | if (fund) { 17 | // Give the account 10 Ether 18 | await network.provider.request({ 19 | method: "hardhat_setBalance", 20 | params: [addr, "0x8AC7230489E80000"], 21 | }); 22 | } 23 | return ethers.provider.getSigner(addr); 24 | }; 25 | 26 | export const impersonateAccount = async (address: string, fund = true): Promise => { 27 | const signer = await impersonate(address, fund); 28 | return { 29 | signer, 30 | address, 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /test-utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./assertions"; 2 | export * from "./constants"; 3 | export * from "./fork"; 4 | export * from "./math"; 5 | export * from "./time"; 6 | export * from "./regex"; 7 | export * from "./merkle"; 8 | -------------------------------------------------------------------------------- /test-utils/math.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber as BN } from "ethers"; 2 | import { DEFAULT_DECIMALS } from "./constants"; 3 | 4 | export { BN }; 5 | 6 | // Converts an unscaled number to scaled number with the specified number of decimals 7 | // eg convert 3 to 3000000000000000000 with 18 decimals 8 | export const simpleToExactAmount = (amount: number | string | BN, decimals: number | BN = DEFAULT_DECIMALS): BN => { 9 | // Code is largely lifted from the guts of web3 toWei here: 10 | // https://github.com/ethjs/ethjs-unit/blob/master/src/index.js 11 | let amountString = amount.toString(); 12 | const decimalsBN = BN.from(decimals); 13 | 14 | if (decimalsBN.gt(100)) { 15 | throw new Error(`Invalid decimals amount`); 16 | } 17 | 18 | const scale = BN.from(10).pow(decimals); 19 | const scaleString = scale.toString(); 20 | 21 | // Is it negative? 22 | const negative = amountString.substring(0, 1) === "-"; 23 | if (negative) { 24 | amountString = amountString.substring(1); 25 | } 26 | 27 | if (amountString === ".") { 28 | throw new Error(`Error converting number ${amountString} to precise unit, invalid value`); 29 | } 30 | 31 | // Split it into a whole and fractional part 32 | // eslint-disable-next-line prefer-const 33 | let [whole, fraction, ...rest] = amountString.split("."); 34 | if (rest.length > 0) { 35 | throw new Error(`Error converting number ${amountString} to precise unit, too many decimal points`); 36 | } 37 | 38 | if (!whole) { 39 | whole = "0"; 40 | } 41 | if (!fraction) { 42 | fraction = "0"; 43 | } 44 | 45 | if (fraction.length > scaleString.length - 1) { 46 | throw new Error(`Error converting number ${amountString} to precise unit, too many decimal places`); 47 | } 48 | 49 | while (fraction.length < scaleString.length - 1) { 50 | fraction += "0"; 51 | } 52 | 53 | const wholeBN = BN.from(whole); 54 | const fractionBN = BN.from(fraction); 55 | let result = wholeBN.mul(scale).add(fractionBN); 56 | 57 | if (negative) { 58 | result = result.mul("-1"); 59 | } 60 | 61 | return result; 62 | }; 63 | 64 | export const percentToWeight = (percent: number | string | BN): BN => simpleToExactAmount(percent, 16); 65 | 66 | // Returns the smaller number 67 | export const minimum = (a: BN, b: BN): BN => (a.lte(b) ? a : b); 68 | 69 | // Returns the bigger number 70 | export const maximum = (a: BN, b: BN): BN => (a.gte(b) ? a : b); 71 | 72 | // Returns the square root of a big number, solution taken from https://github.com/ethers-io/ethers.js/issues/1182#issuecomment-744142921 73 | export const sqrt = (value: BN | number): BN => { 74 | const x = BN.from(value); 75 | let z = x.add(1).div(2); 76 | let y = x; 77 | while (z.sub(y).isNegative()) { 78 | y = z; 79 | z = x.div(z).add(z).div(2); 80 | } 81 | return y; 82 | }; 83 | 84 | export const sum = (a: BN, b: BN): BN => a.add(b); 85 | -------------------------------------------------------------------------------- /test-utils/merkle.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | import { MerkleTree } from "merkletreejs"; 3 | import { keccak256, solidityKeccak256 } from "ethers/lib/utils"; 4 | 5 | const hashFn = (data: string) => keccak256(data).slice(2); 6 | 7 | export const createTreeWithAccounts = (accounts: Record): MerkleTree => { 8 | const elements = Object.entries(accounts).map(([account, balance]) => 9 | solidityKeccak256(["address", "uint256"], [account, balance.toString()]), 10 | ); 11 | return new MerkleTree(elements, hashFn, { sort: true }); 12 | }; 13 | 14 | export const getAccountBalanceProof = (tree: MerkleTree, account: string, balance: BigNumber) => { 15 | const element = solidityKeccak256(["address", "uint256"], [account, balance.toString()]); 16 | return tree.getHexProof(element); 17 | }; 18 | -------------------------------------------------------------------------------- /test-utils/regex.ts: -------------------------------------------------------------------------------- 1 | export const bytes = /^0x([A-Fa-f0-9]{1,})$/; 2 | 3 | export const bytesFixed = (x: number): RegExp => new RegExp(`^0x([A-Fa-f0-9]{${x * 2}})$`); 4 | 5 | export const bytes32 = bytesFixed(32); 6 | export const ethereumAddress = bytesFixed(20); 7 | export const transactionHash = bytes32; 8 | 9 | export const privateKey = /^[A-Fa-f0-9]{1,64}$/; 10 | -------------------------------------------------------------------------------- /test-utils/time.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { Block } from "@ethersproject/abstract-provider"; 3 | import { BigNumberish } from "@ethersproject/bignumber"; 4 | import { BN } from "./math"; 5 | import { ONE_WEEK } from "./constants"; 6 | 7 | export const advanceBlock = async (blocks?: BN | number): Promise => { 8 | if (blocks === undefined) { 9 | await ethers.provider.send("evm_mine", []); 10 | } else { 11 | await ethers.provider.send("hardhat_mine", [ethers.utils.hexlify(blocks)]); 12 | // work around for issue [hardhat_mine produces a failed tx when running in Coverage](https://github.com/NomicFoundation/hardhat/issues/2467) 13 | await ethers.provider.send("hardhat_setNextBlockBaseFeePerGas", ["0x0"]); 14 | } 15 | }; 16 | 17 | export const increaseTime = async (length: BN | number): Promise => { 18 | await ethers.provider.send("evm_increaseTime", [BN.from(length).toNumber()]); 19 | await advanceBlock(); 20 | }; 21 | export const latestBlock = async (): Promise => ethers.provider.getBlock(await ethers.provider.getBlockNumber()); 22 | 23 | export const getTimestamp = async (): Promise => BN.from((await latestBlock()).timestamp); 24 | 25 | export const increaseTimeTo = async (target: BN | number): Promise => { 26 | const now = await getTimestamp(); 27 | const later = BN.from(target); 28 | if (later.lt(now)) 29 | throw Error(`Cannot increase current time (${now.toNumber()}) to a moment in the past (${later.toNumber()})`); 30 | const diff = later.sub(now); 31 | await increaseTime(diff); 32 | await advanceBlock(); 33 | }; 34 | 35 | export const sleep = (ms: number): Promise => new Promise(resolve => setTimeout(resolve, ms)); 36 | 37 | export const startWeek = (epochSeconds: BigNumberish): BN => BN.from(epochSeconds).div(ONE_WEEK).mul(ONE_WEEK); 38 | export const startCurrentWeek = async (): Promise => startWeek(await getTimestamp()); 39 | 40 | export const weekEpoch = (epochSeconds: BigNumberish): BN => BN.from(epochSeconds).div(ONE_WEEK); 41 | export const currentWeekEpoch = async (): Promise => weekEpoch(await getTimestamp()); 42 | -------------------------------------------------------------------------------- /test/AuraClaimZap.spec.ts: -------------------------------------------------------------------------------- 1 | import hre, { ethers } from "hardhat"; 2 | import { Signer } from "ethers"; 3 | import { expect } from "chai"; 4 | import { deployMocks, DeployMocksResult, getMockDistro, getMockMultisigs } from "../scripts/deployMocks"; 5 | import { SystemDeployed, deployPhase1, deployPhase2, deployPhase3, deployPhase4 } from "../scripts/deploySystem"; 6 | import { increaseTime } from "../test-utils/time"; 7 | import { ONE_WEEK } from "../test-utils/constants"; 8 | import { simpleToExactAmount } from "../test-utils/math"; 9 | import { BaseRewardPool__factory } from "../types/generated"; 10 | 11 | describe("AuraClaimZap", () => { 12 | let accounts: Signer[]; 13 | let mocks: DeployMocksResult; 14 | let deployer: Signer; 15 | let contracts: SystemDeployed; 16 | let alice: Signer; 17 | let aliceAddress: string; 18 | 19 | before(async () => { 20 | accounts = await ethers.getSigners(); 21 | 22 | alice = accounts[1]; 23 | aliceAddress = await alice.getAddress(); 24 | 25 | deployer = accounts[0]; 26 | mocks = await deployMocks(hre, deployer); 27 | const multisigs = await getMockMultisigs(accounts[0], accounts[0], accounts[0]); 28 | const distro = getMockDistro(); 29 | const phase1 = await deployPhase1(hre, deployer, mocks.addresses); 30 | const phase2 = await deployPhase2( 31 | hre, 32 | deployer, 33 | phase1, 34 | distro, 35 | multisigs, 36 | mocks.namingConfig, 37 | mocks.addresses, 38 | ); 39 | const phase3 = await deployPhase3(hre, deployer, phase2, multisigs, mocks.addresses); 40 | await phase3.poolManager.setProtectPool(false); 41 | contracts = await deployPhase4(hre, deployer, phase3, mocks.addresses); 42 | 43 | await mocks.crv.transfer(aliceAddress, simpleToExactAmount(1)); 44 | await mocks.crv.transfer(mocks.balancerVault.address, simpleToExactAmount(10)); 45 | await contracts.cvxCrv.transfer(mocks.balancerVault.address, simpleToExactAmount(10)); 46 | 47 | await mocks.balancerVault.setTokens(contracts.cvxCrv.address, mocks.crv.address); 48 | }); 49 | 50 | it("set approval for deposits", async () => { 51 | await contracts.claimZap.setApprovals(); 52 | expect(await mocks.crv.allowance(contracts.claimZap.address, contracts.crvDepositorWrapper.address)).gte( 53 | ethers.constants.MaxUint256, 54 | ); 55 | expect(await contracts.cvxCrv.allowance(contracts.claimZap.address, contracts.cvxCrvRewards.address)).gte( 56 | ethers.constants.MaxUint256, 57 | ); 58 | expect(await contracts.cvx.allowance(contracts.claimZap.address, contracts.cvxLocker.address)).gte( 59 | ethers.constants.MaxUint256, 60 | ); 61 | }); 62 | 63 | it("claim rewards from cvxCrvStaking", async () => { 64 | const lock = true; 65 | const stakeAddress = contracts.cvxCrvRewards.address; 66 | const balance = await mocks.crv.balanceOf(aliceAddress); 67 | 68 | const minOut = await contracts.crvDepositorWrapper.connect(alice).getMinOut(balance, "10000"); 69 | await mocks.crv.connect(alice).approve(contracts.crvDepositorWrapper.address, balance); 70 | await contracts.crvDepositorWrapper.connect(alice).deposit(balance, minOut, lock, stakeAddress); 71 | 72 | const rewardBalance = await contracts.cvxCrvRewards.balanceOf(aliceAddress); 73 | expect(rewardBalance).eq(minOut); 74 | 75 | await contracts.booster.earmarkRewards(0); 76 | 77 | await increaseTime(ONE_WEEK.mul("4")); 78 | 79 | const expectedRewards = await contracts.cvxCrvRewards.earned(aliceAddress); 80 | 81 | await mocks.crv.connect(alice).approve(contracts.claimZap.address, ethers.constants.MaxUint256); 82 | const option = 1 + 16 + 8; 83 | const minBptAmountOut = await contracts.crvDepositorWrapper.getMinOut(expectedRewards, 10000); 84 | await contracts.claimZap 85 | .connect(alice) 86 | .claimRewards([], [], [], [], expectedRewards, minBptAmountOut, 0, option); 87 | 88 | const newRewardBalance = await contracts.cvxCrvRewards.balanceOf(aliceAddress); 89 | expect(newRewardBalance).eq(minBptAmountOut.add(rewardBalance)); 90 | }); 91 | 92 | it("claim from lp staking pool", async () => { 93 | const stake = true; 94 | const amount = ethers.utils.parseEther("10"); 95 | await mocks.lptoken.transfer(aliceAddress, amount); 96 | await mocks.lptoken.connect(alice).approve(contracts.booster.address, amount); 97 | await contracts.booster.connect(alice).deposit(0, amount, stake); 98 | 99 | await contracts.booster.earmarkRewards(0); 100 | const pool = await contracts.booster.poolInfo(0); 101 | const crvRewards = BaseRewardPool__factory.connect(pool.crvRewards, deployer); 102 | await increaseTime(ONE_WEEK.mul("2")); 103 | 104 | const balanceBefore = await mocks.crv.balanceOf(aliceAddress); 105 | const expectedRewards = await crvRewards.earned(aliceAddress); 106 | 107 | const options = 0; 108 | await contracts.claimZap.connect(alice).claimRewards([pool.crvRewards], [], [], [], 0, 0, 0, options); 109 | 110 | const balanceAfter = await mocks.crv.balanceOf(aliceAddress); 111 | expect(balanceAfter.sub(balanceBefore)).eq(expectedRewards); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /test/AuraMinter.spec.ts: -------------------------------------------------------------------------------- 1 | import hre, { ethers } from "hardhat"; 2 | import { Signer } from "ethers"; 3 | import { expect } from "chai"; 4 | import { deployPhase1, deployPhase2, deployPhase3, deployPhase4 } from "../scripts/deploySystem"; 5 | import { deployMocks, DeployMocksResult, getMockDistro, getMockMultisigs } from "../scripts/deployMocks"; 6 | import { AuraToken, AuraMinter } from "../types/generated"; 7 | import { simpleToExactAmount, getTimestamp, increaseTime, ONE_WEEK } from "../test-utils"; 8 | 9 | describe("AuraMinter", () => { 10 | let accounts: Signer[]; 11 | let cvx: AuraToken; 12 | let minter: AuraMinter; 13 | let mocks: DeployMocksResult; 14 | let deployer: Signer; 15 | let alice: Signer; 16 | let aliceAddress: string; 17 | 18 | before(async () => { 19 | accounts = await ethers.getSigners(); 20 | 21 | deployer = accounts[0]; 22 | mocks = await deployMocks(hre, deployer); 23 | const multisigs = await getMockMultisigs(accounts[0], accounts[0], accounts[0]); 24 | const distro = getMockDistro(); 25 | const phase1 = await deployPhase1(hre, deployer, mocks.addresses); 26 | const phase2 = await deployPhase2( 27 | hre, 28 | deployer, 29 | phase1, 30 | distro, 31 | multisigs, 32 | mocks.namingConfig, 33 | mocks.addresses, 34 | ); 35 | const phase3 = await deployPhase3(hre, deployer, phase2, multisigs, mocks.addresses); 36 | await phase3.poolManager.setProtectPool(false); 37 | const contracts = await deployPhase4(hre, deployer, phase3, mocks.addresses); 38 | 39 | alice = accounts[1]; 40 | aliceAddress = await alice.getAddress(); 41 | cvx = contracts.cvx; 42 | minter = contracts.minter; 43 | }); 44 | 45 | it("initial configuration is correct", async () => { 46 | expect(await minter.aura()).to.equal(cvx.address); 47 | expect(await minter.inflationProtectionTime()).to.gt((await getTimestamp()).add(ONE_WEEK.mul(155))); 48 | expect(await minter.inflationProtectionTime()).to.lt((await getTimestamp()).add(ONE_WEEK.mul(157))); 49 | expect(await minter.owner()).to.equal(await deployer.getAddress()); 50 | }); 51 | describe("@method AuraMinter.mint fails if", async () => { 52 | it("sender is not the dao", async () => { 53 | await expect(minter.connect(alice).mint(aliceAddress, simpleToExactAmount(1))).to.revertedWith( 54 | "Ownable: caller is not the owner", 55 | ); 56 | }); 57 | it("inflation protection time has not expired", async () => { 58 | await expect(minter.connect(deployer).mint(aliceAddress, simpleToExactAmount(1))).to.revertedWith( 59 | "Inflation protected for now", 60 | ); 61 | }); 62 | }); 63 | 64 | describe("@method AuraMinter.mint mints when", async () => { 65 | it("protects inflation up to 155", async () => { 66 | await increaseTime(ONE_WEEK.mul(155)); 67 | await expect(minter.connect(deployer).mint(aliceAddress, simpleToExactAmount(1))).to.revertedWith( 68 | "Inflation protected for now", 69 | ); 70 | }); 71 | it("@method AuraMinter.mint mints tokens", async () => { 72 | await increaseTime(ONE_WEEK.mul(2)); 73 | const beforeBalance = await cvx.balanceOf(aliceAddress); 74 | const beforeTotalSupply = await cvx.totalSupply(); 75 | await minter.mint(aliceAddress, 1000); 76 | const afterBalance = await cvx.balanceOf(aliceAddress); 77 | const afterTotalSupply = await cvx.totalSupply(); 78 | expect(beforeBalance, "balance increases").to.lt(afterBalance); 79 | expect(beforeTotalSupply, "total supply increases").to.lt(afterTotalSupply); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/BalInvestor.spec.ts: -------------------------------------------------------------------------------- 1 | import hre, { ethers } from "hardhat"; 2 | import { expect } from "chai"; 3 | import { MockBalInvestor, MockBalInvestor__factory, ERC20__factory, ERC20 } from "../types/generated"; 4 | import { deployContract } from "../tasks/utils"; 5 | import { fullScale } from "../test-utils"; 6 | import { Signer } from "ethers"; 7 | import { deployMocks } from "../scripts/deployMocks"; 8 | 9 | const debug = false; 10 | 11 | describe("TestBalEth", () => { 12 | let testEthBal: MockBalInvestor; 13 | let balToken: ERC20; 14 | let signer: Signer; 15 | 16 | const amount = ethers.utils.parseEther("100"); 17 | 18 | before(async () => { 19 | const accounts = await ethers.getSigners(); 20 | 21 | signer = accounts[0]; 22 | const mocks = await deployMocks(hre, signer, debug); 23 | 24 | const poolId = "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014"; 25 | const vault = mocks.balancerVault.address; 26 | const weth = mocks.weth.address; 27 | const bal = mocks.bal.address; 28 | 29 | balToken = ERC20__factory.connect(bal, signer); 30 | 31 | testEthBal = await deployContract( 32 | hre, 33 | new MockBalInvestor__factory(signer), 34 | "testEthBal", 35 | [vault, bal, weth, poolId], 36 | {}, 37 | debug, 38 | ); 39 | }); 40 | 41 | describe("join BAL:ETH 80/20 pool with BAL", () => { 42 | it("approve BAL for contract", async () => { 43 | const tx = await balToken.approve(testEthBal.address, amount); 44 | await tx.wait(); 45 | }); 46 | 47 | it("add BAL to pool", async () => { 48 | const bptAddress = await testEthBal.BALANCER_POOL_TOKEN(); 49 | const bpt = ERC20__factory.connect(bptAddress, signer); 50 | 51 | const bptBalanceBefore = await bpt.balanceOf(testEthBal.address); 52 | 53 | let tx = await testEthBal.approveToken(); 54 | await tx.wait(); 55 | 56 | const minOut = await testEthBal.getMinOut(amount, 9980); 57 | tx = await testEthBal.addBalToPool(amount, minOut); 58 | await tx.wait(); 59 | 60 | const bptBalanceAfter = await bpt.balanceOf(testEthBal.address); 61 | const bptBalanceDelta = bptBalanceAfter.sub(bptBalanceBefore); 62 | 63 | const bptPrice = await testEthBal.getBptPrice(); 64 | 65 | const bptBalValue = bptPrice.mul(bptBalanceDelta).div(fullScale); 66 | const minAmount = amount.mul("9950").div("10000"); 67 | expect(bptBalValue).gt(minAmount); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/PoolManager.spec.ts: -------------------------------------------------------------------------------- 1 | import hre, { ethers } from "hardhat"; 2 | import { expect } from "chai"; 3 | import { deployPhase1, deployPhase2, deployPhase3 } from "../scripts/deploySystem"; 4 | import { deployMocks, DeployMocksResult, getMockMultisigs, getMockDistro } from "../scripts/deployMocks"; 5 | import { Booster, MockCurveGauge__factory, PoolManagerV3, MockCurveGauge } from "../types/generated"; 6 | import { deployContract } from "../tasks/utils"; 7 | import { Signer } from "ethers"; 8 | 9 | describe("PoolManagerV3", () => { 10 | let booster: Booster; 11 | let poolManager: PoolManagerV3; 12 | let mocks: DeployMocksResult; 13 | let accounts: Signer[]; 14 | 15 | let alice: Signer; 16 | 17 | before(async () => { 18 | accounts = await ethers.getSigners(); 19 | 20 | mocks = await deployMocks(hre, accounts[0]); 21 | const multisigs = await getMockMultisigs(accounts[0], accounts[0], accounts[0]); 22 | const distro = getMockDistro(); 23 | 24 | alice = accounts[5]; 25 | 26 | const phase1 = await deployPhase1(hre, accounts[0], mocks.addresses); 27 | const phase2 = await deployPhase2( 28 | hre, 29 | accounts[0], 30 | phase1, 31 | distro, 32 | multisigs, 33 | mocks.namingConfig, 34 | mocks.addresses, 35 | ); 36 | const contracts = await deployPhase3(hre, accounts[0], phase2, multisigs, mocks.addresses); 37 | 38 | booster = contracts.booster; 39 | poolManager = contracts.poolManager; 40 | }); 41 | 42 | describe("@method addPool", async () => { 43 | let badGauge: MockCurveGauge; 44 | 45 | before(async () => { 46 | const badLptoken = "0x0000000000000000000000000000000000000000"; 47 | badGauge = await deployContract( 48 | hre, 49 | new MockCurveGauge__factory(accounts[0]), 50 | "MockCurveGauge", 51 | ["BadGauge", "badGauge", badLptoken, []], 52 | {}, 53 | false, 54 | ); 55 | }); 56 | 57 | it("addPool called by operator", async () => { 58 | const gauge = mocks.gauges[0]; 59 | const tx = await poolManager["addPool(address)"](gauge.address); 60 | await tx.wait(); 61 | 62 | const lptoken = await gauge.lp_token(); 63 | const pool = await booster.poolInfo(0); 64 | expect(pool.lptoken).to.equal(lptoken); 65 | }); 66 | 67 | it("reverts if pool weight is 0", async () => { 68 | const failedTx = poolManager["addPool(address)"](badGauge.address); 69 | await expect(failedTx).to.revertedWith("must have weight"); 70 | }); 71 | 72 | it("reverts if lptoken address is 0", async () => { 73 | const tx = await mocks.voting.vote_for_gauge_weights(badGauge.address, 1); 74 | await tx.wait(); 75 | 76 | const failedTx = poolManager["addPool(address)"](badGauge.address); 77 | await expect(failedTx).to.revertedWith("lp token is 0"); 78 | }); 79 | 80 | it("reverts if gauge has already been added", async () => { 81 | const failedTx = poolManager["addPool(address)"](mocks.gauges[0].address); 82 | await expect(failedTx).to.revertedWith("already registered gauge"); 83 | }); 84 | }); 85 | 86 | describe("@method shutdownPool", () => { 87 | it("reverts if not called by operator", async () => { 88 | const failedTx = poolManager.connect(accounts[2]).shutdownPool(0); 89 | await expect(failedTx).to.revertedWith("!auth"); 90 | }); 91 | 92 | it("happy path", async () => { 93 | const tx = await poolManager.shutdownPool(0); 94 | await tx.wait(); 95 | 96 | const pool = await booster.poolInfo(0); 97 | expect(pool.shutdown).to.equal(true); 98 | }); 99 | }); 100 | 101 | describe("@method setProtectPool", () => { 102 | it("protectPool defaults to true", async () => { 103 | const startValue = await poolManager.protectAddPool(); 104 | expect(startValue).to.equal(true); 105 | }); 106 | 107 | it("reverts if addPool is protected and caller is not operator", async () => { 108 | const resp = poolManager.connect(alice)["addPool(address)"](mocks.gauges[1].address); 109 | await expect(resp).to.be.revertedWith("!auth"); 110 | }); 111 | 112 | it("reverts if setProtectPool caller is not operator", async () => { 113 | const resp = poolManager.connect(alice).setProtectPool(false); 114 | await expect(resp).to.be.revertedWith("!auth"); 115 | }); 116 | 117 | it("setProtectPool update protectAddPool", async () => { 118 | await poolManager.setProtectPool(false); 119 | const newValue = await poolManager.protectAddPool(); 120 | expect(newValue).to.equal(false); 121 | }); 122 | 123 | it("addPool can be called by anyone", async () => { 124 | const gauge = mocks.gauges[1]; 125 | await poolManager.connect(alice)["addPool(address)"](gauge.address); 126 | 127 | const lptoken = await gauge.lp_token(); 128 | const pool = await booster.poolInfo(1); 129 | expect(pool.lptoken).to.equal(lptoken); 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "declarationMap": true, 6 | "emitDecoratorMetadata": true, 7 | "esModuleInterop": true, 8 | "experimentalDecorators": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "lib": ["es6"], 11 | "module": "commonjs", 12 | "moduleResolution": "node", 13 | "noImplicitAny": false, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "sourceMap": true, 17 | "rootDir": ".", 18 | "strictNullChecks": false, 19 | "strict": true, 20 | "target": "es6", 21 | "outDir": "./dist/" 22 | }, 23 | "exclude": ["node_modules"], 24 | "include": [ 25 | "tasks/**/*", 26 | "./test/*", 27 | "./test/**/*", 28 | "./test-fork/*", 29 | "./test-fork/**/*", 30 | "types/**/*", 31 | "test-utils/**/*", 32 | "scripts/**/*" 33 | ], 34 | "files": [ 35 | "./index.ts", 36 | "./hardhat.config.ts", 37 | "./hardhat-fork.config.ts", 38 | "./hardhat-cvx.config.ts" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /types/common.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from "ethers" 2 | 3 | export type EthAddress = string 4 | export type Bytes32 = string 5 | 6 | export interface Account { 7 | signer: Signer 8 | address: string 9 | } 10 | -------------------------------------------------------------------------------- /types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common" 2 | export * from "./generated" 3 | --------------------------------------------------------------------------------