├── .gitattributes ├── .github └── workflows │ ├── gas.yaml │ ├── lint.yaml │ └── main.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── brownie-config.yaml ├── contracts ├── CRVInfo.vy ├── CryptoPoolProxy.vy ├── ERC20CRV.vy ├── FeeDistributor.vy ├── GaugeController.vy ├── GaugeProxy.vy ├── Minter.vy ├── PoolProxy.vy ├── PoolProxySidechain.vy ├── README.md ├── VotingEscrow.vy ├── bridging │ ├── AnyswapBridger.vy │ ├── PolygonBridger.vy │ └── RootForwarder.vy ├── burners │ ├── README.md │ ├── eth │ │ ├── ABurner.vy │ │ ├── CBurner.vy │ │ ├── CryptoFactoryLPBurner.vy │ │ ├── CryptoLPBurner.vy │ │ ├── LPBurner.vy │ │ ├── MetaBurner.vy │ │ ├── SwapCryptoBurner.vy │ │ ├── SwapStableBurner.vy │ │ ├── SynthBurner.vy │ │ ├── TricryptoFactoryLPBurner.vy │ │ ├── USDNBurner.vy │ │ ├── UnderlyingBurner.vy │ │ ├── UniswapBurner.vy │ │ ├── WrappedBurner.vy │ │ ├── YBurner.vy │ │ ├── crvUSDBurner.vy │ │ ├── deprecated │ │ │ ├── BTCBurner.vy │ │ │ ├── ETHBurner.vy │ │ │ ├── EuroBurner.vy │ │ │ └── README.md │ │ └── wstETHBurner.vy │ ├── fantom │ │ ├── BTCBurnerFantom.vy │ │ ├── CBurnerFantom.vy │ │ ├── GBurnerFantom.vy │ │ ├── LPBurnerFantom.vy │ │ ├── TripCryptoBurnerFantom.vy │ │ └── UnderlyingBurnerFantom.vy │ ├── optimism │ │ └── TricrvBurnerOptimism.vy │ └── polygon │ │ ├── ABurnerPolygon.vy │ │ ├── BTCBurnerPolygon.vy │ │ └── TriCryptoBurnerPolygon.vy ├── gauges │ ├── LiquidityGauge.vy │ ├── LiquidityGaugeReward.vy │ ├── LiquidityGaugeV2.vy │ ├── LiquidityGaugeV3.vy │ ├── LiquidityGaugeV4.vy │ ├── LiquidityGaugeV5.vy │ ├── README.md │ ├── RewardsOnlyGauge.vy │ ├── sidechain │ │ ├── CheckpointProxy.vy │ │ ├── RootGaugeAnyswap.vy │ │ ├── RootGaugeArbitrum.vy │ │ ├── RootGaugeHarmony.vy │ │ ├── RootGaugePolygon.vy │ │ └── RootGaugeXdai.vy │ └── wrappers │ │ ├── LiquidityGaugeRewardWrapper.vy │ │ ├── LiquidityGaugeWrapper.vy │ │ ├── LiquidityGaugeWrapperUnit.vy │ │ └── README.md ├── streamers │ ├── ChildChainStreamer.vy │ ├── RewardClaimer.vy │ └── RewardStream.vy ├── testing │ ├── CurvePool.vy │ ├── CurveRewards.sol │ ├── ERC20LP.vy │ ├── README.md │ └── UnitVault.vy └── vests │ ├── README.md │ ├── VestingEscrow.vy │ ├── VestingEscrowFactory.vy │ └── VestingEscrowSimple.vy ├── deployment-logs ├── 2020-05-20 │ ├── deploy_rinkeby.log │ ├── token_crv.abi │ └── voting_escrow.abi ├── 2020-05-20_1 │ ├── deploy_rinkeby.log │ ├── token_crv.abi │ └── voting_escrow.abi ├── 2020-05-20_2 │ ├── rinkeby.log │ ├── token_crv.abi │ └── voting_escrow.abi ├── 2020-05-21 │ ├── rinkeby.log │ ├── token_crv.abi │ └── voting_escrow.abi ├── 2020-05-24 │ ├── abis │ │ ├── curve_pool.abi │ │ ├── gauge_controller.abi │ │ ├── liquidity_gauge.abi │ │ ├── lp_token.abi │ │ ├── minter.abi │ │ ├── registry.abi │ │ ├── token_crv.abi │ │ └── voting_escrow.abi │ ├── contracts │ │ ├── ERC20CRV.vy │ │ ├── GaugeController.vy │ │ ├── LiquidityGauge.vy │ │ ├── Minter.vy │ │ ├── VotingEscrow.vy │ │ └── testing │ │ │ ├── CurvePool.vy │ │ │ ├── ERC20.vy │ │ │ ├── ERC20LP.vy │ │ │ └── Registry.vy │ └── rinkeby.log └── 2020-05-26 │ ├── abis │ ├── curve_pool.abi │ ├── gauge_controller.abi │ ├── liquidity_gauge.abi │ ├── lp_token.abi │ ├── minter.abi │ ├── registry.abi │ ├── token_crv.abi │ └── voting_escrow.abi │ ├── contracts │ ├── ERC20CRV.vy │ ├── GaugeController.vy │ ├── LiquidityGauge.vy │ ├── Minter.vy │ ├── VotingEscrow.vy │ └── testing │ │ ├── CurvePool.vy │ │ ├── ERC20.vy │ │ ├── ERC20LP.vy │ │ └── Registry.vy │ └── rinkeby.log ├── deployments.json ├── doc ├── README.md ├── dao-overview.pdf ├── dao-overview.svg ├── inflation.pdf ├── inflation.svg ├── readme.pdf ├── votelock.pdf ├── votelock.svg └── watch.sh ├── package.json ├── pyproject.toml ├── requirements.in ├── requirements.txt ├── scripts ├── burners │ ├── burn_fantom.py │ ├── burn_polygon.py │ ├── claim_and_burn_fees.py │ ├── deploy_burners_fee_distro.py │ ├── exit_polygon.py │ └── simulate_fee_distro.py ├── deployment │ ├── Deploy_Aragon_DAO_README.md │ ├── README.md │ ├── deploy_dao.py │ ├── deploy_testnet.py │ ├── deployment_config.py │ ├── early-users.json │ ├── transfer_dao_ownership.py │ ├── transfer_pool_ownership.py │ ├── vest_lp_tokens.py │ └── vest_other_tokens.py ├── sidechain │ ├── checkpoint.py │ └── deploy_gauge.py ├── stats │ ├── gini.py │ ├── plot_vecrv.py │ └── show_weekly_fees.py └── voting │ ├── decode_vote.py │ └── new_vote.py ├── setup.cfg └── tests ├── __init__.py ├── conftest.py ├── fork ├── Burners │ ├── test_aburner.py │ ├── test_btc_burner.py │ ├── test_cburner.py │ ├── test_crvburner.py │ ├── test_crypto_factory_lp_burner.py │ ├── test_eth_burner.py │ ├── test_euro_burner.py │ ├── test_fee_distribution.py │ ├── test_idle_burner.py │ ├── test_lp_burner.py │ ├── test_metaburner.py │ ├── test_swap_crypto_burner.py │ ├── test_swap_stable_burner.py │ ├── test_synth_burner.py │ ├── test_tricrypto_factory_lp_burner.py │ ├── test_underlying_burner.py │ ├── test_usdn_burner.py │ ├── test_wrapped_burner.py │ └── test_yburner.py ├── conftest.py └── test_root_gauge.py ├── integration ├── ERC20CRV │ ├── test_mint_integration.py │ └── test_mintable_in_timeframe.py ├── FeeDistributor │ ├── test_checkpoint_ts.py │ └── test_distribute_fees_stateful.py ├── GaugeController │ ├── test_types_and_weights.py │ └── test_vote_weight.py ├── LiquidityGauge │ ├── test_deposits_withdrawals.py │ └── test_liquidity_gauge.py ├── LiquidityGaugeReward │ ├── test_borked_rewards.py │ ├── test_rewards_ratio.py │ └── test_rewards_total.py ├── LiquidityGaugeV3 │ ├── test_deposits_withdrawals_v3.py │ ├── test_liquidity_gauge_v3.py │ └── test_reward_distribution.py ├── Minter │ ├── test_components.py │ └── test_minter_integration.py ├── RewardsOnlyGauge │ └── test_rewards_total_transferred_in.py ├── VestingEscrow │ └── test_claim_partial.py ├── VotingEscrow │ ├── test_deposit_withdraw_voting.py │ ├── test_voting_escrow.py │ └── test_zero_balance_at_unlock_time.py ├── __init__.py └── test_reward_stream_state.py ├── test_scalability.py └── unitary ├── Burners ├── test_burner_kill.py ├── test_burner_ownership.py └── test_recover_tokens.py ├── CryptoPoolProxy ├── conftest.py ├── test_emergency_admin_crypto.py ├── test_owner_admin_crypto.py ├── test_parameter_admin_crypto.py ├── test_proxy_burn_crypto.py └── test_set_admins_crypto.py ├── ERC20CRV ├── test_burn.py ├── test_epoch_time_supply.py ├── test_inflation_delay.py ├── test_mint.py └── test_setters.py ├── FeeDistribution ├── test_checkpoints.py ├── test_claim_many.py ├── test_fee_distribution.py └── test_kill_fee_distro.py ├── GaugeController ├── conftest.py ├── test_gaugecontroller_admin.py ├── test_gauges_weights.py ├── test_timestamps.py ├── test_total_weight.py ├── test_vote.py └── test_vote_weight_unitary.py ├── GaugeProxy ├── test_gauge_ownership.py ├── test_gauge_proxy_set_admins.py ├── test_set_killed_gauge_proxy.py └── test_set_rewards_gauge_proxy.py ├── LiquidityGauge ├── __init__.py ├── test_checkpoint.py ├── test_deposit_for.py ├── test_deposit_withdraw.py └── test_kick.py ├── LiquidityGaugeReward ├── __init__.py ├── test_checkpoint_reward.py ├── test_claim_rewards.py ├── test_claim_rewards_none.py ├── test_deposit_for_rewards.py └── test_deposit_withdraw_reward.py ├── LiquidityGaugeRewardWrapper ├── __init__.py ├── test_approve.py ├── test_checkpoint.py ├── test_claim_tokens.py ├── test_deposit_withdraw.py ├── test_transfer.py └── test_transferFrom.py ├── LiquidityGaugeV2 ├── __init__.py ├── test_approve.py ├── test_checkpoint.py ├── test_claim_historic_rewards.py ├── test_claim_rewards_multiple.py ├── test_claim_rewards_none.py ├── test_claim_rewards_transfer.py ├── test_claim_rewards_unipool.py ├── test_deposit_for.py ├── test_deposit_withdraw.py ├── test_kick.py ├── test_set_rewards.py ├── test_set_rewards_deposit.py ├── test_set_rewards_no_deposit.py ├── test_transfer.py └── test_transferFrom.py ├── LiquidityGaugeV3 ├── __init__.py ├── test_approve.py ├── test_checkpoint.py ├── test_claim_rewards_multiple.py ├── test_claim_rewards_none.py ├── test_claim_rewards_transfer.py ├── test_claim_rewards_unipool.py ├── test_deposit_for.py ├── test_deposit_withdraw.py ├── test_kick.py ├── test_mass_exit_reward_claim.py ├── test_set_rewards.py ├── test_set_rewards_deposit.py ├── test_set_rewards_no_deposit.py ├── test_set_rewards_receiver.py ├── test_transfer.py └── test_transferFrom.py ├── LiquidityGaugeV5 └── test_permit.py ├── LiquidityGaugeWrapper ├── __init__.py ├── test_approve.py ├── test_checkpoint.py ├── test_claim_tokens.py ├── test_deposit_withdraw.py ├── test_transfer.py └── test_transferFrom.py ├── LiquidityGaugeWrapperUnit ├── __init__.py ├── conftest.py ├── test_approve.py ├── test_checkpoint.py ├── test_claim_tokens.py ├── test_deposit_withdraw.py ├── test_transfer.py ├── test_transferFrom.py └── test_vault.py ├── Minter └── test_minter.py ├── PoolProxy ├── conftest.py ├── test_emergency_admin.py ├── test_owner_admin.py ├── test_parameter_admin.py ├── test_proxy_burn.py └── test_set_admins.py ├── RewardStream ├── conftest.py ├── test_add_receiver.py ├── test_amounts.py ├── test_get_reward.py ├── test_notify_reward_amount.py ├── test_ownership_change.py ├── test_remove_receiver.py ├── test_set_reward_distributor.py └── test_set_reward_duration.py ├── RewardsOnlyGauge ├── __init__.py ├── test_approve.py ├── test_claim_no_reward_contract.py ├── test_claim_rewards_multiple.py ├── test_claim_rewards_none.py ├── test_claim_rewards_transfer.py ├── test_claim_rewards_unipool.py ├── test_deposit_withdraw.py ├── test_mass_exit_reward_claim.py ├── test_set_rewards.py ├── test_set_rewards_no_deposit.py ├── test_set_rewards_receiver.py ├── test_transfer.py └── test_transferFrom.py ├── Sidechain ├── child_chain_streamer │ ├── test_add_reward.py │ ├── test_admin_functions.py │ ├── test_get_reward_child_chain.py │ ├── test_notify_reward_amount_child_chain.py │ └── test_remove_reward.py ├── conftest.py ├── test_accept_transfer_ownership.py ├── test_checkpoint_proxy.py ├── test_commit_transfer_ownership.py ├── test_root_checkpoint.py ├── test_sending_to_bridge.py ├── test_set_checkpoint_admin.py └── test_set_killed.py ├── VestingEscrow ├── test_claim.py ├── test_disable.py ├── test_disable_and_claim.py ├── test_fund.py ├── test_getters.py └── test_vesting_escrow_admin.py ├── VestingEscrowFactory ├── test_admin_factory.py ├── test_admin_simple.py ├── test_claim_simple.py ├── test_deploy_escrow.py ├── test_disable_and_claim_simple.py ├── test_disable_simple.py └── test_getters_simple.py ├── VotingEscrow └── test_votingescrow_admin.py └── __init__.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.vy linguist-language=Python 3 | -------------------------------------------------------------------------------- /.github/workflows/gas.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | name: gas efficiency workflow 7 | 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | NODE_OPTIONS: --max_old_space_size=4096 11 | 12 | jobs: 13 | 14 | gas: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Cache Compiler Installations 21 | uses: actions/cache@v2 22 | with: 23 | path: | 24 | ~/.solcx 25 | ~/.vvm 26 | key: compiler-cache 27 | 28 | - name: Setup Node.js 29 | uses: actions/setup-node@v1 30 | 31 | - name: Install Ganache 32 | run: npm install 33 | 34 | - name: Setup Python 3.8 35 | uses: actions/setup-python@v2 36 | with: 37 | python-version: 3.8 38 | 39 | - name: Install Requirements 40 | run: pip install -r requirements.txt 41 | 42 | - name: Run Tests 43 | run: brownie test tests/test_scalability.py --gas 44 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | on: ["push", "pull_request"] 2 | 3 | name: linting 4 | 5 | jobs: 6 | 7 | lint: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Setup Python 3.8 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: 3.8 17 | 18 | - name: Install Requirements 19 | run: pip install -r requirements.txt 20 | 21 | - name: Run black 22 | run: black --check scripts tests 23 | 24 | - name: Run flake8 25 | run: flake8 scripts tests 26 | 27 | - name: Run isort 28 | run: isort --check-only --diff --recursive scripts tests 29 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: main workflow 4 | 5 | env: 6 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 7 | NODE_OPTIONS: --max_old_space_size=4096 8 | 9 | 10 | jobs: 11 | 12 | unitary: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Cache Compiler Installations 19 | uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.solcx 23 | ~/.vvm 24 | key: compiler-cache 25 | 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v1 28 | 29 | - name: Install Ganache 30 | run: npm install 31 | 32 | - name: Setup Python 3.8 33 | uses: actions/setup-python@v2 34 | with: 35 | python-version: 3.8 36 | 37 | - name: Install Requirements 38 | run: pip install -r requirements.txt 39 | 40 | - name: Run Tests 41 | run: brownie test tests/unitary -C -n auto 42 | 43 | integration: 44 | runs-on: ubuntu-latest 45 | 46 | steps: 47 | - uses: actions/checkout@v2 48 | 49 | - name: Cache Compiler Installations 50 | uses: actions/cache@v2 51 | with: 52 | path: | 53 | ~/.solcx 54 | ~/.vvm 55 | key: compiler-cache 56 | 57 | - name: Setup Node.js 58 | uses: actions/setup-node@v1 59 | 60 | - name: Install Ganache 61 | run: npm install 62 | 63 | - name: Setup Python 3.8 64 | uses: actions/setup-python@v2 65 | with: 66 | python-version: 3.8 67 | 68 | - name: Install Requirements 69 | run: pip install -r requirements.txt 70 | 71 | - name: Run Tests 72 | run: brownie test tests/integration -n auto --failfast --durations 5 --stateful false 73 | 74 | integration-stateful: 75 | runs-on: ubuntu-latest 76 | 77 | steps: 78 | - uses: actions/checkout@v2 79 | 80 | - name: Cache Compiler Installations 81 | uses: actions/cache@v2 82 | with: 83 | path: | 84 | ~/.solcx 85 | ~/.vvm 86 | key: compiler-cache 87 | 88 | - name: Setup Node.js 89 | uses: actions/setup-node@v1 90 | 91 | - name: Install Ganache 92 | run: npm install 93 | 94 | - name: Setup Python 3.8 95 | uses: actions/setup-python@v2 96 | with: 97 | python-version: 3.8 98 | 99 | - name: Install Requirements 100 | run: pip install -r requirements.txt 101 | 102 | - name: Run Tests 103 | run: brownie test tests/integration -n auto --failfast --durations 5 --stateful true 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .venv 3 | venv 4 | __pycache__ 5 | build/ 6 | reports/ 7 | /scripts/deployment_config_prod.py 8 | /scripts/deployment_keys.py 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 20.8b1 4 | hooks: 5 | - id: black 6 | - repo: https://gitlab.com/pycqa/flake8 7 | rev: 3.8.4 8 | hooks: 9 | - id: flake8 10 | - repo: https://github.com/PyCQA/isort 11 | rev: 5.7.0 12 | hooks: 13 | - id: isort 14 | 15 | default_language_version: 16 | python: python3.8 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Curve Finance 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /brownie-config.yaml: -------------------------------------------------------------------------------- 1 | reports: 2 | exclude_paths: 3 | - contracts/testing/*.* 4 | 5 | networks: 6 | development: 7 | cmd_settings: 8 | accounts: 100 9 | chain_id: 1337 10 | mainnet-fork: 11 | cmd_settings: 12 | unlock: 0xC447FcAF1dEf19A583F97b3620627BF69c05b5fB 13 | 14 | autofetch_sources: True 15 | dependencies: 16 | - curvefi/curve-crypto-contract@1.0.0 17 | -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # curve-dao-contracts/contracts 2 | 3 | All contract sources are within this directory. 4 | 5 | ## Subdirectories 6 | 7 | * [`burners`](burners): Contracts used to convert admin fees into 3CRV prior to distribution to the DAO. 8 | * [`gauges`](gauges): Contracts used for measuring provided liquidity. 9 | * [`testing`](testing): Contracts used exclusively for testing. Not considered to be a core part of this project. 10 | * [`vests`](vests): Contracts for vesting CRV. 11 | 12 | ## Contracts 13 | 14 | * [`ERC20CRV`](ERC20CRV.vy): Curve DAO Token (CRV), an [ERC20](https://eips.ethereum.org/EIPS/eip-20) with piecewise-linear mining supply 15 | * [`GaugeController`](GaugeController.vy): Controls liquidity gauges and the issuance of CRV through the liquidity gauges 16 | * [`LiquidityGauge`](LiquidityGauge.vy): Measures the amount of liquidity provided by each user 17 | * [`LiquidityGaugeReward`](LiquidityGaugeReward.vy): Measures provided liquidity and stakes using [Synthetix rewards contract](https://github.com/Synthetixio/synthetix/blob/master/contracts/StakingRewards.sol) 18 | * [`Minter`](Minter.vy): Token minting contract used for issuing new CRV 19 | * [`PoolProxy`](PoolProxy.vy): StableSwap pool proxy contract for interactions between the DAO and pool contracts 20 | * [`VestingEscrow`](VestingEscrow.vy): Vests CRV tokens for multiple addresses over multiple vesting periods 21 | * [`VestingEscrowFactory`](VestingEscrowFactory.vy): Factory to store CRV and deploy many simplified vesting contracts 22 | * [`VestingEscrowSimple`](VestingEscrowSimple.vy): Simplified vesting contract that holds CRV for a single address 23 | * [`VotingEscrow`](VotingEscrow.vy): Vesting contract for locking CRV to participate in DAO governance 24 | -------------------------------------------------------------------------------- /contracts/bridging/AnyswapBridger.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.0 2 | """ 3 | @title Anyswap Bridging Contract 4 | @author Curve Finance 5 | @license MIT 6 | """ 7 | 8 | interface AnyswapToken: 9 | def Swapout(_amount: uint256, _receiver: address) -> bool: nonpayable 10 | def balanceOf(_user: address) -> uint256: view 11 | 12 | 13 | event CommitOwnership: 14 | admin: address 15 | 16 | event ApplyOwnership: 17 | admin: address 18 | 19 | event AssetBridged: 20 | token: address 21 | amount: uint256 22 | 23 | 24 | admin: public(address) 25 | future_admin: public(address) 26 | 27 | root_receiver: public(address) 28 | 29 | 30 | @external 31 | def __init__(_admin: address, _receiver: address): 32 | """ 33 | @param _admin Contract owner. Should be the `PoolProxy` contract 34 | used to handle fee burns. 35 | @param _receiver Receiver address on the root chain. 36 | """ 37 | self.admin = _admin 38 | self.root_receiver = _receiver 39 | 40 | 41 | @external 42 | def bridge(_token: address) -> bool: 43 | """ 44 | @notice Transfer a token to the root chain via Anyswap. 45 | """ 46 | assert msg.sender == self.admin 47 | 48 | amount: uint256 = AnyswapToken(_token).balanceOf(self) 49 | AnyswapToken(_token).Swapout(amount, self.root_receiver) 50 | 51 | log AssetBridged(_token, amount) 52 | return True 53 | 54 | 55 | @external 56 | def commit_transfer_ownership(_addr: address): 57 | """ 58 | @notice Transfer ownership of GaugeController to `addr` 59 | @param _addr Address to have ownership transferred to 60 | """ 61 | assert msg.sender == self.admin # dev: admin only 62 | 63 | self.future_admin = _addr 64 | log CommitOwnership(_addr) 65 | 66 | 67 | @external 68 | def accept_transfer_ownership(): 69 | """ 70 | @notice Accept a pending ownership transfer 71 | """ 72 | _admin: address = self.future_admin 73 | assert msg.sender == _admin # dev: future admin only 74 | 75 | self.admin = _admin 76 | log ApplyOwnership(_admin) 77 | 78 | 79 | @external 80 | def set_root_receiver(_receiver: address): 81 | """ 82 | @notice Set the receiver address on the root chain. 83 | """ 84 | assert msg.sender == self.admin, "Access denied" 85 | 86 | self.root_receiver = _receiver 87 | -------------------------------------------------------------------------------- /contracts/bridging/PolygonBridger.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.0 2 | """ 3 | @title Polygon Bridging Contract 4 | @author Curve Finance 5 | @license MIT 6 | """ 7 | 8 | 9 | interface BridgeToken: 10 | def withdraw(_amount: uint256): nonpayable 11 | def balanceOf(_user: address) -> uint256: view 12 | 13 | 14 | event CommitOwnership: 15 | admin: address 16 | 17 | event ApplyOwnership: 18 | admin: address 19 | 20 | event AssetBridged: 21 | token: address 22 | amount: uint256 23 | 24 | 25 | admin: public(address) 26 | future_admin: public(address) 27 | 28 | 29 | @external 30 | def __init__(_admin: address): 31 | """ 32 | @param _admin Contract owner. Should be the `PoolProxy` contract 33 | used to handle fee burns. 34 | """ 35 | self.admin = _admin 36 | 37 | 38 | @external 39 | def bridge(_token: address) -> bool: 40 | """ 41 | @notice Transfer a token to the root chain via Anyswap. 42 | """ 43 | assert msg.sender == self.admin 44 | 45 | amount: uint256 = BridgeToken(_token).balanceOf(self) 46 | BridgeToken(_token).withdraw(amount) 47 | 48 | log AssetBridged(_token, amount) 49 | return True 50 | 51 | 52 | @external 53 | def commit_transfer_ownership(_addr: address): 54 | """ 55 | @notice Transfer ownership of GaugeController to `addr` 56 | @param _addr Address to have ownership transferred to 57 | """ 58 | assert msg.sender == self.admin # dev: admin only 59 | 60 | self.future_admin = _addr 61 | log CommitOwnership(_addr) 62 | 63 | 64 | @external 65 | def accept_transfer_ownership(): 66 | """ 67 | @notice Accept a pending ownership transfer 68 | """ 69 | _admin: address = self.future_admin 70 | assert msg.sender == _admin # dev: future admin only 71 | 72 | self.admin = _admin 73 | log ApplyOwnership(_admin) 74 | -------------------------------------------------------------------------------- /contracts/bridging/RootForwarder.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.0 2 | """ 3 | @title Root Forwarder 4 | @author Curve Finance 5 | @license MIT 6 | @notice Reciever contract for sidechain fees. Must be deployed to the same 7 | address as the sidechain bridger, when using a bridge that does not 8 | allow specificying a receiver on the root chain. 9 | """ 10 | 11 | 12 | from vyper.interfaces import ERC20 13 | 14 | 15 | owner: public(address) 16 | future_owner: public(address) 17 | 18 | pool_proxy: public(address) 19 | 20 | 21 | @external 22 | def __init__(_owner: address, _pool_proxy: address): 23 | self.owner = _owner 24 | self.pool_proxy = _pool_proxy 25 | 26 | 27 | @external 28 | def transfer(_token: address) -> bool: 29 | # transfer underlying coin from msg.sender to self 30 | amount: uint256 = ERC20(_token).balanceOf(self) 31 | response: Bytes[32] = raw_call( 32 | _token, 33 | _abi_encode(self.pool_proxy, amount, method_id=method_id("transfer(address,uint256)")), 34 | max_outsize=32 35 | ) 36 | if len(response) != 0: 37 | assert convert(response, bool) 38 | 39 | return True 40 | 41 | 42 | @external 43 | def transfer_many(_tokens: address[10]) -> bool: 44 | pool_proxy: address = self.pool_proxy 45 | for token in _tokens: 46 | if token == ZERO_ADDRESS: 47 | break 48 | 49 | # transfer underlying coin from msg.sender to self 50 | amount: uint256 = ERC20(token).balanceOf(self) 51 | response: Bytes[32] = raw_call( 52 | token, 53 | _abi_encode(self.pool_proxy, amount, method_id=method_id("transfer(address,uint256)")), 54 | max_outsize=32 55 | ) 56 | if len(response) != 0: 57 | assert convert(response, bool) 58 | 59 | return True 60 | 61 | 62 | @external 63 | def set_pool_proxy(_pool_proxy: address): 64 | assert msg.sender == self.owner 65 | self.pool_proxy = _pool_proxy 66 | 67 | 68 | @external 69 | def commit_transfer_ownership(_owner: address): 70 | assert msg.sender == self.owner 71 | self.future_owner = _owner 72 | 73 | 74 | @external 75 | def accept_transfer_ownership(): 76 | assert msg.sender == self.future_owner 77 | self.owner = self.future_owner 78 | -------------------------------------------------------------------------------- /contracts/burners/README.md: -------------------------------------------------------------------------------- 1 | # curve-dao-contracts/contracts/burners/deprecated 2 | 3 | Contracts used to convert admin fees into 3CRV prior to distribution to the DAO. 4 | 5 | View the [documentation](https://curve.readthedocs.io/dao-fees.html#the-burn-process) to learn more about the fee burning process and how to interact with these contracts. 6 | -------------------------------------------------------------------------------- /contracts/burners/eth/WrappedBurner.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.3 2 | """ 3 | @title Wrapped ETH Burner 4 | @notice Withdraws ETH from Wrapped ETH 5 | """ 6 | 7 | 8 | interface wETH: 9 | def balanceOf(_owner: address) -> uint256: view 10 | def transferFrom(_sender: address, _receiver: address, _amount: uint256): nonpayable 11 | def withdraw(_amount: uint256): nonpayable 12 | 13 | 14 | WETH: immutable(wETH) 15 | RECEIVER: immutable(address) 16 | 17 | 18 | @external 19 | def __init__(_weth: address, _receiver: address): 20 | """ 21 | @notice Contract constructor 22 | @param _weth Address of wrapped ETh 23 | @param _receiver Address of receiver of ETH 24 | """ 25 | WETH = wETH(_weth) 26 | RECEIVER = _receiver 27 | 28 | 29 | @payable 30 | @external 31 | def __default__(): 32 | pass 33 | 34 | 35 | @view 36 | @external 37 | def receiver() -> address: 38 | return RECEIVER 39 | 40 | 41 | @external 42 | def burn(_coin: address) -> bool: 43 | """ 44 | @notice Unwrap ETH 45 | @param _coin Remained for compatability 46 | @return bool success 47 | """ 48 | amount: uint256 = WETH.balanceOf(msg.sender) 49 | WETH.transferFrom(msg.sender, self, amount) 50 | amount = WETH.balanceOf(self) 51 | WETH.withdraw(amount) 52 | raw_call(RECEIVER, b"", value=self.balance) 53 | return True 54 | -------------------------------------------------------------------------------- /contracts/burners/eth/deprecated/README.md: -------------------------------------------------------------------------------- 1 | # curve-dao-contracts/contracts/burners/deprecated 2 | 3 | Fee burner contracts which are no longer in use. 4 | -------------------------------------------------------------------------------- /contracts/burners/eth/wstETHBurner.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.7 2 | """ 3 | @title Wrapped stETH Burner 4 | @notice Withdraws stETH from Wrapped stETH 5 | """ 6 | 7 | from vyper.interfaces import ERC20 8 | 9 | 10 | interface wstETH: 11 | def balanceOf(_owner: address) -> uint256: view 12 | def transferFrom(_sender: address, _receiver: address, _amount: uint256): nonpayable 13 | def unwrap(_wstETHAmount: uint256): nonpayable 14 | def stETH() -> address: view 15 | 16 | 17 | WSTETH: immutable(wstETH) 18 | STETH: immutable(ERC20) 19 | RECEIVER: immutable(address) 20 | 21 | 22 | @external 23 | def __init__(_wsteth: wstETH, _receiver: address): 24 | """ 25 | @notice Contract constructor 26 | @param _wsteth Address of wrapped stETH 27 | @param _receiver Address of receiver of stETH 28 | """ 29 | WSTETH = _wsteth 30 | STETH = ERC20(_wsteth.stETH()) 31 | RECEIVER = _receiver 32 | 33 | 34 | @payable 35 | @external 36 | def __default__(): 37 | pass 38 | 39 | 40 | @view 41 | @external 42 | def receiver() -> address: 43 | return RECEIVER 44 | 45 | 46 | @external 47 | def burn(_coin: address) -> bool: 48 | """ 49 | @notice Unwrap stETH 50 | @param _coin Remained for compatability 51 | @return bool success 52 | """ 53 | amount: uint256 = WSTETH.balanceOf(msg.sender) 54 | WSTETH.transferFrom(msg.sender, self, amount) 55 | amount = WSTETH.balanceOf(self) 56 | WSTETH.unwrap(amount) 57 | 58 | amount = STETH.balanceOf(self) 59 | STETH.transfer(RECEIVER, amount) 60 | return True 61 | -------------------------------------------------------------------------------- /contracts/gauges/README.md: -------------------------------------------------------------------------------- 1 | # curve-dao-contracts/contracts/gauges 2 | 3 | Contracts used for measuring provided liquidity within Curve pools. 4 | 5 | You can read the [documentation](https://curve.readthedocs.io/dao-gauges.html) to learn more about Curve gauges. 6 | 7 | ## Contracts 8 | 9 | * [`LiquidityGaugeV3`](LiquidityGaugeV3.vy): Tokenized liquidity gauge further optimized for gas efficiency. 10 | * [`LiquidityGaugeV2`](LiquidityGaugeV2.vy): Tokenized liquidity gauge with generalized onward staking and support for multiple reward tokens. 11 | * [`LiquidityGauge`](LiquidityGauge.vy): Measures the amount of liquidity provided by each user. 12 | * [`LiquidityGaugeReward`](LiquidityGaugeReward.vy): Measures provided liquidity and stakes using the [Synthetix rewards contract](https://github.com/curvefi/unipool-fork). 13 | * [`RewardsOnlyGauge`](RewardsOnlyGauge.vy): Handles distribution of 3rd-party incentives without receiving CRV emissions. Typically used on sidechains. 14 | -------------------------------------------------------------------------------- /contracts/gauges/sidechain/CheckpointProxy.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.12 2 | """ 3 | @title Checkpoint Proxy 4 | @author Curve.Fi 5 | @license MIT 6 | @notice Calls `checkpoint` on Anyswap gauges to meet bridge whitelisting requirements 7 | """ 8 | 9 | interface RootGauge: 10 | def checkpoint() -> bool: nonpayable 11 | 12 | 13 | @external 14 | def checkpoint(_gauge: address) -> bool: 15 | # anyswap bridge cannot handle multiple transfers in one call, so we 16 | # block smart contracts that could checkpoint multiple gauges at once 17 | assert msg.sender == tx.origin 18 | 19 | RootGauge(_gauge).checkpoint() 20 | return True 21 | -------------------------------------------------------------------------------- /contracts/gauges/wrappers/README.md: -------------------------------------------------------------------------------- 1 | # curve-dao-contracts/contracts/gauges/wrappers 2 | 3 | Wrappers are used to tokenize deposits in first-gen gauges. 4 | 5 | ## Contracts 6 | 7 | * [`LiquidityGaugeRewardWrapper`](LiquidityGaugeRewardWrapper.vy): ERC20 wrapper for depositing into[`LiquidityGaugeReward`](LiquidityGaugeReward.vy) 8 | * [`LiquidityGaugeWrapper`](LiquidityGaugeWrapper.vy): ERC20 wrapper for depositing into [`LiquidityGauge`](LiquidityGauge.vy) 9 | * [`LiquidityGaugeWrapperUnit`](LiquidityGaugeWrapper.vy): Tokenizes gauge deposits to allow claiming of CRV when deposited as a collateral within the [unit.xyz](https://unit.xyz/) vault. 10 | -------------------------------------------------------------------------------- /contracts/streamers/RewardClaimer.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.16 2 | """ 3 | @title RewardClaimer 4 | @author Curve.Fi 5 | @license MIT 6 | @notice Passthrough to allow claiming from multiple reward streamers 7 | """ 8 | 9 | from vyper.interfaces import ERC20 10 | 11 | 12 | interface RewardStream: 13 | def get_reward(): nonpayable 14 | 15 | 16 | struct RewardData: 17 | claim: address 18 | reward: address 19 | 20 | 21 | owner: public(address) 22 | future_owner: public(address) 23 | reward_receiver: public(address) 24 | 25 | reward_data: public(RewardData[4]) 26 | 27 | 28 | @external 29 | def __init__(_owner: address, _reward_receiver: address): 30 | """ 31 | @notice Contract constructor 32 | @param _owner Owner address 33 | @param _reward_receiver Reward receiver address 34 | """ 35 | self.owner = _owner 36 | self.reward_receiver = _reward_receiver 37 | 38 | 39 | @external 40 | def get_reward(): 41 | """ 42 | @notice Claim all available rewards 43 | @dev Only callable by the reward receiver 44 | """ 45 | assert msg.sender == self.reward_receiver 46 | 47 | for i in range(4): 48 | data: RewardData = self.reward_data[i] 49 | if data.reward == ZERO_ADDRESS: 50 | break 51 | 52 | RewardStream(data.claim).get_reward() 53 | amount: uint256 = ERC20(data.reward).balanceOf(self) 54 | if amount > 0: 55 | assert ERC20(data.reward).transfer(msg.sender, amount) 56 | 57 | 58 | @external 59 | def set_reward_data(_idx: uint256, _claim: address, _reward: address): 60 | """ 61 | @notice Set data about a reward streamer 62 | @dev Only callable by the owner 63 | @param _idx Index of `reward_data` to modify 64 | @param _claim Address of the streamer to claim from 65 | @param _reward Address of the reward token 66 | """ 67 | assert msg.sender == self.owner 68 | 69 | self.reward_data[_idx] = RewardData({claim: _claim, reward: _reward}) 70 | 71 | 72 | @external 73 | def commit_transfer_ownership(_owner: address): 74 | """ 75 | @notice Initiate ownership tansfer of the contract 76 | @param _owner Address to have ownership transferred to 77 | """ 78 | assert msg.sender == self.owner # dev: only owner 79 | 80 | self.future_owner = _owner 81 | 82 | 83 | @external 84 | def accept_transfer_ownership(): 85 | """ 86 | @notice Accept a pending ownership transfer 87 | """ 88 | owner: address = self.future_owner 89 | assert msg.sender == owner # dev: only new owner 90 | 91 | self.owner = owner 92 | -------------------------------------------------------------------------------- /contracts/testing/README.md: -------------------------------------------------------------------------------- 1 | # curve-dao-contracts/contracts/testing 2 | 3 | Contracts used exclusively for testing. These are not considered part of Curve DAO and so do not fall under the Curve DAO [license](../../LICENSE). 4 | 5 | ## Contracts 6 | 7 | * [`CurvePool`](CurvePool.vy): Curve [pool contract](https://github.com/curvefi/curve-contract) for two plain coins. 8 | * [`CurveRewards`](CurveRewards.sol): Synthetix [LP Rewards](https://etherscan.io/address/0xdcb6a51ea3ca5d3fd898fd6564757c7aaec3ca92#code) contract. 9 | * [`ERC20LP`](ERC20LP.vy): Curve LP ERC20. 10 | * [`UnitVault`](UnitVault.vy): Minimal mock of [unit.xyz](https://unit.xyz/) [`Vault`](https://github.com/unitprotocol/core/blob/master/contracts/Vault.sol) contract. 11 | -------------------------------------------------------------------------------- /contracts/testing/UnitVault.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.11 2 | 3 | from vyper.interfaces import ERC20 4 | 5 | # asset -> owner -> amount 6 | collaterals: public(HashMap[address, HashMap[address, uint256]]) 7 | 8 | token: address 9 | 10 | 11 | @external 12 | def set_token(_token: address): 13 | self.token = _token 14 | 15 | @external 16 | def deposit(_amount: uint256): 17 | ERC20(self.token).transferFrom(msg.sender, self, _amount) 18 | self.collaterals[self.token][msg.sender] += _amount 19 | 20 | 21 | @external 22 | def withdraw(_amount: uint256): 23 | self.collaterals[self.token][msg.sender] -= _amount 24 | ERC20(self.token).transfer(msg.sender, _amount) 25 | 26 | 27 | @external 28 | def liquidate(_user: address, _amount: uint256): 29 | total: uint256 = self.collaterals[self.token][_user] 30 | self.collaterals[self.token][_user] = 0 31 | ERC20(self.token).transfer(_user, total - _amount) 32 | ERC20(self.token).transfer(msg.sender, _amount) 33 | -------------------------------------------------------------------------------- /contracts/vests/README.md: -------------------------------------------------------------------------------- 1 | # curve-dao-contracts/contracts/vests 2 | 3 | Contracts for vesting CRV. 4 | 5 | ## Contracts 6 | 7 | * [`VestingEscrow`](VestingEscrow.vy): Vests CRV tokens for multiple addresses over multiple vesting periods 8 | * [`VestingEscrowFactory`](VestingEscrowFactory.vy): Factory to store CRV and deploy many simplified vesting contracts 9 | * [`VestingEscrowSimple`](VestingEscrowSimple.vy): Simplified vesting contract that holds CRV for a single address 10 | -------------------------------------------------------------------------------- /deployment-logs/2020-05-20/deploy_rinkeby.log: -------------------------------------------------------------------------------- 1 | Deploying with: 0xFD3DeCC0cF498bb9f54786cb65800599De505706 2 | 3 | Transaction sent: 0x108090bf0deb868e450bdbfd534a51f52edd839a9f68747619f4bcbc1c211677 4 | Gas price: 1.0 gwei Gas limit: 1284741 5 | Waiting for confirmation... 6 | ERC20CRV.constructor confirmed - Block: 6525224 Gas used: 1284741 (100.00%) 7 | ERC20CRV deployed at: 0x0Ae0dB2f215f8D7c63fD8e7B64974efBc83bE2D8 8 | 9 | 10 | Transaction sent: 0x513ce77c2c2fb1a3e390670fb7fd8034e130d5f308d9342658a4e18c4b13a371 11 | Gas price: 1.0 gwei Gas limit: 2245448 12 | Waiting for confirmation... 13 | VotingEscrow.constructor confirmed - Block: 6525225 Gas used: 2245448 (100.00%) 14 | VotingEscrow deployed at: 0x0B48e841e90803326767fA55A877acC93556f450 15 | -------------------------------------------------------------------------------- /deployment-logs/2020-05-20_1/deploy_rinkeby.log: -------------------------------------------------------------------------------- 1 | Deploying with: 0xFD3DeCC0cF498bb9f54786cb65800599De505706 2 | 3 | Transaction sent: 0x3bbd64f30f68d4232aee2ce847f6b66c6456f36e9df4d1eb2fd455f0d89aea9b 4 | Gas price: 1.0 gwei Gas limit: 1284741 5 | Waiting for confirmation... 6 | ERC20CRV.constructor confirmed - Block: 6525910 Gas used: 1284741 (100.00%) 7 | ERC20CRV deployed at: 0x1F268C7657e264884B56cbFd3e489d0526bF1BFF 8 | 9 | Transaction sent: 0x41a1e974c06db715b0a1b6d022b1642c12b8f09cde096e35311c1372d5651fa1 10 | Gas price: 1.0 gwei Gas limit: 2287883 11 | Waiting for confirmation... 12 | VotingEscrow.constructor confirmed - Block: 6525911 Gas used: 2287883 (100.00%) 13 | VotingEscrow deployed at: 0x61B3DdC8e0131707919Dad9BBC4fcc6420369ccC 14 | 15 | -------------------------------------------------------------------------------- /deployment-logs/2020-05-20_2/rinkeby.log: -------------------------------------------------------------------------------- 1 | Deploying with: 0xFD3DeCC0cF498bb9f54786cb65800599De505706 2 | 3 | Transaction sent: 0x6b500e72a08b8d9ef14477d18b82c83031aa6b6b29e24c5462048a29148cd2d9 4 | Gas price: 1.0 gwei Gas limit: 1284741 5 | Waiting for confirmation... 6 | ERC20CRV.constructor confirmed - Block: 6525978 Gas used: 1284741 (100.00%) 7 | ERC20CRV deployed at: 0x820cFd153F6B8E4f021e401E3C78cbEDb292dAB7 8 | 9 | Transaction sent: 0xa7efaa1021099168e62a18ef2adb64d7eaefe7542f0db5268ec69aab97e37876 10 | Gas price: 1.0 gwei Gas limit: 2316340 11 | Waiting for confirmation... 12 | VotingEscrow.constructor confirmed - Block: 6525979 Gas used: 2316340 (100.00%) 13 | VotingEscrow deployed at: 0x9aF19290962A72E53d3DccE0f68994D65f107430 14 | 15 | -------------------------------------------------------------------------------- /deployment-logs/2020-05-21/rinkeby.log: -------------------------------------------------------------------------------- 1 | Deploying with: 0xFD3DeCC0cF498bb9f54786cb65800599De505706 2 | 3 | Transaction sent: 0x4bc0f0358d3b96eba4ae4ddb45a8b267d2c312b7f6358b44273440cedf84b8cd 4 | Gas price: 1.0 gwei Gas limit: 1284741 5 | Waiting for confirmation... 6 | ERC20CRV.constructor confirmed - Block: 6526505 Gas used: 1284741 (100.00%) 7 | ERC20CRV deployed at: 0xCeCE4E7555A088629d8DF42193bFc995e8a99448 8 | 9 | Transaction sent: 0xaa4165ab10ec4b40a936b150d65ff72e78b75f462c4a0cba9a0cc4b49d720a65 10 | Gas price: 1.0 gwei Gas limit: 2316340 11 | Waiting for confirmation... 12 | VotingEscrow.constructor confirmed - Block: 6526506 Gas used: 2316340 (100.00%) 13 | VotingEscrow deployed at: 0x1B602019cBa74b033F6Deb566faef231dBB472bC 14 | 15 | Changing controller... 16 | Transaction sent: 0xc9251455530b25742389b9c86defbb6c8a2c2c7d8d3bf70e9190f543ec5c80c8 17 | Gas price: 1.0 gwei Gas limit: 27727 18 | Waiting for confirmation... 19 | VotingEscrow.changeController confirmed - Block: 6526507 Gas used: 27727 (100.00%) 20 | 21 | Sending coins... 22 | Transaction sent: 0xd5569b972e3add6ffe652ff001d97fd7ae97b552509b58d8be37223692a246ca 23 | Gas price: 1.0 gwei Gas limit: 50823 24 | Waiting for confirmation... 25 | ERC20CRV.transfer confirmed - Block: 6526508 Gas used: 50823 (100.00%) 26 | -------------------------------------------------------------------------------- /deployment-logs/2020-05-24/abis/liquidity_gauge.abi: -------------------------------------------------------------------------------- 1 | [{"constant": false, "inputs": [{"name": "crv_addr", "type": "address"}, {"name": "lp_addr", "type": "address"}, {"name": "controller_addr", "type": "address"}], "outputs": [], "payable": false, "type": "constructor", "name": "constructor"}, {"constant": false, "gas": 56980807, "inputs": [{"name": "addr", "type": "address"}], "name": "user_checkpoint", "outputs": [], "payable": false, "type": "function"}, {"constant": false, "gas": 57111532, "inputs": [{"name": "value", "type": "uint256"}], "name": "deposit", "outputs": [], "payable": false, "type": "function"}, {"constant": false, "gas": 57111539, "inputs": [{"name": "value", "type": "uint256"}], "name": "withdraw", "outputs": [], "payable": false, "type": "function"}, {"constant": true, "gas": 1271, "inputs": [], "name": "crv_token", "outputs": [{"name": "", "type": "address"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1301, "inputs": [], "name": "lp_token", "outputs": [{"name": "", "type": "address"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1331, "inputs": [], "name": "controller", "outputs": [{"name": "", "type": "address"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1515, "inputs": [{"name": "arg0", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1391, "inputs": [], "name": "totalSupply", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1575, "inputs": [{"name": "arg0", "type": "address"}], "name": "integrate_fraction", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2020-05-24/abis/minter.abi: -------------------------------------------------------------------------------- 1 | [{"constant": false, "inputs": [{"name": "_token", "type": "address"}, {"name": "_controller", "type": "address"}], "outputs": [], "payable": false, "type": "constructor", "name": "constructor"}, {"constant": false, "gas": 103561, "inputs": [{"name": "gauge_id", "type": "int128"}], "name": "mint", "outputs": [], "payable": false, "type": "function"}, {"constant": true, "gas": 1181, "inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1211, "inputs": [], "name": "controller", "outputs": [{"name": "", "type": "address"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1549, "inputs": [{"name": "arg0", "type": "address"}, {"name": "arg1", "type": "address"}], "name": "minted", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2020-05-24/contracts/Minter.vy: -------------------------------------------------------------------------------- 1 | contract Controller: 2 | def gauges(gauge_id: int128) -> address: constant 3 | 4 | contract Gauge: 5 | # Presumably, other gauges will provide the same interfaces 6 | def integrate_fraction(addr: address) -> uint256: constant 7 | def user_checkpoint(addr: address): modifying 8 | 9 | contract MERC20: 10 | def mint(_to: address, _value: uint256): modifying 11 | 12 | 13 | token: public(address) 14 | controller: public(address) 15 | 16 | minted: public(map(address, map(address, uint256))) # user -> gauge -> value 17 | 18 | 19 | @public 20 | def __init__(_token: address, _controller: address): 21 | self.token = _token 22 | self.controller = _controller 23 | 24 | 25 | @public 26 | @nonreentrant('lock') 27 | def mint(gauge_id: int128): 28 | """ 29 | Mint everything which belongs to msg.sender and send to them 30 | """ 31 | gauge_addr: address = Controller(self.controller).gauges(gauge_id) 32 | assert gauge_addr != ZERO_ADDRESS, "Gauge is not in controller" 33 | 34 | Gauge(gauge_addr).user_checkpoint(msg.sender) 35 | total_mint: uint256 = Gauge(gauge_addr).integrate_fraction(msg.sender) 36 | to_mint: uint256 = total_mint - self.minted[msg.sender][gauge_addr] 37 | 38 | if to_mint > 0: 39 | MERC20(self.token).mint(msg.sender, to_mint) 40 | self.minted[msg.sender][gauge_addr] = total_mint 41 | -------------------------------------------------------------------------------- /deployment-logs/2020-05-24/contracts/testing/ERC20.vy: -------------------------------------------------------------------------------- 1 | """ 2 | @notice Mock ERC20 for testing 3 | """ 4 | 5 | Transfer: event({_from: indexed(address), _to: indexed(address), _value: uint256}) 6 | Approval: event({_owner: indexed(address), _spender: indexed(address), _value: uint256}) 7 | 8 | name: public(string[64]) 9 | symbol: public(string[32]) 10 | decimals: public(uint256) 11 | balanceOf: public(map(address, uint256)) 12 | allowances: map(address, map(address, uint256)) 13 | total_supply: uint256 14 | 15 | 16 | @public 17 | def __init__(_name: string[64], _symbol: string[32], _decimals: uint256): 18 | self.name = _name 19 | self.symbol = _symbol 20 | self.decimals = _decimals 21 | 22 | 23 | @public 24 | @constant 25 | def totalSupply() -> uint256: 26 | return self.total_supply 27 | 28 | 29 | @public 30 | @constant 31 | def allowance(_owner : address, _spender : address) -> uint256: 32 | return self.allowances[_owner][_spender] 33 | 34 | 35 | @public 36 | def transfer(_to : address, _value : uint256) -> bool: 37 | self.balanceOf[msg.sender] -= _value 38 | self.balanceOf[_to] += _value 39 | log.Transfer(msg.sender, _to, _value) 40 | return True 41 | 42 | 43 | @public 44 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 45 | self.balanceOf[_from] -= _value 46 | self.balanceOf[_to] += _value 47 | self.allowances[_from][msg.sender] -= _value 48 | log.Transfer(_from, _to, _value) 49 | return True 50 | 51 | 52 | @public 53 | def approve(_spender : address, _value : uint256) -> bool: 54 | self.allowances[msg.sender][_spender] = _value 55 | log.Approval(msg.sender, _spender, _value) 56 | return True 57 | 58 | 59 | @public 60 | def _mint_for_testing(_value: uint256): 61 | self.total_supply += _value 62 | self.balanceOf[msg.sender] += _value 63 | log.Transfer(ZERO_ADDRESS, msg.sender, _value) 64 | -------------------------------------------------------------------------------- /deployment-logs/2020-05-26/abis/liquidity_gauge.abi: -------------------------------------------------------------------------------- 1 | [{"anonymous": false, "inputs": [{"indexed": true, "name": "provider", "type": "address"}, {"indexed": false, "name": "value", "type": "uint256"}], "name": "Deposit", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "provider", "type": "address"}, {"indexed": false, "name": "value", "type": "uint256"}], "name": "Withdraw", "type": "event"}, {"constant": false, "inputs": [{"name": "crv_addr", "type": "address"}, {"name": "lp_addr", "type": "address"}, {"name": "controller_addr", "type": "address"}], "outputs": [], "payable": false, "type": "constructor", "name": "constructor"}, {"constant": false, "gas": 56980807, "inputs": [{"name": "addr", "type": "address"}], "name": "user_checkpoint", "outputs": [], "payable": false, "type": "function"}, {"constant": false, "gas": 57113259, "inputs": [{"name": "value", "type": "uint256"}], "name": "deposit", "outputs": [], "payable": false, "type": "function"}, {"constant": false, "gas": 57113266, "inputs": [{"name": "value", "type": "uint256"}], "name": "withdraw", "outputs": [], "payable": false, "type": "function"}, {"constant": true, "gas": 1271, "inputs": [], "name": "crv_token", "outputs": [{"name": "", "type": "address"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1301, "inputs": [], "name": "lp_token", "outputs": [{"name": "", "type": "address"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1331, "inputs": [], "name": "controller", "outputs": [{"name": "", "type": "address"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1515, "inputs": [{"name": "arg0", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1391, "inputs": [], "name": "totalSupply", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1575, "inputs": [{"name": "arg0", "type": "address"}], "name": "integrate_fraction", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2020-05-26/abis/minter.abi: -------------------------------------------------------------------------------- 1 | [{"constant": false, "inputs": [{"name": "_token", "type": "address"}, {"name": "_controller", "type": "address"}], "outputs": [], "payable": false, "type": "constructor", "name": "constructor"}, {"constant": false, "gas": 103561, "inputs": [{"name": "gauge_id", "type": "int128"}], "name": "mint", "outputs": [], "payable": false, "type": "function"}, {"constant": true, "gas": 1181, "inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1211, "inputs": [], "name": "controller", "outputs": [{"name": "", "type": "address"}], "payable": false, "type": "function"}, {"constant": true, "gas": 1549, "inputs": [{"name": "arg0", "type": "address"}, {"name": "arg1", "type": "address"}], "name": "minted", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2020-05-26/contracts/Minter.vy: -------------------------------------------------------------------------------- 1 | contract Controller: 2 | def gauges(gauge_id: int128) -> address: constant 3 | 4 | contract Gauge: 5 | # Presumably, other gauges will provide the same interfaces 6 | def integrate_fraction(addr: address) -> uint256: constant 7 | def user_checkpoint(addr: address): modifying 8 | 9 | contract MERC20: 10 | def mint(_to: address, _value: uint256): modifying 11 | 12 | 13 | token: public(address) 14 | controller: public(address) 15 | 16 | minted: public(map(address, map(address, uint256))) # user -> gauge -> value 17 | 18 | 19 | @public 20 | def __init__(_token: address, _controller: address): 21 | self.token = _token 22 | self.controller = _controller 23 | 24 | 25 | @public 26 | @nonreentrant('lock') 27 | def mint(gauge_id: int128): 28 | """ 29 | Mint everything which belongs to msg.sender and send to them 30 | """ 31 | gauge_addr: address = Controller(self.controller).gauges(gauge_id) 32 | assert gauge_addr != ZERO_ADDRESS, "Gauge is not in controller" 33 | 34 | Gauge(gauge_addr).user_checkpoint(msg.sender) 35 | total_mint: uint256 = Gauge(gauge_addr).integrate_fraction(msg.sender) 36 | to_mint: uint256 = total_mint - self.minted[msg.sender][gauge_addr] 37 | 38 | if to_mint > 0: 39 | MERC20(self.token).mint(msg.sender, to_mint) 40 | self.minted[msg.sender][gauge_addr] = total_mint 41 | -------------------------------------------------------------------------------- /deployment-logs/2020-05-26/contracts/testing/ERC20.vy: -------------------------------------------------------------------------------- 1 | """ 2 | @notice Mock ERC20 for testing 3 | """ 4 | 5 | Transfer: event({_from: indexed(address), _to: indexed(address), _value: uint256}) 6 | Approval: event({_owner: indexed(address), _spender: indexed(address), _value: uint256}) 7 | 8 | name: public(string[64]) 9 | symbol: public(string[32]) 10 | decimals: public(uint256) 11 | balanceOf: public(map(address, uint256)) 12 | allowances: map(address, map(address, uint256)) 13 | total_supply: uint256 14 | 15 | 16 | @public 17 | def __init__(_name: string[64], _symbol: string[32], _decimals: uint256): 18 | self.name = _name 19 | self.symbol = _symbol 20 | self.decimals = _decimals 21 | 22 | 23 | @public 24 | @constant 25 | def totalSupply() -> uint256: 26 | return self.total_supply 27 | 28 | 29 | @public 30 | @constant 31 | def allowance(_owner : address, _spender : address) -> uint256: 32 | return self.allowances[_owner][_spender] 33 | 34 | 35 | @public 36 | def transfer(_to : address, _value : uint256) -> bool: 37 | self.balanceOf[msg.sender] -= _value 38 | self.balanceOf[_to] += _value 39 | log.Transfer(msg.sender, _to, _value) 40 | return True 41 | 42 | 43 | @public 44 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 45 | self.balanceOf[_from] -= _value 46 | self.balanceOf[_to] += _value 47 | self.allowances[_from][msg.sender] -= _value 48 | log.Transfer(_from, _to, _value) 49 | return True 50 | 51 | 52 | @public 53 | def approve(_spender : address, _value : uint256) -> bool: 54 | self.allowances[msg.sender][_spender] = _value 55 | log.Approval(msg.sender, _spender, _value) 56 | return True 57 | 58 | 59 | @public 60 | def _mint_for_testing(_value: uint256): 61 | self.total_supply += _value 62 | self.balanceOf[msg.sender] += _value 63 | log.Transfer(ZERO_ADDRESS, msg.sender, _value) 64 | -------------------------------------------------------------------------------- /deployments.json: -------------------------------------------------------------------------------- 1 | { 2 | "ERC20CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", 3 | "PoolProxy": "0xeCb456EA5365865EbAb8a2661B0c503410e9B347", 4 | "GaugeProxy": "0x519AFB566c05E00cfB9af73496D00217A630e4D5", 5 | "VotingEscrow": "0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2", 6 | "GaugeController": "0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB", 7 | "Minter": "0xd061D61a4d941c39E5453435B6345Dc261C2fcE0", 8 | "FeeDistributor": "0xA464e6DCda8AC41e03616F95f4BC98a13b8922Dc", 9 | "VestingEscrowFactory": { 10 | "advisors": "0xf22995a3EA2C83F6764c711115B23A88411CAfdd", 11 | "employees": "0x41Df5d28C7e801c4df0aB33421E2ed6ce52D2567", 12 | "community": "0xe3997288987E6297Ad550A69B31439504F513267" 13 | }, 14 | "gauges": { 15 | "LiquidityGauge": { 16 | "compound": "0x7ca5b0a2910B33e9759DC7dDB0413949071D7575", 17 | "busd": "0x69Fb7c45726cfE2baDeE8317005d3F94bE838840", 18 | "pax": "0x64E3C23bfc40722d3B649844055F1D51c1ac041d", 19 | "renbtc": "0xB1F2cdeC61db658F091671F5f199635aEF202CAC", 20 | "usdt": "0xBC89cd85491d81C6AD2954E6d0362Ee29fCa8F53", 21 | "Y": "0xFA712EE4788C042e2B7BB55E6cb8ec569C4530c1" 22 | }, 23 | "LiquidityGaugeReward": { 24 | "sbtc": "0x705350c4BcD35c9441419DdD5d2f097d7a55410F", 25 | "susd": "0xA90996896660DEcC6E997655E065b23788857849" 26 | } 27 | }, 28 | "burners": { 29 | "ABurner": "0xAbADFd391b3821f3C80D48a59229769D0b677d2E", 30 | "BTCBurner": "0x00702BbDEaD24C40647f235F15971dB0867F6bdB", 31 | "CBurner": "0x55858AdaBb9EA24dDd0678DB0E9b41D8bD48e7EE", 32 | "ETHBurner": "0xD782EbD4bAbd95c2D8255112eFc6DD865c849394", 33 | "EuroBurner": "0x3a16b6001201577CC67bDD8aAE5A105bbB035882", 34 | "LPBurner": "0xaa42C0CD9645A58dfeB699cCAeFBD30f19B1ff81", 35 | "MetaBurner": "0xE4b65889469ad896e866331f0AB5652C1EcfB3E6", 36 | "SynthBurner": "0x67a0213310202DBc2cbE788f4349B72fbA90f9Fa", 37 | "UnderlyingBurner": "0x786b374b5eef874279f4b7b4de16940e57301a58", 38 | "USDNBurner": "0x06534b0BF7Ff378F162d4F348390BDA53b15fA35", 39 | "YBurner": "0x94c866a48EF49Db7Bf24207F2752E69BA85F6287" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /doc/dao-overview.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/doc/dao-overview.pdf -------------------------------------------------------------------------------- /doc/inflation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/doc/inflation.pdf -------------------------------------------------------------------------------- /doc/readme.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/doc/readme.pdf -------------------------------------------------------------------------------- /doc/votelock.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/doc/votelock.pdf -------------------------------------------------------------------------------- /doc/watch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script compiles the document as soon as all the .md files in the project are updated. 4 | # In debian-based distros, it needs inotify-tools and (optionally) libnotify-bin 5 | 6 | inotifywait -m -e create ./ | while read dir event file; do 7 | if [[ $file =~ .*\.md ]] 8 | then 9 | pandoc README.md -o readme.pdf && notify-send "Markdown compiled" || notify-send "Markdown error" 10 | fi 11 | done 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "preinstall": "npm i -g ganache-cli@6.12.1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 100 3 | target-version = ['py36', 'py37', 'py38'] 4 | include = '\.pyi?$' 5 | exclude = ''' 6 | /( 7 | \.eggs 8 | | \.git 9 | | \.hg 10 | | \.mypy_cache 11 | | \.tox 12 | | \.venv 13 | | _build 14 | | buck-out 15 | | build 16 | | dist 17 | | env 18 | | venv 19 | )/ 20 | ''' 21 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | black 2 | eth-brownie 3 | flake8 4 | isort 5 | brownie-token-tester 6 | -------------------------------------------------------------------------------- /scripts/burners/burn_fantom.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from brownie import ZERO_ADDRESS, Contract, PoolProxySidechain, accounts 4 | 5 | 6 | def _get_pool_list(): 7 | sys.stdout.write("Getting list of pools and coins from registry...") 8 | sys.stdout.flush() 9 | 10 | provider = Contract("0x0000000022D53366457F9d5E68Ec105046FC4383") 11 | registry = Contract(provider.get_registry()) 12 | 13 | pool_count = registry.pool_count() 14 | pool_list = [] 15 | coin_list = set() 16 | for i in range(pool_count): 17 | sys.stdout.write(f"\rGetting list of pools and coins from registry ({i+1}/{pool_count})...") 18 | sys.stdout.flush() 19 | swap = Contract(registry.pool_list(i)) 20 | pool_list.append(swap) 21 | coin_list.update([i for i in registry.get_coins(swap) if i != ZERO_ADDRESS]) 22 | 23 | print() 24 | return pool_list, coin_list 25 | 26 | 27 | def main(): 28 | acct = accounts.load("curve-deploy") 29 | 30 | proxy = PoolProxySidechain.at("0xffbACcE0CC7C19d46132f1258FC16CF6871D153c") 31 | usdt = Contract("0x049d68029688eabf473097a2fc38ef61633a3c7a") 32 | 33 | pool_list, coin_list = _get_pool_list() 34 | 35 | while pool_list: 36 | to_claim = pool_list[:20] 37 | pool_list = pool_list[20:] 38 | if len(to_claim) < 20: 39 | to_claim += [ZERO_ADDRESS] * (20 - len(to_claim)) 40 | proxy.withdraw_many(to_claim, {"from": acct}) 41 | 42 | coin_list.remove(usdt.address) 43 | coin_list.add("0x58e57ca18b7a47112b877e31929798cd3d703b0f") # tricrypto 44 | coin_list = list(coin_list) 45 | 46 | to_burn = [] 47 | while coin_list: 48 | coin = Contract(coin_list.pop()) 49 | if coin.balanceOf(proxy) == 0: 50 | continue 51 | to_burn.append(coin) 52 | to_burn_padded = to_burn + [ZERO_ADDRESS] * (20 - len(to_burn)) 53 | if not coin_list or proxy.burn_many.estimate_gas(to_burn_padded, {"from": acct}) >= 4000000: 54 | proxy.burn_many(to_burn_padded, {"from": acct}) 55 | to_burn.clear() 56 | 57 | proxy.bridge(usdt, {"from": acct}) 58 | -------------------------------------------------------------------------------- /scripts/burners/burn_polygon.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from brownie import ZERO_ADDRESS, Contract, PoolProxySidechain, accounts 4 | 5 | 6 | def _get_pool_list(): 7 | sys.stdout.write("Getting list of pools and coins from registry...") 8 | sys.stdout.flush() 9 | 10 | provider = Contract("0x0000000022D53366457F9d5E68Ec105046FC4383") 11 | registry = Contract(provider.get_registry()) 12 | 13 | pool_count = registry.pool_count() 14 | pool_list = [] 15 | coin_list = set() 16 | for i in range(pool_count): 17 | sys.stdout.write(f"\rGetting list of pools and coins from registry ({i+1}/{pool_count})...") 18 | sys.stdout.flush() 19 | swap = Contract(registry.pool_list(i)) 20 | pool_list.append(swap) 21 | coin_list.update([i for i in registry.get_coins(swap) if i != ZERO_ADDRESS]) 22 | 23 | print() 24 | return pool_list, coin_list 25 | 26 | 27 | def main(): 28 | acct = accounts.load("curve-deploy") 29 | 30 | proxy = PoolProxySidechain.at("0xd6930b7f661257DA36F93160149b031735237594") 31 | usdc = Contract("0x2791bca1f2de4661ed88a30c99a7a9449aa84174") 32 | 33 | pool_list, coin_list = _get_pool_list() 34 | 35 | while pool_list: 36 | to_claim = pool_list[:20] 37 | pool_list = pool_list[20:] 38 | if len(to_claim) < 20: 39 | to_claim += [ZERO_ADDRESS] * (20 - len(to_claim)) 40 | proxy.withdraw_many(to_claim, {"from": acct, "gas_price": "30 gwei"}) 41 | 42 | coin_list.discard(usdc.address) 43 | coin_list.add("0xdAD97F7713Ae9437fa9249920eC8507e5FbB23d3") # tricrypto3 44 | coin_list = list(coin_list) 45 | 46 | to_burn = [] 47 | while coin_list: 48 | coin = Contract(coin_list.pop()) 49 | if coin.balanceOf(proxy) == 0: 50 | continue 51 | to_burn.append(coin) 52 | to_burn_padded = to_burn + [ZERO_ADDRESS] * (20 - len(to_burn)) 53 | if not coin_list or proxy.burn_many.estimate_gas(to_burn_padded, {"from": acct}) >= 4000000: 54 | proxy.burn_many(to_burn_padded, {"from": acct, "gas_price": "30 gwei"}) 55 | to_burn.clear() 56 | 57 | amount = usdc.balanceOf(proxy) 58 | tx = proxy.bridge(usdc, {"from": acct, "gas_price": "30 gwei"}) 59 | print(f"Burning phase 1 complete!\nAmount: {amount/1e6:,.2f} USDC\nBridge txid: {tx.txid}") 60 | print( 61 | "\nUse `brownie run burners/exit_polygon --network mainnet` to claim on ETH" 62 | " once the checkpoint is added." 63 | ) 64 | -------------------------------------------------------------------------------- /scripts/burners/simulate_fee_distro.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | from brownie import Contract, FeeDistributor, accounts, chain 5 | 6 | 7 | def main(): 8 | alice = accounts[0] 9 | fee_token = Contract("0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490") 10 | voting_escrow = Contract("0x5f3b5dfeb7b28cdbd7faba78963ee202a494e2a2") 11 | 12 | # sept 17, 2020 - 2 days before admin fee collection begins 13 | start_time = 1600300800 14 | distributor = FeeDistributor.deploy( 15 | voting_escrow, start_time, fee_token, alice, alice, {"from": alice} 16 | ) 17 | 18 | # transfer 2m USD of 3CRV 19 | fee_token.mint( 20 | distributor, 2000000 * 10 ** 18, {"from": "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7"} 21 | ) 22 | 23 | distributor.checkpoint_token() 24 | distributor.checkpoint_total_supply() 25 | chain.sleep(86400 * 14) 26 | distributor.checkpoint_token() 27 | distributor.checkpoint_total_supply() 28 | 29 | with Path("votelocks-11237343.json").open() as fp: 30 | data = json.load(fp) 31 | data = set([i["provider"] for i in data]) 32 | 33 | for c, acct in enumerate(data): 34 | print(f"Claiming, {c}/{len(data)}") 35 | 36 | # ensure we claim up to the user's max epoch, some accounts require multiple claims 37 | max_epoch = voting_escrow.user_point_epoch(acct) 38 | epoch = 0 39 | 40 | while epoch < max_epoch: 41 | distributor.claim({"from": acct}) 42 | epoch = distributor.user_epoch_of(acct) 43 | 44 | amount = fee_token.balanceOf(distributor) 45 | print(f"Remaining fee balance: ${amount/1e18:,.2f}") 46 | -------------------------------------------------------------------------------- /scripts/deployment/transfer_dao_ownership.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from brownie import ERC20CRV, GaugeController, PoolProxy, VotingEscrow 4 | 5 | from . import deployment_config as config 6 | 7 | 8 | def live(): 9 | admin, _ = config.get_live_admin() 10 | with open(config.DEPLOYMENTS_JSON) as fp: 11 | deployments = json.load(fp) 12 | 13 | transfer_ownership( 14 | admin, 15 | config.ARAGON_AGENT, 16 | deployments["GaugeController"], 17 | deployments["VotingEscrow"], 18 | deployments["PoolProxy"], 19 | deployments["ERC20CRV"], 20 | config.REQUIRED_CONFIRMATIONS, 21 | ) 22 | 23 | 24 | def development(): 25 | # only works on a forked mainnet after the previous stages have been completed 26 | admin, _ = config.get_live_admin() 27 | with open(config.DEPLOYMENTS_JSON) as fp: 28 | deployments = json.load(fp) 29 | 30 | transfer_ownership( 31 | admin, 32 | config.ARAGON_AGENT, 33 | deployments["GaugeController"], 34 | deployments["VotingEscrow"], 35 | deployments["PoolProxy"], 36 | deployments["ERC20CRV"], 37 | ) 38 | 39 | 40 | def transfer_ownership( 41 | admin, new_admin, gauge_controller, voting_escrow, pool_proxy, erc20crv, confs=1 42 | ): 43 | gauge_controller = GaugeController.at(gauge_controller) 44 | voting_escrow = VotingEscrow.at(voting_escrow) 45 | pool_proxy = PoolProxy.at(pool_proxy) 46 | erc20crv = ERC20CRV.at(erc20crv) 47 | 48 | gauge_controller.commit_transfer_ownership(new_admin, {"from": admin, "required_confs": confs}) 49 | gauge_controller.apply_transfer_ownership({"from": admin, "required_confs": confs}) 50 | 51 | voting_escrow.commit_transfer_ownership(new_admin, {"from": admin, "required_confs": confs}) 52 | voting_escrow.apply_transfer_ownership({"from": admin, "required_confs": confs}) 53 | 54 | pool_proxy.commit_set_admins( 55 | new_admin, new_admin, new_admin, {"from": admin, "required_confs": confs} 56 | ) 57 | pool_proxy.apply_set_admins({"from": admin, "required_confs": confs}) 58 | 59 | erc20crv.set_admin(new_admin, {"from": admin, "required_confs": confs}) 60 | -------------------------------------------------------------------------------- /scripts/deployment/transfer_pool_ownership.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from brownie import CurvePool, accounts, chain 4 | 5 | from . import deployment_config as config 6 | 7 | POOLS = [ 8 | "0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56", 9 | "0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C", 10 | "0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51", 11 | "0x79a8C46DeA5aDa233ABaFFD40F3A0A2B1e5A4F27", 12 | "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD", 13 | "0x06364f10B501e868329afBc005b3492902d6C763", 14 | "0x93054188d876f558f4a66B2EF1d97d16eDf0895B", 15 | "0x7fC77b5c7614E1533320Ea6DDc2Eb61fa00A9714", 16 | ] 17 | 18 | 19 | def live(): 20 | admin = accounts.at("0xC447FcAF1dEf19A583F97b3620627BF69c05b5fB") 21 | with open(config.DEPLOYMENTS_JSON) as fp: 22 | pool_proxy = json.load(fp)["PoolProxy"] 23 | 24 | transfer_ownership(admin, pool_proxy, config.REQUIRED_CONFIRMATIONS) 25 | 26 | 27 | def development(): 28 | # only works on a forked mainnet 29 | admin = accounts.at("0xC447FcAF1dEf19A583F97b3620627BF69c05b5fB") 30 | new_admin = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" 31 | 32 | transfer_ownership(admin, new_admin, 1) 33 | chain.sleep(86400 * 3 + 1) 34 | transfer_ownership(admin, new_admin, 1) 35 | sanity_check(new_admin) 36 | 37 | 38 | def transfer_ownership(admin, new_admin, confs): 39 | for addr in POOLS: 40 | contract = CurvePool.at(addr) 41 | 42 | if contract.owner() != admin: 43 | print(f"ERROR: {admin} is not the owner of {addr}") 44 | continue 45 | 46 | deadline = contract.transfer_ownership_deadline() 47 | 48 | if deadline == 0: 49 | contract.commit_transfer_ownership(new_admin, {"from": admin}) 50 | print(f"SUCCESS: Ownership transfer of {addr} has been initiated") 51 | elif deadline < chain.time(): 52 | contract.apply_transfer_ownership({"from": admin}) 53 | print(f"SUCCESS: Ownership transfer of {addr} is complete") 54 | else: 55 | print( 56 | f"ERROR: Transfer deadline for {addr} not passed, you must " 57 | f"wait another {deadline - chain.time()} seconds" 58 | ) 59 | 60 | 61 | def sanity_check(owner): 62 | for addr in POOLS: 63 | contract = CurvePool.at(addr) 64 | 65 | if contract.owner() != owner: 66 | raise ValueError(f"Unexpected owner for {addr}") 67 | -------------------------------------------------------------------------------- /scripts/sidechain/deploy_gauge.py: -------------------------------------------------------------------------------- 1 | from brownie import ( 2 | ZERO_ADDRESS, 3 | ChildChainStreamer, 4 | Contract, 5 | RewardClaimer, 6 | RewardsOnlyGauge, 7 | accounts, 8 | ) 9 | 10 | CRV = { 11 | "ftm": "0x1E4F97b9f9F913c46F1632781732927B9019C68b", 12 | "polygon": "0x172370d5cd63279efa6d502dab29171933a610af", 13 | } 14 | 15 | STREAM_DEPLOYER = { 16 | "ftm": "0x0000c13f1d4a5832120a895d7484660d31fb784f", 17 | "polygon": "0x0000d7b833bb2b5caa98be580d65a9236d6cc5ea", 18 | } 19 | CONFS = 3 20 | 21 | 22 | def main(network_name, lp_token): 23 | deployer = accounts.at("0x7EeAC6CDdbd1D0B8aF061742D41877D7F707289a", True) 24 | stream_deployer = accounts.at(STREAM_DEPLOYER[network_name], True) 25 | crv = Contract(CRV[network_name]) 26 | 27 | gauge = RewardsOnlyGauge.deploy(deployer, lp_token, {"from": deployer, "required_confs": CONFS}) 28 | claimer = RewardClaimer.deploy(deployer, gauge, {"from": deployer, "required_confs": CONFS}) 29 | streamer = ChildChainStreamer.deploy( 30 | deployer, claimer, crv, {"from": stream_deployer, "required_confs": CONFS} 31 | ) 32 | claimer.set_reward_data(0, streamer, crv, {"from": deployer, "required_confs": CONFS}) 33 | 34 | gauge.set_rewards( 35 | claimer, 36 | f"0x0000000000000000{claimer.get_reward.signature[2:]}" + "00" * 20, 37 | [crv] + [ZERO_ADDRESS] * 7, 38 | {"from": deployer, "required_confs": CONFS}, 39 | ) 40 | -------------------------------------------------------------------------------- /scripts/stats/gini.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pylab 3 | import requests 4 | from brownie import web3 5 | 6 | START_BLOCK = 10647813 + 86400 7 | graph_url = "https://api.thegraph.com/subgraphs/name/pengiundev/curve-votingescrow3" 8 | query = { 9 | "query": """query ($block: Int!, $first: Int!, $skip: Int!) {\n userBalances(orderBy: weight, orderDirection: desc, first: $first, skip: $skip, block: {number: $block}) {\n id\n startTx\n user\n CRVLocked\n lock_start\n unlock_time\n weight\n __typename\n }\n}\n""", # noqa 10 | "variables": {"block": None, "first": 1000, "skip": 0}, 11 | } 12 | 13 | 14 | def gini(x): 15 | # from https://stackoverflow.com/questions/39512260/calculating-gini-coefficient-in-python-numpy 16 | # (Warning: This is a concise implementation, but it is O(n**2) 17 | # in time and memory, where n = len(x). *Don't* pass in huge 18 | # samples!) 19 | 20 | # Mean absolute difference 21 | mad = np.abs(np.subtract.outer(x, x)).mean() 22 | # Relative mean absolute difference 23 | rmad = mad / np.mean(x) 24 | # Gini coefficient 25 | g = 0.5 * rmad 26 | return g 27 | 28 | 29 | def main(): 30 | current_block = web3.eth.blockNumber 31 | blocks = np.linspace(START_BLOCK, current_block, 50) 32 | ginis = [] 33 | for block in blocks: 34 | query["variables"]["block"] = int(block) 35 | while True: 36 | try: 37 | resp = requests.post(graph_url, json=query).json() 38 | weights = [int(u["weight"]) / 1e18 for u in resp["data"]["userBalances"]] 39 | except KeyError: 40 | print("Error") 41 | continue 42 | break 43 | ginis.append(gini(weights)) 44 | print(block, ginis[-1]) 45 | 46 | pylab.plot(blocks, ginis) 47 | pylab.title("Gini coefficient") 48 | pylab.xlabel("Block number") 49 | pylab.ylabel("veCRV Gini coefficient") 50 | pylab.show() 51 | -------------------------------------------------------------------------------- /scripts/stats/plot_vecrv.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pylab 3 | from brownie import Contract, web3 4 | 5 | START_BLOCK = 10647813 6 | 7 | 8 | def main(): 9 | vecrv = Contract("0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2") 10 | current_block = web3.eth.blockNumber 11 | blocks = np.linspace(START_BLOCK, current_block, 100) 12 | powers = [vecrv.totalSupplyAt(int(block)) / 1e18 for block in blocks] 13 | 14 | pylab.plot(blocks, powers) 15 | pylab.xlabel("Block number") 16 | pylab.ylabel("Total veCRV") 17 | pylab.show() 18 | -------------------------------------------------------------------------------- /scripts/stats/show_weekly_fees.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from time import time 3 | 4 | import pylab # Requires matplotlib 5 | from brownie import Contract 6 | 7 | WEEK = 86400 * 7 8 | 9 | 10 | def main(): 11 | distributor = Contract("0xA464e6DCda8AC41e03616F95f4BC98a13b8922Dc") 12 | tri_pool = Contract("0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7") 13 | t = int(time()) // WEEK * WEEK 14 | virtual_price = tri_pool.get_virtual_price() / 1e18 15 | 16 | output = [] 17 | while True: 18 | fees = distributor.tokens_per_week(t) 19 | if fees == 0 and len(output) > 0: 20 | break 21 | d = datetime.fromtimestamp(t) 22 | output.append((d, fees)) 23 | t -= WEEK 24 | if output[0][1] == 0: 25 | output = output[1:] 26 | dates = [] 27 | fees = [] 28 | for d, fee in output[::-1]: 29 | dates.append(d) 30 | fees.append(fee * virtual_price / 1e18) 31 | print("{0}|\t${1:.2f}".format(d, fees[-1])) 32 | 33 | # pylab.bar(range(len(fees)), fees) 34 | # pylab.xticks(range(len(dates)), [d.strftime("%d-%m-%y") for d in dates]) 35 | 36 | pylab.semilogy(dates, fees) 37 | pylab.xlabel("Distribution date") 38 | pylab.ylabel("Fees distributed (USD)") 39 | pylab.ylim(0, max(fees) * 1.1) 40 | pylab.yticks( 41 | [0.2e6, 0.4e6, 0.6e6, 0.8e6, 1e6, 2e6], labels=["200k", "400k", "600k", "800k", "1M", "2M"] 42 | ) 43 | pylab.grid() 44 | pylab.show() 45 | -------------------------------------------------------------------------------- /scripts/voting/decode_vote.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from brownie import Contract 4 | from hexbytes import HexBytes 5 | 6 | warnings.filterwarnings("ignore") 7 | 8 | # the vote ID you wish to decrypt 9 | VOTE_ID = 29 10 | 11 | # address of the contract where the vote was created 12 | # ownership votes: 0xe478de485ad2fe566d49342cbd03e49ed7db3356 13 | # parameter votes: 0xbcff8b0b9419b9a88c44546519b1e909cf330399 14 | VOTING_ADDRESS = "0xe478de485ad2fe566d49342cbd03e49ed7db3356" 15 | 16 | 17 | def main(vote_id=VOTE_ID): 18 | aragon = Contract(VOTING_ADDRESS) 19 | 20 | script = HexBytes(aragon.getVote(vote_id)["script"]) 21 | 22 | idx = 4 23 | while idx < len(script): 24 | target = Contract(script[idx : idx + 20]) 25 | idx += 20 26 | length = int(script[idx : idx + 4].hex(), 16) 27 | idx += 4 28 | calldata = script[idx : idx + length] 29 | idx += length 30 | fn, inputs = target.decode_input(calldata) 31 | if calldata[:4].hex() == "0xb61d27f6": 32 | agent_target = Contract(inputs[0]) 33 | fn, inputs = agent_target.decode_input(inputs[2]) 34 | print( 35 | f"Call via agent ({target}):\n ├─ To: {agent_target}\n" 36 | f" ├─ Function: {fn}\n └─ Inputs: {inputs}\n" 37 | ) 38 | else: 39 | print(f"Direct call:\n ├─ To: {target}\n ├─ Function: {fn}\n └─ Inputs: {inputs}") 40 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 100 3 | ignore = E203,W503,W605 4 | 5 | [tool:isort] 6 | force_grid_wrap = 0 7 | include_trailing_comma = True 8 | line_length = 100 9 | multi_line_output = 3 10 | use_parentheses = True 11 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/__init__.py -------------------------------------------------------------------------------- /tests/fork/Burners/test_aburner.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract 3 | 4 | 5 | @pytest.fixture(scope="module") 6 | def burner(ABurner, alice, receiver): 7 | yield ABurner.deploy(receiver, receiver, alice, alice, {"from": alice}) 8 | 9 | 10 | ctokens = ( 11 | ("0x028171bCA77440897B824Ca71D1c56caC55b68A3", "aDAI"), 12 | ("0xBcca60bB61934080951369a648Fb03DF4F96263C", "aUSDC"), 13 | ("0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811", "aUSDT"), 14 | ) 15 | 16 | 17 | @pytest.mark.parametrize("token", [i[0] for i in ctokens], ids=[i[1] for i in ctokens]) 18 | @pytest.mark.parametrize("burner_balance", (True, False)) 19 | @pytest.mark.parametrize("caller_balance", (True, False)) 20 | def test_ctoken_unwrap( 21 | MintableTestToken, alice, receiver, burner, token, burner_balance, caller_balance 22 | ): 23 | wrapped = MintableTestToken(token) 24 | amount = 10 ** wrapped.decimals() 25 | underlying = Contract(wrapped.UNDERLYING_ASSET_ADDRESS()) 26 | 27 | if caller_balance: 28 | wrapped._mint_for_testing(alice, amount, {"from": alice}) 29 | wrapped.approve(burner, 2 ** 256 - 1, {"from": alice}) 30 | 31 | if burner_balance: 32 | wrapped._mint_for_testing(burner, amount, {"from": alice}) 33 | 34 | burner.burn(wrapped, {"from": alice}) 35 | 36 | if burner_balance or caller_balance: 37 | assert wrapped.balanceOf(alice) == 0 38 | assert wrapped.balanceOf(receiver) == 0 39 | assert wrapped.balanceOf(burner) == 0 40 | 41 | assert underlying.balanceOf(alice) == 0 42 | assert underlying.balanceOf(receiver) > 0 43 | assert underlying.balanceOf(burner) == 0 44 | -------------------------------------------------------------------------------- /tests/fork/Burners/test_cburner.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract 3 | 4 | 5 | @pytest.fixture(scope="module") 6 | def burner(CBurner, alice, receiver): 7 | yield CBurner.deploy(receiver, receiver, alice, alice, {"from": alice}) 8 | 9 | 10 | ctokens = ( 11 | ("0x39aa39c021dfbae8fac545936693ac917d5e7563", "cDAI"), 12 | ("0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643", "cUSDC"), 13 | ) 14 | 15 | 16 | @pytest.mark.parametrize("token", [i[0] for i in ctokens], ids=[i[1] for i in ctokens]) 17 | @pytest.mark.parametrize("burner_balance", (True, False)) 18 | @pytest.mark.parametrize("caller_balance", (True, False)) 19 | def test_ctoken_unwrap( 20 | MintableTestToken, alice, receiver, burner, token, burner_balance, caller_balance 21 | ): 22 | wrapped = MintableTestToken(token) 23 | amount = 10 ** wrapped.decimals() 24 | underlying = Contract(wrapped.underlying()) 25 | 26 | if caller_balance: 27 | wrapped._mint_for_testing(alice, amount, {"from": alice}) 28 | wrapped.approve(burner, 2 ** 256 - 1, {"from": alice}) 29 | 30 | if burner_balance: 31 | wrapped._mint_for_testing(burner, amount, {"from": alice}) 32 | 33 | burner.burn(wrapped, {"from": alice}) 34 | 35 | if burner_balance or caller_balance: 36 | assert wrapped.balanceOf(alice) == 0 37 | assert wrapped.balanceOf(receiver) == 0 38 | assert wrapped.balanceOf(burner) == 0 39 | 40 | assert underlying.balanceOf(alice) == 0 41 | assert underlying.balanceOf(receiver) > 0 42 | assert underlying.balanceOf(burner) == 0 43 | -------------------------------------------------------------------------------- /tests/fork/Burners/test_eth_burner.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import ETH_ADDRESS 3 | 4 | 5 | @pytest.fixture(scope="module") 6 | def burner(ETHBurner, alice, receiver): 7 | yield ETHBurner.deploy(receiver, receiver, alice, alice, {"from": alice}) 8 | 9 | 10 | tokens = ( 11 | ("0x5e74C9036fb86BD7eCdcb084a0673EFc32eA31cb", "sETH"), 12 | ("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "stETH"), 13 | ("0xE95A203B1a91a908F9B9CE46459d101078c2c3cb", "aETH"), 14 | ) 15 | 16 | 17 | @pytest.mark.parametrize("token", [i[0] for i in tokens], ids=[i[1] for i in tokens]) 18 | @pytest.mark.parametrize("burner_balance", (True, False)) 19 | @pytest.mark.parametrize("caller_balance", (True, False)) 20 | def test_swap( 21 | MintableTestToken, SUSD, alice, receiver, burner, token, burner_balance, caller_balance 22 | ): 23 | coin = MintableTestToken(token) 24 | amount = 10 ** coin.decimals() 25 | 26 | if caller_balance: 27 | coin._mint_for_testing(alice, amount, {"from": alice}) 28 | coin.approve(burner, 2 ** 256 - 1, {"from": alice}) 29 | 30 | if burner_balance: 31 | coin._mint_for_testing(burner, amount, {"from": alice}) 32 | 33 | burner.burn(coin, {"from": alice}) 34 | 35 | if burner_balance or caller_balance: 36 | assert coin.balanceOf(alice) == 0 37 | assert coin.balanceOf(burner) == 0 38 | assert coin.balanceOf(receiver) == 0 39 | 40 | assert SUSD.balanceOf(alice) == 0 41 | assert SUSD.balanceOf(burner) > 0 42 | assert SUSD.balanceOf(receiver) == 0 43 | 44 | 45 | def test_swap_ether(MintableTestToken, SUSD, alice, receiver, burner): 46 | burner.burn(ETH_ADDRESS, {"from": alice, "value": "1 ether"}) 47 | 48 | assert alice.balance() == "99 ether" 49 | assert burner.balance() == 0 50 | assert receiver.balance() == 0 51 | 52 | assert SUSD.balanceOf(alice) == 0 53 | assert SUSD.balanceOf(burner) > 0 54 | assert SUSD.balanceOf(receiver) == 0 55 | 56 | 57 | def test_execute(SUSD, USDC, alice, burner, receiver): 58 | SUSD._mint_for_testing(burner, 10 ** 18, {"from": alice}) 59 | 60 | burner.execute({"from": alice}) 61 | 62 | assert SUSD.balanceOf(alice) == 0 63 | assert SUSD.balanceOf(burner) == 0 64 | assert SUSD.balanceOf(receiver) == 0 65 | 66 | assert USDC.balanceOf(alice) == 0 67 | assert USDC.balanceOf(burner) == 0 68 | assert USDC.balanceOf(receiver) > 0 69 | -------------------------------------------------------------------------------- /tests/fork/Burners/test_idle_burner.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract 3 | 4 | 5 | @pytest.fixture(scope="module") 6 | def burner(IdleBurner, alice, receiver): 7 | yield IdleBurner.deploy(receiver, receiver, alice, alice, {"from": alice}) 8 | 9 | 10 | ctokens = ( 11 | ("0x3fe7940616e5bc47b0775a0dccf6237893353bb4", "idleDAI"), 12 | ("0x5274891bEC421B39D23760c04A6755eCB444797C", "idleUSDC"), 13 | ("0xF34842d05A1c888Ca02769A633DF37177415C2f8", "idleUSDT"), 14 | ) 15 | 16 | 17 | @pytest.mark.parametrize("token", [i[0] for i in ctokens], ids=[i[1] for i in ctokens]) 18 | @pytest.mark.parametrize("burner_balance", (True, False)) 19 | @pytest.mark.parametrize("caller_balance", (True, False)) 20 | def test_ctoken_unwrap( 21 | MintableTestToken, alice, receiver, burner, token, burner_balance, caller_balance 22 | ): 23 | wrapped = MintableTestToken(token) 24 | amount = 10 ** wrapped.decimals() 25 | underlying = Contract(wrapped.token()) 26 | 27 | if caller_balance: 28 | wrapped._mint_for_testing(alice, amount, {"from": alice}) 29 | wrapped.approve(burner, 2 ** 256 - 1, {"from": alice}) 30 | 31 | if burner_balance: 32 | wrapped._mint_for_testing(burner, amount, {"from": alice}) 33 | 34 | burner.burn(wrapped, {"from": alice}) 35 | 36 | if burner_balance or caller_balance: 37 | assert wrapped.balanceOf(alice) == 0 38 | assert wrapped.balanceOf(receiver) == 0 39 | assert wrapped.balanceOf(burner) == 0 40 | 41 | assert underlying.balanceOf(alice) == 0 42 | assert underlying.balanceOf(receiver) > 0 43 | assert underlying.balanceOf(burner) == 0 44 | -------------------------------------------------------------------------------- /tests/fork/Burners/test_lp_burner.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="module") 5 | def btc_burner(BTCBurner, alice, receiver): 6 | yield BTCBurner.deploy(receiver, receiver, alice, alice, {"from": alice}) 7 | 8 | 9 | @pytest.fixture(scope="module") 10 | def lp_burner(LPBurner, alice, receiver): 11 | yield LPBurner.deploy(receiver, alice, alice, {"from": alice}) 12 | 13 | 14 | tokens = (("0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3", "sbtcCRV"),) 15 | 16 | 17 | @pytest.mark.parametrize("token", [i[0] for i in tokens], ids=[i[1] for i in tokens]) 18 | @pytest.mark.parametrize("burner_balance", (True, False)) 19 | @pytest.mark.parametrize("caller_balance", (True, False)) 20 | def test_swap( 21 | MintableTestToken, 22 | SBTC, 23 | alice, 24 | receiver, 25 | lp_burner, 26 | btc_burner, 27 | token, 28 | burner_balance, 29 | caller_balance, 30 | ): 31 | coin = MintableTestToken(token) 32 | amount = 10 ** coin.decimals() 33 | 34 | lp_burner.set_swap_data(token, SBTC, btc_burner) 35 | 36 | if caller_balance: 37 | coin._mint_for_testing(alice, amount, {"from": alice}) 38 | coin.approve(lp_burner, 2 ** 256 - 1, {"from": alice}) 39 | 40 | if burner_balance: 41 | coin._mint_for_testing(lp_burner, amount, {"from": alice}) 42 | 43 | lp_burner.burn(coin, {"from": alice}) 44 | 45 | if burner_balance or caller_balance: 46 | assert coin.balanceOf(alice) == 0 47 | assert coin.balanceOf(lp_burner) == 0 48 | assert coin.balanceOf(btc_burner) == 0 49 | assert coin.balanceOf(receiver) == 0 50 | 51 | assert SBTC.balanceOf(alice) == 0 52 | assert SBTC.balanceOf(lp_burner) == 0 53 | assert SBTC.balanceOf(btc_burner) > 0 54 | assert SBTC.balanceOf(receiver) == 0 55 | -------------------------------------------------------------------------------- /tests/fork/Burners/test_metaburner.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="module") 5 | def burner(MetaBurner, alice, receiver): 6 | yield MetaBurner.deploy(receiver, receiver, alice, alice, {"from": alice}) 7 | 8 | 9 | tokens = ( 10 | ("0x056fd409e1d7a124bd7017459dfea2f387b6d5cd", "GUSD"), 11 | ("0xdf574c24545e5ffecb9a659c229253d4111d87e1", "HUSD"), 12 | ("0x1c48f86ae57291f7686349f12601910bd8d470bb", "USDK"), 13 | ("0x674C6Ad92Fd080e4004b2312b45f796a192D27a0", "USDN"), 14 | ("0x0E2EC54fC0B509F445631Bf4b91AB8168230C752", "LinkUSD"), 15 | ("0xe2f2a5C287993345a840Db3B0845fbC70f5935a5", "MUSD"), 16 | ("0x196f4727526eA7FB1e17b2071B3d8eAA38486988", "RSV"), 17 | ("0x5bc25f649fc4e26069ddf4cf4010f9f706c23831", "DUSD"), 18 | ) 19 | 20 | 21 | @pytest.mark.parametrize("token", [i[0] for i in tokens], ids=[i[1] for i in tokens]) 22 | @pytest.mark.parametrize("burner_balance", (True, False)) 23 | @pytest.mark.parametrize("caller_balance", (True, False)) 24 | def test_swap( 25 | MintableTestToken, ThreeCRV, alice, receiver, burner, token, burner_balance, caller_balance 26 | ): 27 | wrapped = MintableTestToken(token) 28 | amount = 10 ** wrapped.decimals() 29 | 30 | if caller_balance: 31 | wrapped._mint_for_testing(alice, amount, {"from": alice}) 32 | wrapped.approve(burner, 2 ** 256 - 1, {"from": alice}) 33 | 34 | if burner_balance: 35 | wrapped._mint_for_testing(burner, amount, {"from": alice}) 36 | 37 | burner.burn(wrapped, {"from": alice}) 38 | 39 | if burner_balance or caller_balance: 40 | assert wrapped.balanceOf(alice) == 0 41 | assert wrapped.balanceOf(receiver) == 0 42 | assert wrapped.balanceOf(burner) == 0 43 | 44 | assert ThreeCRV.balanceOf(alice) == 0 45 | assert ThreeCRV.balanceOf(receiver) > 0 46 | assert ThreeCRV.balanceOf(burner) == 0 47 | -------------------------------------------------------------------------------- /tests/fork/Burners/test_usdn_burner.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="module") 5 | def burner(USDNBurner, alice, receiver, pool_proxy, swap): 6 | contract = USDNBurner.deploy(pool_proxy, receiver, receiver, alice, alice, {"from": alice}) 7 | pool_proxy.set_donate_approval(swap, contract, True, {"from": alice}) 8 | 9 | yield contract 10 | 11 | 12 | @pytest.fixture(scope="module") 13 | def swap(Contract): 14 | yield Contract("0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1") 15 | 16 | 17 | @pytest.fixture(scope="module") 18 | def pool_proxy(PoolProxy, chain, alice, swap): 19 | contract = PoolProxy.deploy(alice, alice, alice, {"from": alice}) 20 | swap.commit_transfer_ownership(contract, {"from": swap.owner()}) 21 | chain.sleep(86400 * 3) 22 | swap.apply_transfer_ownership({"from": swap.owner()}) 23 | 24 | yield contract 25 | 26 | 27 | @pytest.fixture(scope="module") 28 | def USDN(MintableTestToken): 29 | yield MintableTestToken("0x674C6Ad92Fd080e4004b2312b45f796a192D27a0") 30 | 31 | 32 | @pytest.mark.parametrize("burner_balance", (True, False)) 33 | @pytest.mark.parametrize("caller_balance", (True, False)) 34 | def test_swap( 35 | USDN, ThreeCRV, alice, receiver, burner, burner_balance, caller_balance, swap, pool_proxy 36 | ): 37 | amount = 0 38 | 39 | if caller_balance: 40 | amount += 10 ** 18 41 | USDN._mint_for_testing(alice, 10 ** 18, {"from": alice}) 42 | USDN.approve(burner, 2 ** 256 - 1, {"from": alice}) 43 | 44 | if burner_balance: 45 | amount += 10 ** 18 46 | USDN._mint_for_testing(burner, 10 ** 18, {"from": alice}) 47 | 48 | swap_balance = swap.balances(0) 49 | 50 | burner.burn(USDN, {"from": alice}) 51 | 52 | if burner_balance or caller_balance: 53 | assert USDN.balanceOf(alice) == 0 54 | assert USDN.balanceOf(receiver) == 0 55 | assert USDN.balanceOf(burner) == 0 56 | 57 | assert ThreeCRV.balanceOf(alice) == 0 58 | assert ThreeCRV.balanceOf(receiver) > 0 59 | assert ThreeCRV.balanceOf(burner) == 0 60 | 61 | assert swap.balances(0) == swap_balance + amount 62 | assert USDN.balanceOf(pool_proxy) > 0 63 | -------------------------------------------------------------------------------- /tests/fork/Burners/test_wrapped_burner.py: -------------------------------------------------------------------------------- 1 | def test_burner(WrappedBurner, WETH, alice, receiver): 2 | initial_balance = receiver.balance() 3 | burner = WrappedBurner.deploy(WETH, receiver, {"from": alice}) 4 | amount = 12 * 10 ** 18 5 | WETH._mint_for_testing(alice, amount, {"from": alice}) 6 | WETH.approve(burner, 2 ** 256 - 1, {"from": alice}) 7 | WETH._mint_for_testing(burner, 2 * amount, {"from": alice}) 8 | 9 | burner.burn(WETH, {"from": alice}) 10 | 11 | assert burner.balance() == 0 12 | assert WETH.balanceOf(burner) == 0 13 | 14 | assert receiver.balance() - initial_balance == 3 * amount 15 | assert WETH.balanceOf(receiver) == 0 16 | -------------------------------------------------------------------------------- /tests/fork/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie_tokens import MintableForkToken 3 | 4 | 5 | class _MintableTestToken(MintableForkToken): 6 | def __init__(self, address): 7 | super().__init__(address) 8 | 9 | 10 | @pytest.fixture(scope="session") 11 | def MintableTestToken(): 12 | yield _MintableTestToken 13 | 14 | 15 | @pytest.fixture(scope="module") 16 | def USDC(): 17 | yield _MintableTestToken("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") 18 | 19 | 20 | @pytest.fixture(scope="module") 21 | def ThreeCRV(): 22 | yield _MintableTestToken("0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490") 23 | 24 | 25 | @pytest.fixture(scope="module") 26 | def SUSD(): 27 | yield _MintableTestToken("0x57ab1ec28d129707052df4df418d58a2d46d5f51") 28 | 29 | 30 | @pytest.fixture(scope="module") 31 | def SBTC(): 32 | yield _MintableTestToken("0xfE18be6b3Bd88A2D2A7f928d00292E7a9963CfC6") 33 | 34 | 35 | @pytest.fixture(scope="module") 36 | def WETH(): 37 | yield _MintableTestToken("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") 38 | -------------------------------------------------------------------------------- /tests/fork/test_root_gauge.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | 5 | @pytest.fixture(scope="module") 6 | def root_gauge(GaugeController, Minter, RootGaugeArbitrum, alice, chain): 7 | gauge_controller = GaugeController.at("0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB") 8 | minter = Minter.at("0xd061D61a4d941c39E5453435B6345Dc261C2fcE0") 9 | 10 | root_gauge = RootGaugeArbitrum.deploy( 11 | minter, alice, 1000000, 990000000, 10000000000000, {"from": alice} 12 | ) 13 | 14 | gauge_controller.add_gauge(root_gauge, 0, 10 ** 18, {"from": gauge_controller.admin()}) 15 | 16 | chain.mine(timedelta=86400 * 14) 17 | return root_gauge 18 | 19 | 20 | def test_checkpoint(alice, interface, root_gauge): 21 | crv = interface.ERC20(root_gauge.crv_token()) 22 | 23 | balance_before = crv.balanceOf("0xa3A7B6F88361F48403514059F1F16C8E78d60EeC") 24 | root_gauge.checkpoint({"from": alice, "value": "0.001 ether"}) 25 | balance_after = crv.balanceOf("0xa3A7B6F88361F48403514059F1F16C8E78d60EeC") 26 | 27 | assert balance_after > balance_before 28 | 29 | 30 | def test_insufficient_ether(alice, root_gauge): 31 | with brownie.reverts(): 32 | root_gauge.checkpoint({"from": alice, "value": "0.0009 ether"}) 33 | 34 | 35 | def test_excess_ether(alice, root_gauge): 36 | root_gauge.checkpoint({"from": alice, "value": "0.00123 ether"}) 37 | assert root_gauge.balance() == "0.00023 ether" 38 | 39 | 40 | def test_excess_ether_multiple_checkpoints(alice, root_gauge, chain): 41 | root_gauge.checkpoint({"from": alice, "value": "0.0035 ether"}) 42 | chain.sleep(86400 * 7) 43 | root_gauge.checkpoint({"from": alice}) 44 | assert root_gauge.balance() == "0.0015 ether" 45 | 46 | 47 | def test_modify_gas(alice, root_gauge): 48 | root_gauge.set_arbitrum_fees(2000000, 50000000, 66000000000000, {"from": alice}) 49 | assert root_gauge.get_total_bridge_cost() == 2000000 * 50000000 + 66000000000000 50 | 51 | root_gauge.checkpoint({"from": alice, "value": 2000000 * 50000000 + 66000000000000}) 52 | assert root_gauge.balance() == 0 53 | 54 | 55 | def test_modify_gas_is_guarded(bob, root_gauge): 56 | with brownie.reverts(): 57 | root_gauge.set_arbitrum_fees(2000000, 50000000, 66000000000000, {"from": bob}) 58 | -------------------------------------------------------------------------------- /tests/integration/ERC20CRV/test_mint_integration.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | from brownie.test import given, strategy 4 | 5 | from tests.conftest import YEAR 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def initial_setup(chain, token): 10 | chain.sleep(86401) 11 | token.update_mining_parameters() 12 | 13 | 14 | @given(duration=strategy("uint", min_value=86500, max_value=YEAR)) 15 | def test_mint(accounts, chain, token, duration): 16 | token.set_minter(accounts[0], {"from": accounts[0]}) 17 | creation_time = token.start_epoch_time() 18 | initial_supply = token.totalSupply() 19 | rate = token.rate() 20 | chain.sleep(duration) 21 | 22 | amount = (chain.time() - creation_time) * rate 23 | token.mint(accounts[1], amount, {"from": accounts[0]}) 24 | 25 | assert token.balanceOf(accounts[1]) == amount 26 | assert token.totalSupply() == initial_supply + amount 27 | 28 | 29 | @given(duration=strategy("uint", min_value=86500, max_value=YEAR)) 30 | def test_overmint(accounts, chain, token, duration): 31 | token.set_minter(accounts[0], {"from": accounts[0]}) 32 | creation_time = token.start_epoch_time() 33 | rate = token.rate() 34 | chain.sleep(duration) 35 | 36 | with brownie.reverts("dev: exceeds allowable mint amount"): 37 | token.mint(accounts[1], (chain.time() - creation_time + 2) * rate, {"from": accounts[0]}) 38 | 39 | 40 | @given(durations=strategy("uint[5]", min_value=YEAR * 0.33, max_value=YEAR * 0.9)) 41 | def test_mint_multiple(accounts, chain, token, durations): 42 | token.set_minter(accounts[0], {"from": accounts[0]}) 43 | total_supply = token.totalSupply() 44 | balance = 0 45 | epoch_start = token.start_epoch_time() 46 | 47 | for time in durations: 48 | chain.sleep(time) 49 | 50 | if chain.time() - epoch_start > YEAR: 51 | token.update_mining_parameters({"from": accounts[0]}) 52 | epoch_start = token.start_epoch_time() 53 | 54 | amount = token.available_supply() - total_supply 55 | token.mint(accounts[1], amount, {"from": accounts[0]}) 56 | 57 | balance += amount 58 | total_supply += amount 59 | 60 | assert token.balanceOf(accounts[1]) == balance 61 | assert token.totalSupply() == total_supply 62 | -------------------------------------------------------------------------------- /tests/integration/FeeDistributor/test_checkpoint_ts.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie.test import given, strategy 3 | from hypothesis import settings 4 | 5 | WEEK = 86400 * 7 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def distributor(accounts, chain, fee_distributor, voting_escrow, token): 10 | distributor = fee_distributor() 11 | 12 | for i in range(10): 13 | token.approve(voting_escrow, 2 ** 256 - 1, {"from": accounts[i]}) 14 | token.transfer(accounts[i], 10 ** 21, {"from": accounts[0]}) 15 | 16 | yield distributor 17 | 18 | 19 | @given( 20 | st_amount=strategy("decimal[10]", min_value=1, max_value=100, places=4, unique=True), 21 | st_locktime=strategy("uint256[10]", min_value=1, max_value=52, unique=True), 22 | st_sleep=strategy("uint256[10]", min_value=1, max_value=30, unique=True), 23 | ) 24 | @settings(max_examples=10) 25 | def test_checkpoint_total_supply( 26 | accounts, chain, distributor, voting_escrow, st_amount, st_locktime, st_sleep 27 | ): 28 | final_lock = 0 29 | for i in range(10): 30 | chain.sleep(st_sleep[i] * 86400) 31 | lock_time = chain.time() + WEEK * st_locktime[i] 32 | final_lock = max(final_lock, lock_time) 33 | voting_escrow.create_lock(int(st_amount[i] * 10 ** 18), lock_time, {"from": accounts[i]}) 34 | 35 | while chain.time() < final_lock: 36 | 37 | week_epoch = (chain.time() + WEEK) // WEEK * WEEK 38 | chain.mine(timestamp=week_epoch) 39 | week_block = chain[-1].number 40 | chain.sleep(1) 41 | 42 | # Max: 42 weeks 43 | # doing checkpoint 3 times is enough 44 | for i in range(3): 45 | distributor.checkpoint_total_supply({"from": accounts[0]}) 46 | 47 | assert distributor.ve_supply(week_epoch) == voting_escrow.totalSupplyAt(week_block) 48 | -------------------------------------------------------------------------------- /tests/integration/LiquidityGaugeReward/test_borked_rewards.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie.test import given, strategy 3 | from hypothesis import settings 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def initial_setup(accounts, reward_contract, mock_lp_token, liquidity_gauge_reward): 8 | mock_lp_token.approve(liquidity_gauge_reward, 2 ** 256 - 1, {"from": accounts[0]}) 9 | liquidity_gauge_reward.deposit(100000, {"from": accounts[0]}) 10 | 11 | for i in range(1, 11): 12 | mock_lp_token.transfer(accounts[i], 10 ** 18, {"from": accounts[0]}) 13 | mock_lp_token.approve(liquidity_gauge_reward, 10 ** 18, {"from": accounts[i]}) 14 | 15 | 16 | @given( 17 | amounts=strategy("uint256[10]", min_value=10 ** 17, max_value=10 ** 18, unique=True), 18 | durations=strategy("uint256[10]", max_value=86400 * 30, unique=True), 19 | ) 20 | @settings(max_examples=10) 21 | def test_withdraw_borked_rewards( 22 | accounts, 23 | chain, 24 | reward_contract, 25 | mock_lp_token, 26 | liquidity_gauge_reward, 27 | amounts, 28 | durations, 29 | ): 30 | for i, (amount, duration) in enumerate(zip(amounts, durations), start=1): 31 | liquidity_gauge_reward.deposit(amount, {"from": accounts[i]}) 32 | chain.sleep(duration) 33 | 34 | # Calling this method without transfering tokens breaks `CurveRewards.getReward` 35 | # however, it should still be possible to withdraw if `claim_rewards = False` 36 | reward_contract.notifyRewardAmount(10 ** 20, {"from": accounts[0]}) 37 | 38 | for i, (amount, duration) in enumerate(zip(amounts, durations), start=1): 39 | liquidity_gauge_reward.withdraw(amount, False, {"from": accounts[i]}) 40 | assert mock_lp_token.balanceOf(accounts[i]) == 10 ** 18 41 | -------------------------------------------------------------------------------- /tests/integration/LiquidityGaugeReward/test_rewards_ratio.py: -------------------------------------------------------------------------------- 1 | from brownie.test import given, strategy 2 | from hypothesis import settings 3 | 4 | from tests.conftest import approx 5 | 6 | 7 | @given(values=strategy("uint[8]", min_value=1, max_value=10 ** 21)) 8 | @settings(max_examples=25) 9 | def test_ratio_equality( 10 | chain, 11 | accounts, 12 | liquidity_gauge_reward, 13 | mock_lp_token, 14 | reward_contract, 15 | coin_reward, 16 | values, 17 | ): 18 | N_acc = len(values) 19 | 20 | # fund accounts to be used in the test 21 | for acct in accounts[1:N_acc]: 22 | mock_lp_token.transfer(acct, 10 ** 21, {"from": accounts[0]}) 23 | 24 | # approve liquidity_gauge from the funded accounts 25 | for acct in accounts[:N_acc]: 26 | mock_lp_token.approve(liquidity_gauge_reward, 2 ** 256 - 1, {"from": acct}) 27 | 28 | # Deposit 29 | for value, act in zip(values, accounts[:N_acc]): 30 | liquidity_gauge_reward.deposit(value, {"from": act}) 31 | 32 | # Fund rewards 33 | coin_reward._mint_for_testing(reward_contract, 10 ** 20) 34 | reward_contract.notifyRewardAmount(10 ** 20) 35 | 36 | chain.sleep(2 * 7 * 86400) 37 | 38 | # Claim and measure rewards 39 | rewards = [] 40 | for act in accounts[:N_acc]: 41 | before = coin_reward.balanceOf(act) 42 | liquidity_gauge_reward.claim_rewards({"from": act}) 43 | after = coin_reward.balanceOf(act) 44 | rewards.append(after - before) 45 | 46 | for v, r in zip(values, rewards): 47 | if r > 0 and v > 0: 48 | precision = max(1 / min(r, v), 1e-15) 49 | assert approx(v / sum(values), r / sum(rewards), precision) 50 | -------------------------------------------------------------------------------- /tests/integration/VestingEscrow/test_claim_partial.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie.test import given, strategy 3 | 4 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 5 | 6 | 7 | @pytest.fixture(scope="module", autouse=True) 8 | def initial_funding(vesting, accounts): 9 | vesting.add_tokens(10 ** 21, {"from": accounts[0]}) 10 | recipients = [accounts[1]] + [ZERO_ADDRESS] * 99 11 | vesting.fund(recipients, [10 ** 20] + [0] * 99, {"from": accounts[0]}) 12 | 13 | 14 | @given(sleep_time=strategy("uint", max_value=100000)) 15 | def test_claim_partial(vesting, coin_a, accounts, chain, start_time, sleep_time, end_time): 16 | chain.sleep(start_time - chain.time() + sleep_time) 17 | tx = vesting.claim({"from": accounts[1]}) 18 | expected_amount = 10 ** 20 * (tx.timestamp - start_time) // (end_time - start_time) 19 | 20 | assert coin_a.balanceOf(accounts[1]) == expected_amount 21 | -------------------------------------------------------------------------------- /tests/integration/VotingEscrow/test_zero_balance_at_unlock_time.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie.test import given, strategy 3 | 4 | WEEK = 86400 * 7 5 | 6 | 7 | @pytest.fixture(scope="module", autouse=True) 8 | def setup(accounts, token, voting_escrow): 9 | token.approve(voting_escrow, 10 ** 18, {"from": accounts[0]}) 10 | 11 | 12 | @given( 13 | st_initial=strategy("uint", min_value=WEEK * 2, max_value=WEEK * 52), 14 | st_extend=strategy("uint", min_value=WEEK, max_value=WEEK * 2), 15 | ) 16 | def test_create_lock_zero_balance(accounts, chain, token, voting_escrow, st_initial, st_extend): 17 | expected_unlock = chain.time() + st_initial 18 | voting_escrow.create_lock(10 ** 18, expected_unlock, {"from": accounts[0]}) 19 | 20 | actual_unlock = voting_escrow.locked(accounts[0])[1] 21 | 22 | chain.sleep(actual_unlock - chain.time() - 5) 23 | chain.mine() 24 | assert voting_escrow.balanceOf(accounts[0]) 25 | 26 | chain.sleep(10) 27 | chain.mine() 28 | assert not voting_escrow.balanceOf(accounts[0]) 29 | 30 | 31 | @given( 32 | st_initial=strategy("uint", min_value=WEEK * 2, max_value=WEEK * 52), 33 | st_extend=strategy("uint", min_value=WEEK, max_value=WEEK * 2), 34 | ) 35 | def test_increase_unlock_zero_balance(accounts, chain, token, voting_escrow, st_initial, st_extend): 36 | voting_escrow.create_lock(10 ** 18, chain.time() + st_initial, {"from": accounts[0]}) 37 | 38 | initial_unlock = voting_escrow.locked(accounts[0])[1] 39 | extended_expected_unlock = initial_unlock + st_extend 40 | 41 | voting_escrow.increase_unlock_time(extended_expected_unlock) 42 | extended_actual_unlock = voting_escrow.locked(accounts[0])[1] 43 | 44 | chain.sleep(extended_actual_unlock - chain.time() - 5) 45 | chain.mine() 46 | assert voting_escrow.balanceOf(accounts[0]) 47 | 48 | chain.sleep(10) 49 | chain.mine() 50 | assert not voting_escrow.balanceOf(accounts[0]) 51 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/unitary/Burners/test_burner_kill.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_owner_can_kill(burner, alice): 5 | burner.set_killed(True, {"from": alice}) 6 | 7 | assert burner.is_killed() 8 | 9 | 10 | def test_emergency_owner_can_kill(burner, bob): 11 | burner.set_killed(True, {"from": bob}) 12 | 13 | assert burner.is_killed() 14 | 15 | 16 | def test_kill_and_unkill(burner, alice): 17 | burner.set_killed(True, {"from": alice}) 18 | burner.set_killed(False, {"from": alice}) 19 | 20 | assert not burner.is_killed() 21 | 22 | 23 | def test_only_owner_can_kill(burner, charlie): 24 | with brownie.reverts("dev: only owner"): 25 | burner.set_killed(True, {"from": charlie}) 26 | 27 | 28 | def test_cannot_burn_when_killed(burner, alice): 29 | burner.set_killed(True, {"from": alice}) 30 | with brownie.reverts("dev: is killed"): 31 | burner.burn(alice, {"from": alice}) 32 | 33 | if hasattr(burner, "execute"): 34 | with brownie.reverts("dev: is killed"): 35 | burner.execute({"from": alice}) 36 | -------------------------------------------------------------------------------- /tests/unitary/Burners/test_burner_ownership.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_commit_transfer_ownership(burner, alice, bob): 5 | burner.commit_transfer_ownership(bob, {"from": alice}) 6 | 7 | assert burner.owner() == alice 8 | assert burner.future_owner() == bob 9 | 10 | 11 | def test_accept_transfer_ownership(burner, alice, bob): 12 | burner.commit_transfer_ownership(bob, {"from": alice}) 13 | burner.accept_transfer_ownership({"from": bob}) 14 | 15 | assert burner.owner() == bob 16 | assert burner.future_owner() == bob 17 | 18 | 19 | def test_commit_twice_without_accept(burner, alice, bob): 20 | burner.commit_transfer_ownership(bob, {"from": alice}) 21 | burner.commit_transfer_ownership(alice, {"from": alice}) 22 | 23 | assert burner.owner() == alice 24 | assert burner.future_owner() == alice 25 | 26 | 27 | def test_commit_only_owner(burner, alice, bob): 28 | with brownie.reverts("dev: only owner"): 29 | burner.commit_transfer_ownership(bob, {"from": bob}) 30 | 31 | 32 | def test_accept_only_owner(burner, alice, bob): 33 | burner.commit_transfer_ownership(bob, {"from": alice}) 34 | with brownie.reverts("dev: only owner"): 35 | burner.accept_transfer_ownership({"from": alice}) 36 | 37 | 38 | def test_commit_transfer_emergency_ownership(burner, alice, bob): 39 | burner.commit_transfer_emergency_ownership(alice, {"from": bob}) 40 | 41 | assert burner.emergency_owner() == bob 42 | assert burner.future_emergency_owner() == alice 43 | 44 | 45 | def test_accept_transfer_emergency_ownership(burner, alice, bob): 46 | burner.commit_transfer_emergency_ownership(alice, {"from": bob}) 47 | burner.accept_transfer_emergency_ownership({"from": alice}) 48 | 49 | assert burner.emergency_owner() == alice 50 | assert burner.future_emergency_owner() == alice 51 | 52 | 53 | def test_commit_twice_without_accept_emergency(burner, alice, bob): 54 | burner.commit_transfer_emergency_ownership(alice, {"from": bob}) 55 | burner.commit_transfer_emergency_ownership(bob, {"from": bob}) 56 | 57 | assert burner.emergency_owner() == bob 58 | assert burner.future_emergency_owner() == bob 59 | 60 | 61 | def test_commit_emergency_only_owner(burner, alice): 62 | with brownie.reverts("dev: only owner"): 63 | burner.commit_transfer_emergency_ownership(alice, {"from": alice}) 64 | 65 | 66 | def test_accept_emergency_only_owner(burner, alice, bob): 67 | burner.commit_transfer_emergency_ownership(alice, {"from": bob}) 68 | with brownie.reverts("dev: only owner"): 69 | burner.accept_transfer_emergency_ownership({"from": bob}) 70 | -------------------------------------------------------------------------------- /tests/unitary/Burners/test_recover_tokens.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_recover_balance(burner, alice, receiver, coin_a): 5 | coin_a._mint_for_testing(burner, 10 ** 18) 6 | 7 | burner.recover_balance(coin_a, {"from": alice}) 8 | 9 | assert coin_a.balanceOf(burner) == 0 10 | assert coin_a.balanceOf(receiver) == 10 ** 18 11 | 12 | 13 | def test_recover_balance_emergency_owner(burner, bob, receiver, coin_a): 14 | coin_a._mint_for_testing(burner, 10 ** 18) 15 | 16 | burner.recover_balance(coin_a, {"from": bob}) 17 | 18 | assert coin_a.balanceOf(burner) == 0 19 | assert coin_a.balanceOf(receiver) == 10 ** 18 20 | 21 | 22 | def test_recover_only_owner(burner, coin_a, charlie): 23 | with brownie.reverts("dev: only owner"): 24 | burner.recover_balance(coin_a, {"from": charlie}) 25 | 26 | 27 | def test_set_recovery(alice, bob, receiver, burner): 28 | assert burner.recovery() == receiver 29 | 30 | burner.set_recovery(bob, {"from": alice}) 31 | 32 | assert burner.recovery() == bob 33 | 34 | 35 | def test_emergency_admin_cannot_set_recover(alice, bob, receiver, burner): 36 | with brownie.reverts("dev: only owner"): 37 | burner.set_recovery(bob, {"from": bob}) 38 | -------------------------------------------------------------------------------- /tests/unitary/CryptoPoolProxy/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="module", autouse=True) 5 | def admin_setup(accounts, chain, crypto_pool_proxy, crypto_pool): 6 | # set admins as accounts[:3] 7 | crypto_pool_proxy.commit_set_admins( 8 | accounts[0], accounts[1], accounts[2], {"from": accounts[0]} 9 | ) 10 | crypto_pool_proxy.apply_set_admins({"from": accounts[0]}) 11 | 12 | # set crypto_pool_proxy as owner of crypto_pool 13 | crypto_pool.commit_transfer_ownership(crypto_pool_proxy, {"from": accounts[0]}) 14 | chain.sleep(3 * 86400) 15 | crypto_pool.apply_transfer_ownership({"from": accounts[0]}) 16 | -------------------------------------------------------------------------------- /tests/unitary/CryptoPoolProxy/test_emergency_admin_crypto.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 5 | 6 | 7 | def test_kill(accounts, crypto_pool_proxy, crypto_pool): 8 | crypto_pool_proxy.kill_me(crypto_pool, {"from": accounts[2]}) 9 | 10 | 11 | @pytest.mark.parametrize("idx", [0, 2]) 12 | def test_unkill(accounts, crypto_pool_proxy, crypto_pool, idx): 13 | crypto_pool_proxy.unkill_me(crypto_pool, {"from": accounts[idx]}) 14 | 15 | 16 | @pytest.mark.parametrize("idx", [0, 1, 3]) 17 | def test_kill_no_access(accounts, crypto_pool_proxy, crypto_pool, idx): 18 | with brownie.reverts("Access denied"): 19 | crypto_pool_proxy.kill_me(crypto_pool, {"from": accounts[idx]}) 20 | 21 | 22 | @pytest.mark.parametrize("idx", [1, 3]) 23 | def test_unkill_no_access(accounts, crypto_pool_proxy, crypto_pool, idx): 24 | with brownie.reverts("Access denied"): 25 | crypto_pool_proxy.unkill_me(crypto_pool, {"from": accounts[idx]}) 26 | -------------------------------------------------------------------------------- /tests/unitary/CryptoPoolProxy/test_proxy_burn_crypto.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | from brownie import ETH_ADDRESS, ZERO_ADDRESS 4 | 5 | burner_mock = """ 6 | # @version 0.2.7 7 | 8 | is_burned: public(HashMap[address, bool]) 9 | 10 | @payable 11 | @external 12 | def burn(coin: address) -> bool: 13 | self.is_burned[coin] = True 14 | return True 15 | """ 16 | 17 | 18 | @pytest.fixture(scope="module") 19 | def burner(accounts, crypto_pool_proxy, coin_a): 20 | contract = brownie.compile_source(burner_mock).Vyper.deploy({"from": accounts[0]}) 21 | crypto_pool_proxy.set_burner(coin_a, contract, {"from": accounts[0]}) 22 | crypto_pool_proxy.set_burner(ETH_ADDRESS, contract, {"from": accounts[0]}) 23 | accounts[0].transfer(crypto_pool_proxy, 31337) 24 | 25 | yield contract 26 | 27 | 28 | @pytest.mark.parametrize("idx", range(2)) 29 | def test_burn(accounts, crypto_pool_proxy, burner, coin_a, idx): 30 | crypto_pool_proxy.burn(coin_a, {"from": accounts[idx]}) 31 | 32 | assert burner.is_burned(coin_a) 33 | assert crypto_pool_proxy.balance() == 31337 34 | assert burner.balance() == 0 35 | 36 | 37 | @pytest.mark.parametrize("idx", range(2)) 38 | def test_burn_eth(accounts, crypto_pool_proxy, burner, idx): 39 | crypto_pool_proxy.burn(ETH_ADDRESS, {"from": accounts[idx]}) 40 | 41 | assert burner.is_burned(ETH_ADDRESS) 42 | assert crypto_pool_proxy.balance() == 0 43 | assert burner.balance() == 31337 44 | 45 | 46 | @pytest.mark.parametrize("idx", range(2)) 47 | def test_burn_many(accounts, crypto_pool_proxy, burner, coin_a, idx): 48 | crypto_pool_proxy.burn_many([coin_a, ETH_ADDRESS] + [ZERO_ADDRESS] * 18, {"from": accounts[0]}) 49 | 50 | assert burner.is_burned(coin_a) 51 | assert burner.is_burned(ETH_ADDRESS) 52 | 53 | assert crypto_pool_proxy.balance() == 0 54 | assert burner.balance() == 31337 55 | 56 | 57 | def test_burn_not_exists(accounts, crypto_pool_proxy): 58 | with brownie.reverts("dev: should implement burn()"): 59 | crypto_pool_proxy.burn(accounts[1], {"from": accounts[0]}) 60 | 61 | 62 | def test_burn_many_not_exists(accounts, crypto_pool_proxy, burner, coin_a): 63 | with brownie.reverts("dev: should implement burn()"): 64 | crypto_pool_proxy.burn_many( 65 | [coin_a, ETH_ADDRESS, accounts[1]] + [ZERO_ADDRESS] * 17, {"from": accounts[0]} 66 | ) 67 | -------------------------------------------------------------------------------- /tests/unitary/CryptoPoolProxy/test_set_admins_crypto.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 5 | 6 | 7 | def test_set_admins(accounts, crypto_pool_proxy): 8 | crypto_pool_proxy.commit_set_admins( 9 | accounts[1], accounts[2], accounts[3], {"from": accounts[0]} 10 | ) 11 | crypto_pool_proxy.apply_set_admins() 12 | 13 | assert crypto_pool_proxy.ownership_admin() == accounts[1] 14 | assert crypto_pool_proxy.parameter_admin() == accounts[2] 15 | assert crypto_pool_proxy.emergency_admin() == accounts[3] 16 | 17 | 18 | @pytest.mark.parametrize("idx", range(1, 4)) 19 | def test_set_admins_no_access(accounts, crypto_pool_proxy, idx): 20 | with brownie.reverts("Access denied"): 21 | crypto_pool_proxy.commit_set_admins( 22 | accounts[1], accounts[2], accounts[3], {"from": accounts[idx]} 23 | ) 24 | with brownie.reverts("Access denied"): 25 | crypto_pool_proxy.apply_set_admins({"from": accounts[idx]}) 26 | -------------------------------------------------------------------------------- /tests/unitary/ERC20CRV/test_burn.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | WEEK = 86400 * 7 4 | YEAR = 365 * 86400 5 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 6 | 7 | 8 | def test_burn(accounts, token): 9 | balance = token.balanceOf(accounts[0]) 10 | initial_supply = token.totalSupply() 11 | token.burn(31337, {"from": accounts[0]}) 12 | 13 | assert token.balanceOf(accounts[0]) == balance - 31337 14 | assert token.totalSupply() == initial_supply - 31337 15 | 16 | 17 | def test_burn_not_admin(accounts, token): 18 | initial_supply = token.totalSupply() 19 | token.transfer(accounts[1], 1000000, {"from": accounts[0]}) 20 | token.burn(31337, {"from": accounts[1]}) 21 | 22 | assert token.balanceOf(accounts[1]) == 1000000 - 31337 23 | assert token.totalSupply() == initial_supply - 31337 24 | 25 | 26 | def test_burn_all(accounts, token): 27 | initial_supply = token.totalSupply() 28 | token.burn(initial_supply, {"from": accounts[0]}) 29 | 30 | assert token.balanceOf(accounts[0]) == 0 31 | assert token.totalSupply() == 0 32 | 33 | 34 | def test_overburn(accounts, token): 35 | initial_supply = token.totalSupply() 36 | 37 | with brownie.reverts("Integer underflow"): 38 | token.burn(initial_supply + 1, {"from": accounts[0]}) 39 | -------------------------------------------------------------------------------- /tests/unitary/ERC20CRV/test_epoch_time_supply.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | WEEK = 86400 * 7 4 | YEAR = 365 * 86400 5 | 6 | 7 | def test_start_epoch_time_write(token, chain, accounts): 8 | creation_time = token.start_epoch_time() 9 | chain.sleep(YEAR) 10 | chain.mine() 11 | 12 | # the constant function should not report a changed value 13 | assert token.start_epoch_time() == creation_time 14 | 15 | # the state-changing function should show the changed value 16 | assert token.start_epoch_time_write().return_value == creation_time + YEAR 17 | 18 | # after calling the state-changing function, the view function is changed 19 | assert token.start_epoch_time() == creation_time + YEAR 20 | 21 | 22 | def test_start_epoch_time_write_same_epoch(token, chain, accounts): 23 | # calling `start_epoch_token_write` within the same epoch should not raise 24 | token.start_epoch_time_write() 25 | token.start_epoch_time_write() 26 | 27 | 28 | def test_update_mining_parameters(token, chain, accounts): 29 | creation_time = token.start_epoch_time() 30 | new_epoch = creation_time + YEAR - chain.time() 31 | chain.sleep(new_epoch) 32 | token.update_mining_parameters({"from": accounts[0]}) 33 | 34 | 35 | def test_update_mining_parameters_same_epoch(token, chain, accounts): 36 | creation_time = token.start_epoch_time() 37 | new_epoch = creation_time + YEAR - chain.time() 38 | chain.sleep(new_epoch - 3) 39 | with brownie.reverts("dev: too soon!"): 40 | token.update_mining_parameters({"from": accounts[0]}) 41 | 42 | 43 | def test_mintable_in_timeframe_end_before_start(token, accounts): 44 | creation_time = token.start_epoch_time() 45 | with brownie.reverts("dev: start > end"): 46 | token.mintable_in_timeframe(creation_time + 1, creation_time) 47 | 48 | 49 | def test_mintable_in_timeframe_multiple_epochs(token, accounts): 50 | creation_time = token.start_epoch_time() 51 | 52 | # two epochs should not raise 53 | token.mintable_in_timeframe(creation_time, int(creation_time + YEAR * 1.9)) 54 | 55 | with brownie.reverts("dev: too far in future"): 56 | # three epochs should raise 57 | token.mintable_in_timeframe(creation_time, int(creation_time + YEAR * 2.1)) 58 | 59 | 60 | def test_available_supply(chain, web3, token): 61 | creation_time = token.start_epoch_time() 62 | initial_supply = token.totalSupply() 63 | rate = token.rate() 64 | chain.sleep(WEEK) 65 | chain.mine() 66 | 67 | expected = initial_supply + (chain[-1].timestamp - creation_time) * rate 68 | assert token.available_supply() == expected 69 | -------------------------------------------------------------------------------- /tests/unitary/ERC20CRV/test_inflation_delay.py: -------------------------------------------------------------------------------- 1 | WEEK = 86400 * 7 2 | YEAR = 365 * 86400 3 | 4 | 5 | def test_rate(accounts, chain, token): 6 | assert token.rate() == 0 7 | 8 | chain.sleep(86401) 9 | token.update_mining_parameters({"from": accounts[0]}) 10 | 11 | assert token.rate() > 0 12 | 13 | 14 | def test_start_epoch_time(accounts, chain, token): 15 | creation_time = token.start_epoch_time() 16 | assert creation_time == token.tx.timestamp + 86400 - YEAR 17 | 18 | chain.sleep(86401) 19 | token.update_mining_parameters({"from": accounts[0]}) 20 | 21 | assert token.start_epoch_time() == creation_time + YEAR 22 | 23 | 24 | def test_mining_epoch(accounts, chain, token): 25 | assert token.mining_epoch() == -1 26 | 27 | chain.sleep(86401) 28 | token.update_mining_parameters({"from": accounts[0]}) 29 | 30 | assert token.mining_epoch() == 0 31 | 32 | 33 | def test_available_supply(accounts, chain, token): 34 | assert token.available_supply() == 1_303_030_303 * 10 ** 18 35 | 36 | chain.sleep(86401) 37 | token.update_mining_parameters({"from": accounts[0]}) 38 | 39 | assert token.available_supply() > 1_303_030_303 * 10 ** 18 40 | -------------------------------------------------------------------------------- /tests/unitary/ERC20CRV/test_mint.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | from brownie import ZERO_ADDRESS 4 | 5 | WEEK = 86400 * 7 6 | YEAR = 365 * 86400 7 | 8 | 9 | @pytest.fixture(scope="module", autouse=True) 10 | def initial_setup(chain, token): 11 | chain.sleep(86401) 12 | token.update_mining_parameters() 13 | 14 | 15 | def test_available_supply(chain, token): 16 | creation_time = token.start_epoch_time() 17 | initial_supply = token.totalSupply() 18 | rate = token.rate() 19 | chain.sleep(WEEK) 20 | chain.mine() 21 | 22 | expected = initial_supply + (chain[-1].timestamp - creation_time) * rate 23 | assert token.available_supply() == expected 24 | 25 | 26 | def test_mint(accounts, chain, token): 27 | token.set_minter(accounts[0], {"from": accounts[0]}) 28 | creation_time = token.start_epoch_time() 29 | initial_supply = token.totalSupply() 30 | rate = token.rate() 31 | chain.sleep(WEEK) 32 | 33 | amount = (chain.time() - creation_time) * rate 34 | token.mint(accounts[1], amount, {"from": accounts[0]}) 35 | 36 | assert token.balanceOf(accounts[1]) == amount 37 | assert token.totalSupply() == initial_supply + amount 38 | 39 | 40 | def test_overmint(accounts, chain, token): 41 | token.set_minter(accounts[0], {"from": accounts[0]}) 42 | creation_time = token.start_epoch_time() 43 | rate = token.rate() 44 | chain.sleep(WEEK) 45 | 46 | with brownie.reverts("dev: exceeds allowable mint amount"): 47 | token.mint(accounts[1], (chain.time() - creation_time + 2) * rate, {"from": accounts[0]}) 48 | 49 | 50 | def test_minter_only(accounts, token): 51 | token.set_minter(accounts[0], {"from": accounts[0]}) 52 | with brownie.reverts("dev: minter only"): 53 | token.mint(accounts[1], 0, {"from": accounts[1]}) 54 | 55 | 56 | def test_zero_address(accounts, token): 57 | token.set_minter(accounts[0], {"from": accounts[0]}) 58 | with brownie.reverts("dev: zero address"): 59 | token.mint(ZERO_ADDRESS, 0, {"from": accounts[0]}) 60 | -------------------------------------------------------------------------------- /tests/unitary/ERC20CRV/test_setters.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_set_minter_admin_only(accounts, token): 5 | with brownie.reverts("dev: admin only"): 6 | token.set_minter(accounts[2], {"from": accounts[1]}) 7 | 8 | 9 | def test_set_admin_admin_only(accounts, token): 10 | with brownie.reverts("dev: admin only"): 11 | token.set_admin(accounts[2], {"from": accounts[1]}) 12 | 13 | 14 | def test_set_name_admin_only(accounts, token): 15 | with brownie.reverts("Only admin is allowed to change name"): 16 | token.set_name("Foo Token", "FOO", {"from": accounts[1]}) 17 | 18 | 19 | def test_set_minter(accounts, token): 20 | token.set_minter(accounts[1], {"from": accounts[0]}) 21 | 22 | assert token.minter() == accounts[1] 23 | 24 | 25 | def test_set_admin(accounts, token): 26 | token.set_admin(accounts[1], {"from": accounts[0]}) 27 | 28 | assert token.admin() == accounts[1] 29 | 30 | 31 | def test_set_name(accounts, token): 32 | token.set_name("Foo Token", "FOO", {"from": accounts[0]}) 33 | 34 | assert token.name() == "Foo Token" 35 | assert token.symbol() == "FOO" 36 | -------------------------------------------------------------------------------- /tests/unitary/FeeDistribution/test_checkpoints.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | WEEK = 86400 * 7 4 | 5 | 6 | @pytest.fixture(scope="module") 7 | def distributor(accounts, chain, fee_distributor, voting_escrow, token): 8 | distributor = fee_distributor() 9 | 10 | token.approve(voting_escrow, 2 ** 256 - 1, {"from": accounts[0]}) 11 | voting_escrow.create_lock(10 ** 21, chain.time() + WEEK * 52, {"from": accounts[0]}) 12 | 13 | yield distributor 14 | 15 | 16 | def test_checkpoint_total_supply(accounts, chain, distributor, voting_escrow): 17 | start_time = distributor.time_cursor() 18 | 19 | week_epoch = (chain.time() + WEEK) // WEEK * WEEK 20 | chain.mine(timestamp=week_epoch) 21 | week_block = chain[-1].number 22 | 23 | # sleep for 1 second to ensure the total suppply checkpoint happens in the new period 24 | chain.sleep(1) 25 | distributor.checkpoint_total_supply({"from": accounts[0]}) 26 | 27 | assert distributor.ve_supply(start_time) == 0 28 | assert distributor.ve_supply(week_epoch) == voting_escrow.totalSupplyAt(week_block) 29 | 30 | 31 | def test_advance_time_cursor(accounts, chain, distributor): 32 | start_time = distributor.time_cursor() 33 | chain.sleep(86400 * 365) 34 | chain.mine() 35 | 36 | distributor.checkpoint_total_supply({"from": accounts[0]}) 37 | 38 | # total supply checkpoints should advance at most 20 weeks at a time 39 | assert distributor.time_cursor() == start_time + WEEK * 20 40 | assert distributor.ve_supply(start_time + WEEK * 19) > 0 41 | assert distributor.ve_supply(start_time + WEEK * 20) == 0 42 | 43 | distributor.checkpoint_total_supply({"from": accounts[0]}) 44 | 45 | assert distributor.time_cursor() == start_time + WEEK * 40 46 | assert distributor.ve_supply(start_time + WEEK * 20) > 0 47 | assert distributor.ve_supply(start_time + WEEK * 39) > 0 48 | assert distributor.ve_supply(start_time + WEEK * 40) == 0 49 | 50 | 51 | def test_claim_checkpoints_total_supply(accounts, chain, distributor): 52 | start_time = distributor.time_cursor() 53 | 54 | distributor.claim({"from": accounts[0]}) 55 | 56 | assert distributor.time_cursor() == start_time + WEEK 57 | 58 | 59 | def test_toggle_allow_checkpoint(accounts, chain, distributor): 60 | 61 | last_token_time = distributor.last_token_time() 62 | chain.sleep(WEEK) 63 | 64 | distributor.claim({"from": accounts[0]}) 65 | assert distributor.last_token_time() == last_token_time 66 | 67 | distributor.toggle_allow_checkpoint_token({"from": accounts[0]}) 68 | tx = distributor.claim({"from": accounts[0]}) 69 | 70 | assert distributor.last_token_time() == tx.timestamp 71 | -------------------------------------------------------------------------------- /tests/unitary/FeeDistribution/test_claim_many.py: -------------------------------------------------------------------------------- 1 | from brownie import ZERO_ADDRESS 2 | 3 | WEEK = 86400 * 7 4 | 5 | 6 | def test_claim_many(alice, bob, charlie, chain, voting_escrow, fee_distributor, coin_a, token): 7 | amount = 1000 * 10 ** 18 8 | 9 | for acct in (alice, bob, charlie): 10 | token.approve(voting_escrow, amount * 10, {"from": acct}) 11 | token.transfer(acct, amount, {"from": alice}) 12 | voting_escrow.create_lock(amount, chain.time() + 8 * WEEK, {"from": acct}) 13 | 14 | chain.sleep(WEEK) 15 | chain.mine() 16 | start_time = int(chain.time()) 17 | chain.sleep(WEEK * 5) 18 | 19 | fee_distributor = fee_distributor(t=start_time) 20 | coin_a._mint_for_testing(fee_distributor, 10 ** 19) 21 | fee_distributor.checkpoint_token() 22 | chain.sleep(WEEK) 23 | fee_distributor.checkpoint_token() 24 | 25 | fee_distributor.claim_many([alice, bob, charlie] + [ZERO_ADDRESS] * 17, {"from": alice}) 26 | 27 | balances = [coin_a.balanceOf(i) for i in (alice, bob, charlie)] 28 | chain.undo() 29 | 30 | fee_distributor.claim({"from": alice}) 31 | fee_distributor.claim({"from": bob}) 32 | fee_distributor.claim({"from": charlie}) 33 | 34 | assert balances == [coin_a.balanceOf(i) for i in (alice, bob, charlie)] 35 | 36 | 37 | def test_claim_many_same_account( 38 | alice, bob, charlie, chain, voting_escrow, fee_distributor, coin_a, token 39 | ): 40 | amount = 1000 * 10 ** 18 41 | 42 | for acct in (alice, bob, charlie): 43 | token.approve(voting_escrow, amount * 10, {"from": acct}) 44 | token.transfer(acct, amount, {"from": alice}) 45 | voting_escrow.create_lock(amount, chain.time() + 8 * WEEK, {"from": acct}) 46 | 47 | chain.sleep(WEEK) 48 | chain.mine() 49 | start_time = int(chain.time()) 50 | chain.sleep(WEEK * 5) 51 | 52 | fee_distributor = fee_distributor(t=start_time) 53 | coin_a._mint_for_testing(fee_distributor, 10 ** 19) 54 | fee_distributor.checkpoint_token() 55 | chain.sleep(WEEK) 56 | fee_distributor.checkpoint_token() 57 | 58 | expected = fee_distributor.claim.call({"from": alice}) 59 | 60 | fee_distributor.claim_many([alice] * 20, {"from": alice}) 61 | 62 | assert coin_a.balanceOf(alice) == expected 63 | -------------------------------------------------------------------------------- /tests/unitary/GaugeController/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | TYPE_WEIGHTS = [5 * 10 ** 17, 2 * 10 ** 18] 4 | GAUGE_WEIGHTS = [2 * 10 ** 18, 10 ** 18, 5 * 10 ** 17] 5 | 6 | 7 | @pytest.fixture(scope="module", autouse=True) 8 | def gauge_setup(gauge_controller, accounts): 9 | gauge_controller.add_type(b"Liquidity", TYPE_WEIGHTS[0], {"from": accounts[0]}) 10 | 11 | 12 | @pytest.fixture(scope="module") 13 | def gauge(three_gauges): 14 | yield three_gauges[0] 15 | -------------------------------------------------------------------------------- /tests/unitary/GaugeController/test_gaugecontroller_admin.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_commit_admin_only(gauge_controller, accounts): 5 | with brownie.reverts("dev: admin only"): 6 | gauge_controller.commit_transfer_ownership(accounts[1], {"from": accounts[1]}) 7 | 8 | 9 | def test_apply_admin_only(gauge_controller, accounts): 10 | with brownie.reverts("dev: admin only"): 11 | gauge_controller.apply_transfer_ownership({"from": accounts[1]}) 12 | 13 | 14 | def test_commit_transfer_ownership(gauge_controller, accounts): 15 | gauge_controller.commit_transfer_ownership(accounts[1], {"from": accounts[0]}) 16 | 17 | assert gauge_controller.admin() == accounts[0] 18 | assert gauge_controller.future_admin() == accounts[1] 19 | 20 | 21 | def test_apply_transfer_ownership(gauge_controller, accounts): 22 | gauge_controller.commit_transfer_ownership(accounts[1], {"from": accounts[0]}) 23 | gauge_controller.apply_transfer_ownership({"from": accounts[0]}) 24 | 25 | assert gauge_controller.admin() == accounts[1] 26 | 27 | 28 | def test_apply_without_commit(gauge_controller, accounts): 29 | with brownie.reverts("dev: admin not set"): 30 | gauge_controller.apply_transfer_ownership({"from": accounts[0]}) 31 | -------------------------------------------------------------------------------- /tests/unitary/GaugeController/test_timestamps.py: -------------------------------------------------------------------------------- 1 | WEEK = 7 * 86400 2 | YEAR = 365 * 86400 3 | 4 | 5 | def test_timestamps(accounts, chain, gauge_controller): 6 | assert gauge_controller.time_total() == (chain.time() + WEEK) // WEEK * WEEK 7 | for i in range(5): 8 | chain.sleep(int(1.1 * YEAR)) 9 | gauge_controller.checkpoint({"from": accounts[0]}) 10 | assert gauge_controller.time_total() == (chain.time() + WEEK) // WEEK * WEEK 11 | -------------------------------------------------------------------------------- /tests/unitary/GaugeController/test_total_weight.py: -------------------------------------------------------------------------------- 1 | TYPE_WEIGHTS = [5 * 10 ** 17, 2 * 10 ** 18] 2 | GAUGE_WEIGHTS = [2 * 10 ** 18, 10 ** 18, 5 * 10 ** 17] 3 | 4 | 5 | def test_total_weight(accounts, chain, gauge_controller, three_gauges): 6 | gauge_controller.add_gauge(three_gauges[0], 0, GAUGE_WEIGHTS[0], {"from": accounts[0]}) 7 | 8 | assert gauge_controller.get_total_weight() == (GAUGE_WEIGHTS[0] * TYPE_WEIGHTS[0]) 9 | 10 | 11 | def test_change_type_weight(accounts, chain, gauge_controller, three_gauges): 12 | gauge_controller.add_gauge(three_gauges[0], 0, 10 ** 18, {"from": accounts[0]}) 13 | 14 | gauge_controller.change_type_weight(0, 31337, {"from": accounts[0]}) 15 | 16 | assert gauge_controller.get_total_weight() == 10 ** 18 * 31337 17 | 18 | 19 | def test_change_gauge_weight(accounts, chain, gauge_controller, three_gauges): 20 | gauge_controller.add_gauge(three_gauges[0], 0, 10 ** 18, {"from": accounts[0]}) 21 | 22 | gauge_controller.change_gauge_weight(three_gauges[0], 31337, {"from": accounts[0]}) 23 | 24 | assert gauge_controller.get_total_weight() == TYPE_WEIGHTS[0] * 31337 25 | 26 | 27 | def test_multiple(accounts, chain, gauge_controller, three_gauges): 28 | gauge_controller.add_type(b"Insurance", TYPE_WEIGHTS[1], {"from": accounts[0]}) 29 | gauge_controller.add_gauge(three_gauges[0], 0, GAUGE_WEIGHTS[0], {"from": accounts[0]}) 30 | gauge_controller.add_gauge(three_gauges[1], 0, GAUGE_WEIGHTS[1], {"from": accounts[0]}) 31 | gauge_controller.add_gauge(three_gauges[2], 1, GAUGE_WEIGHTS[2], {"from": accounts[0]}) 32 | 33 | expected = ( 34 | (GAUGE_WEIGHTS[0] * TYPE_WEIGHTS[0]) 35 | + (GAUGE_WEIGHTS[1] * TYPE_WEIGHTS[0]) 36 | + (GAUGE_WEIGHTS[2] * TYPE_WEIGHTS[1]) 37 | ) 38 | 39 | assert gauge_controller.get_total_weight() == expected 40 | -------------------------------------------------------------------------------- /tests/unitary/GaugeController/test_vote_weight_unitary.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | YEAR = 86400 * 365 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def gauge_vote_setup(accounts, chain, gauge_controller, three_gauges, voting_escrow, token): 8 | gauge_controller.add_type(b"Insurance", {"from": accounts[0]}) 9 | gauge_controller.add_gauge(three_gauges[0], 0, {"from": accounts[0]}) 10 | gauge_controller.add_gauge(three_gauges[1], 1, {"from": accounts[0]}) 11 | 12 | token.approve(voting_escrow, 10 ** 24, {"from": accounts[0]}) 13 | voting_escrow.create_lock(10 ** 24, chain.time() + YEAR, {"from": accounts[0]}) 14 | 15 | 16 | def test_no_immediate_effect_on_weight(accounts, chain, gauge_controller, three_gauges): 17 | gauge_controller.vote_for_gauge_weights(three_gauges[0], 10000, {"from": accounts[0]}) 18 | assert not gauge_controller.gauge_relative_weight(three_gauges[0]) 19 | 20 | 21 | def test_effect_on_following_period(accounts, chain, gauge_controller, three_gauges): 22 | gauge_controller.vote_for_gauge_weights(three_gauges[0], 10000, {"from": accounts[0]}) 23 | 24 | chain.sleep(86400 * 7) 25 | gauge_controller.checkpoint_gauge(three_gauges[0], {"from": accounts[0]}) 26 | assert gauge_controller.gauge_relative_weight(three_gauges[0]) == 10 ** 18 # 1.0 * 1e18 27 | 28 | 29 | def test_remove_vote_no_immediate_effect(accounts, chain, gauge_controller, three_gauges): 30 | gauge_controller.vote_for_gauge_weights(three_gauges[0], 10000, {"from": accounts[0]}) 31 | chain.sleep(86400 * 10) 32 | 33 | gauge_controller.checkpoint_gauge(three_gauges[0], {"from": accounts[0]}) 34 | gauge_controller.vote_for_gauge_weights(three_gauges[0], 0, {"from": accounts[0]}) 35 | 36 | assert gauge_controller.gauge_relative_weight(three_gauges[0]) == 10 ** 18 37 | 38 | 39 | def test_remove_vote_means_no_weight(accounts, chain, gauge_controller, three_gauges): 40 | gauge_controller.vote_for_gauge_weights(three_gauges[0], 10000, {"from": accounts[0]}) 41 | chain.sleep(86400 * 10) 42 | gauge_controller.checkpoint_gauge(three_gauges[0], {"from": accounts[0]}) 43 | 44 | assert gauge_controller.gauge_relative_weight(three_gauges[0]) == 10 ** 18 45 | 46 | gauge_controller.vote_for_gauge_weights(three_gauges[0], 0, {"from": accounts[0]}) 47 | 48 | chain.sleep(86400 * 7) 49 | gauge_controller.checkpoint_gauge(three_gauges[0], {"from": accounts[0]}) 50 | 51 | assert not gauge_controller.gauge_relative_weight(three_gauges[0]) 52 | -------------------------------------------------------------------------------- /tests/unitary/GaugeProxy/test_gauge_ownership.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize("idx", range(4)) 6 | def test_accept_transfer_ownership(alice, accounts, gauge_proxy, gauge_v2, idx): 7 | # parametrized becuase anyone should be able to call to accept 8 | gauge_v2.commit_transfer_ownership(gauge_proxy, {"from": alice}) 9 | gauge_proxy.accept_transfer_ownership(gauge_v2, {"from": accounts[idx]}) 10 | 11 | assert gauge_v2.admin() == gauge_proxy 12 | 13 | 14 | def test_commit_transfer_ownership(alice, bob, gauge_proxy, gauge_v2): 15 | gauge_v2.commit_transfer_ownership(gauge_proxy, {"from": alice}) 16 | gauge_proxy.accept_transfer_ownership(gauge_v2, {"from": alice}) 17 | gauge_proxy.commit_transfer_ownership(gauge_v2, bob, {"from": alice}) 18 | 19 | assert gauge_v2.future_admin() == bob 20 | 21 | 22 | @pytest.mark.parametrize("idx", range(1, 4)) 23 | def test_commit_only_owner(alice, accounts, gauge_proxy, gauge_v2, idx): 24 | gauge_v2.commit_transfer_ownership(gauge_proxy, {"from": alice}) 25 | gauge_proxy.accept_transfer_ownership(gauge_v2, {"from": alice}) 26 | with brownie.reverts("Access denied"): 27 | gauge_proxy.commit_transfer_ownership(gauge_v2, alice, {"from": accounts[idx]}) 28 | -------------------------------------------------------------------------------- /tests/unitary/GaugeProxy/test_gauge_proxy_set_admins.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | from brownie import ZERO_ADDRESS 4 | 5 | 6 | def test_assumptions(gauge_proxy, alice, bob): 7 | assert gauge_proxy.ownership_admin() == alice 8 | assert gauge_proxy.emergency_admin() == bob 9 | 10 | assert gauge_proxy.future_ownership_admin() == ZERO_ADDRESS 11 | assert gauge_proxy.future_emergency_admin() == ZERO_ADDRESS 12 | 13 | 14 | def test_commit_set_admin(gauge_proxy, alice, bob, charlie): 15 | gauge_proxy.commit_set_admins(bob, charlie, {"from": alice}) 16 | 17 | assert gauge_proxy.ownership_admin() == alice 18 | assert gauge_proxy.emergency_admin() == bob 19 | 20 | assert gauge_proxy.future_ownership_admin() == bob 21 | assert gauge_proxy.future_emergency_admin() == charlie 22 | 23 | 24 | def test_accept_set_admin(gauge_proxy, alice, bob, charlie): 25 | gauge_proxy.commit_set_admins(bob, charlie, {"from": alice}) 26 | gauge_proxy.accept_set_admins({"from": bob}) 27 | 28 | assert gauge_proxy.ownership_admin() == bob 29 | assert gauge_proxy.emergency_admin() == charlie 30 | 31 | assert gauge_proxy.future_ownership_admin() == bob 32 | assert gauge_proxy.future_emergency_admin() == charlie 33 | 34 | 35 | @pytest.mark.parametrize("idx", range(1, 4)) 36 | def test_commit_only_owner(accounts, alice, gauge_proxy, idx): 37 | with brownie.reverts("Access denied"): 38 | gauge_proxy.commit_set_admins(alice, alice, {"from": accounts[idx]}) 39 | 40 | 41 | @pytest.mark.parametrize("idx", range(1, 4)) 42 | def test_accept_only_owner(accounts, alice, bob, gauge_proxy, idx): 43 | gauge_proxy.commit_set_admins(alice, bob, {"from": alice}) 44 | 45 | with brownie.reverts("Access denied"): 46 | gauge_proxy.accept_set_admins({"from": accounts[idx]}) 47 | -------------------------------------------------------------------------------- /tests/unitary/GaugeProxy/test_set_killed_gauge_proxy.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | 5 | @pytest.fixture(scope="module", autouse=True) 6 | def setup(alice, gauge_proxy, gauge_v2, mock_lp_token): 7 | gauge_v2.commit_transfer_ownership(gauge_proxy, {"from": alice}) 8 | gauge_proxy.accept_transfer_ownership(gauge_v2, {"from": alice}) 9 | 10 | 11 | @pytest.mark.parametrize("is_killed", [True, False]) 12 | @pytest.mark.parametrize("idx", range(2)) 13 | def test_kill(accounts, gauge_proxy, gauge_v2, is_killed, idx): 14 | gauge_proxy.set_killed(gauge_v2, is_killed, {"from": accounts[idx]}) 15 | 16 | assert gauge_v2.is_killed() == is_killed 17 | 18 | 19 | @pytest.mark.parametrize("is_killed", [True, False]) 20 | @pytest.mark.parametrize("idx", range(2, 4)) 21 | def test_only_owner(accounts, gauge_proxy, gauge_v2, is_killed, idx): 22 | with brownie.reverts(): 23 | gauge_proxy.set_killed(gauge_v2, is_killed, {"from": accounts[idx]}) 24 | -------------------------------------------------------------------------------- /tests/unitary/GaugeProxy/test_set_rewards_gauge_proxy.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | from brownie import ZERO_ADDRESS 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def setup(alice, gauge_proxy, gauge_v2, mock_lp_token): 8 | gauge_v2.commit_transfer_ownership(gauge_proxy, {"from": alice}) 9 | gauge_proxy.accept_transfer_ownership(gauge_v2, {"from": alice}) 10 | mock_lp_token.approve(gauge_v2, 10 ** 18, {"from": alice}) 11 | gauge_v2.deposit(10 ** 18, {"from": alice}) 12 | 13 | 14 | def test_set_rewards(alice, gauge_proxy, gauge_v2, reward_contract, coin_reward): 15 | sigs = [ 16 | reward_contract.stake.signature[2:], 17 | reward_contract.withdraw.signature[2:], 18 | reward_contract.getReward.signature[2:], 19 | ] 20 | sigs = f"0x{sigs[0]}{sigs[1]}{sigs[2]}{'00' * 20}" 21 | 22 | gauge_proxy.set_rewards( 23 | gauge_v2, reward_contract, sigs, [coin_reward] + [ZERO_ADDRESS] * 7, {"from": alice} 24 | ) 25 | assert gauge_v2.reward_contract() == reward_contract 26 | assert gauge_v2.reward_tokens(0) == coin_reward 27 | 28 | 29 | @pytest.mark.parametrize("idx", [1, 2]) 30 | def test_only_ownership_admin(accounts, gauge_proxy, gauge_v2, reward_contract, idx): 31 | 32 | with brownie.reverts("Access denied"): 33 | gauge_proxy.set_rewards( 34 | gauge_v2, reward_contract, "0x00", [ZERO_ADDRESS] * 8, {"from": accounts[idx]} 35 | ) 36 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGauge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/unitary/LiquidityGauge/__init__.py -------------------------------------------------------------------------------- /tests/unitary/LiquidityGauge/test_checkpoint.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | YEAR = 86400 * 365 4 | 5 | 6 | def test_user_checkpoint(accounts, liquidity_gauge): 7 | liquidity_gauge.user_checkpoint(accounts[1], {"from": accounts[1]}) 8 | 9 | 10 | def test_user_checkpoint_new_period(accounts, chain, liquidity_gauge): 11 | liquidity_gauge.user_checkpoint(accounts[1], {"from": accounts[1]}) 12 | chain.sleep(int(YEAR * 1.1)) 13 | liquidity_gauge.user_checkpoint(accounts[1], {"from": accounts[1]}) 14 | 15 | 16 | def test_user_checkpoint_wrong_account(accounts, liquidity_gauge): 17 | with brownie.reverts("dev: unauthorized"): 18 | liquidity_gauge.user_checkpoint(accounts[2], {"from": accounts[1]}) 19 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGauge/test_deposit_for.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_deposit_for(accounts, liquidity_gauge, mock_lp_token): 5 | mock_lp_token.approve(liquidity_gauge, 2 ** 256 - 1, {"from": accounts[0]}) 6 | balance = mock_lp_token.balanceOf(accounts[0]) 7 | liquidity_gauge.set_approve_deposit(accounts[0], True, {"from": accounts[1]}) 8 | liquidity_gauge.deposit(100000, accounts[1], {"from": accounts[0]}) 9 | 10 | assert mock_lp_token.balanceOf(liquidity_gauge) == 100000 11 | assert mock_lp_token.balanceOf(accounts[0]) == balance - 100000 12 | assert liquidity_gauge.totalSupply() == 100000 13 | assert liquidity_gauge.balanceOf(accounts[1]) == 100000 14 | 15 | 16 | def test_set_approve_deposit_initial(accounts, liquidity_gauge): 17 | assert liquidity_gauge.approved_to_deposit(accounts[0], accounts[1]) is False 18 | 19 | 20 | def test_set_approve_deposit_true(accounts, liquidity_gauge): 21 | liquidity_gauge.set_approve_deposit(accounts[0], True, {"from": accounts[1]}) 22 | assert liquidity_gauge.approved_to_deposit(accounts[0], accounts[1]) is True 23 | 24 | 25 | def test_set_approve_deposit_false(accounts, liquidity_gauge): 26 | liquidity_gauge.set_approve_deposit(accounts[0], False, {"from": accounts[1]}) 27 | assert liquidity_gauge.approved_to_deposit(accounts[0], accounts[1]) is False 28 | 29 | 30 | def test_set_approve_deposit_toggle(accounts, liquidity_gauge): 31 | for value in [True, True, False, False, True, False, True]: 32 | liquidity_gauge.set_approve_deposit(accounts[0], value, {"from": accounts[1]}) 33 | assert liquidity_gauge.approved_to_deposit(accounts[0], accounts[1]) is value 34 | 35 | 36 | def test_not_approved(accounts, liquidity_gauge, mock_lp_token): 37 | mock_lp_token.approve(liquidity_gauge, 2 ** 256 - 1, {"from": accounts[0]}) 38 | with brownie.reverts("Not approved"): 39 | liquidity_gauge.deposit(100000, accounts[1], {"from": accounts[0]}) 40 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGauge/test_kick.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | MAX_UINT256 = 2 ** 256 - 1 4 | WEEK = 7 * 86400 5 | 6 | 7 | def test_kick(chain, accounts, liquidity_gauge, voting_escrow, token, mock_lp_token): 8 | alice, bob = accounts[:2] 9 | chain.sleep(2 * WEEK + 5) 10 | 11 | token.approve(voting_escrow, MAX_UINT256, {"from": alice}) 12 | voting_escrow.create_lock(10 ** 20, chain.time() + 4 * WEEK, {"from": alice}) 13 | 14 | mock_lp_token.approve(liquidity_gauge.address, MAX_UINT256, {"from": alice}) 15 | liquidity_gauge.deposit(10 ** 21, {"from": alice}) 16 | 17 | assert liquidity_gauge.working_balances(alice) == 10 ** 21 18 | 19 | chain.sleep(WEEK) 20 | 21 | with brownie.reverts("dev: kick not allowed"): 22 | liquidity_gauge.kick(alice, {"from": bob}) 23 | 24 | chain.sleep(4 * WEEK) 25 | 26 | liquidity_gauge.kick(alice, {"from": bob}) 27 | assert liquidity_gauge.working_balances(alice) == 4 * 10 ** 20 28 | 29 | with brownie.reverts("dev: kick not needed"): 30 | liquidity_gauge.kick(alice, {"from": bob}) 31 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeReward/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/unitary/LiquidityGaugeReward/__init__.py -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeReward/test_checkpoint_reward.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | YEAR = 86400 * 365 4 | 5 | 6 | def test_user_checkpoint(accounts, liquidity_gauge_reward): 7 | liquidity_gauge_reward.user_checkpoint(accounts[1], {"from": accounts[1]}) 8 | 9 | 10 | def test_user_checkpoint_new_period(accounts, chain, liquidity_gauge_reward): 11 | liquidity_gauge_reward.user_checkpoint(accounts[1], {"from": accounts[1]}) 12 | chain.sleep(int(YEAR * 1.1)) 13 | liquidity_gauge_reward.user_checkpoint(accounts[1], {"from": accounts[1]}) 14 | 15 | 16 | def test_user_checkpoint_wrong_account(accounts, liquidity_gauge_reward): 17 | with brownie.reverts("dev: unauthorized"): 18 | liquidity_gauge_reward.user_checkpoint(accounts[2], {"from": accounts[1]}) 19 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeReward/test_claim_rewards_none.py: -------------------------------------------------------------------------------- 1 | REWARD = 10 ** 20 2 | WEEK = 7 * 86400 3 | LP_AMOUNT = 10 ** 18 4 | 5 | 6 | def test_claim_no_deposit(accounts, chain, liquidity_gauge_reward, reward_contract, coin_reward): 7 | # Fund 8 | coin_reward._mint_for_testing(accounts[0], REWARD) 9 | coin_reward.transfer(reward_contract, REWARD, {"from": accounts[0]}) 10 | reward_contract.notifyRewardAmount(REWARD, {"from": accounts[0]}) 11 | 12 | chain.sleep(WEEK) 13 | 14 | liquidity_gauge_reward.claim_rewards({"from": accounts[1]}) 15 | 16 | assert coin_reward.balanceOf(accounts[1]) == 0 17 | 18 | 19 | def test_claim_no_rewards( 20 | accounts, chain, liquidity_gauge_reward, mock_lp_token, reward_contract, coin_reward 21 | ): 22 | # Deposit 23 | mock_lp_token.transfer(accounts[1], LP_AMOUNT, {"from": accounts[0]}) 24 | mock_lp_token.approve(liquidity_gauge_reward, LP_AMOUNT, {"from": accounts[1]}) 25 | liquidity_gauge_reward.deposit(LP_AMOUNT, {"from": accounts[1]}) 26 | 27 | chain.sleep(WEEK) 28 | 29 | liquidity_gauge_reward.withdraw(LP_AMOUNT, {"from": accounts[1]}) 30 | liquidity_gauge_reward.claim_rewards({"from": accounts[1]}) 31 | 32 | assert coin_reward.balanceOf(accounts[1]) == 0 33 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeReward/test_deposit_for_rewards.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_deposit_for(accounts, liquidity_gauge_reward, mock_lp_token, reward_contract): 5 | mock_lp_token.approve(liquidity_gauge_reward, 2 ** 256 - 1, {"from": accounts[0]}) 6 | balance = mock_lp_token.balanceOf(accounts[0]) 7 | liquidity_gauge_reward.set_approve_deposit(accounts[0], True, {"from": accounts[1]}) 8 | liquidity_gauge_reward.deposit(100000, accounts[1], {"from": accounts[0]}) 9 | 10 | assert mock_lp_token.balanceOf(reward_contract) == 100000 11 | assert mock_lp_token.balanceOf(accounts[0]) == balance - 100000 12 | assert liquidity_gauge_reward.totalSupply() == 100000 13 | assert liquidity_gauge_reward.balanceOf(accounts[1]) == 100000 14 | 15 | 16 | def test_set_approve_deposit_initial(accounts, liquidity_gauge_reward): 17 | assert liquidity_gauge_reward.approved_to_deposit(accounts[0], accounts[1]) is False 18 | 19 | 20 | def test_set_approve_deposit_true(accounts, liquidity_gauge_reward): 21 | liquidity_gauge_reward.set_approve_deposit(accounts[0], True, {"from": accounts[1]}) 22 | assert liquidity_gauge_reward.approved_to_deposit(accounts[0], accounts[1]) is True 23 | 24 | 25 | def test_set_approve_deposit_false(accounts, liquidity_gauge_reward): 26 | liquidity_gauge_reward.set_approve_deposit(accounts[0], False, {"from": accounts[1]}) 27 | assert liquidity_gauge_reward.approved_to_deposit(accounts[0], accounts[1]) is False 28 | 29 | 30 | def test_set_approve_deposit_toggle(accounts, liquidity_gauge_reward): 31 | for value in [True, True, False, False, True, False, True]: 32 | liquidity_gauge_reward.set_approve_deposit(accounts[0], value, {"from": accounts[1]}) 33 | assert liquidity_gauge_reward.approved_to_deposit(accounts[0], accounts[1]) is value 34 | 35 | 36 | def test_not_approved(accounts, liquidity_gauge_reward, mock_lp_token): 37 | mock_lp_token.approve(liquidity_gauge_reward, 2 ** 256 - 1, {"from": accounts[0]}) 38 | with brownie.reverts("Not approved"): 39 | liquidity_gauge_reward.deposit(100000, accounts[1], {"from": accounts[0]}) 40 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeRewardWrapper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/unitary/LiquidityGaugeRewardWrapper/__init__.py -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeRewardWrapper/test_checkpoint.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | YEAR = 86400 * 365 5 | 6 | 7 | @pytest.fixture(scope="module", autouse=True) 8 | def setup(accounts, gauge_controller, minter, liquidity_gauge_reward, token): 9 | token.set_minter(minter, {"from": accounts[0]}) 10 | 11 | gauge_controller.add_type(b"Liquidity", 10 ** 10, {"from": accounts[0]}) 12 | gauge_controller.add_gauge(liquidity_gauge_reward, 0, 0, {"from": accounts[0]}) 13 | 14 | 15 | def test_user_checkpoint(accounts, reward_gauge_wrapper): 16 | reward_gauge_wrapper.user_checkpoint(accounts[1], {"from": accounts[1]}) 17 | 18 | 19 | def test_user_checkpoint_new_period(accounts, chain, reward_gauge_wrapper): 20 | reward_gauge_wrapper.user_checkpoint(accounts[1], {"from": accounts[1]}) 21 | chain.sleep(int(YEAR * 1.1)) 22 | reward_gauge_wrapper.user_checkpoint(accounts[1], {"from": accounts[1]}) 23 | 24 | 25 | def test_user_checkpoint_wrong_account(accounts, reward_gauge_wrapper): 26 | with brownie.reverts("dev: unauthorized"): 27 | reward_gauge_wrapper.user_checkpoint(accounts[2], {"from": accounts[1]}) 28 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/unitary/LiquidityGaugeV2/__init__.py -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV2/test_approve.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.parametrize("idx", range(5)) 5 | def test_initial_approval_is_zero(gauge_v2, accounts, idx): 6 | assert gauge_v2.allowance(accounts[0], accounts[idx]) == 0 7 | 8 | 9 | def test_approve(gauge_v2, accounts): 10 | gauge_v2.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 11 | 12 | assert gauge_v2.allowance(accounts[0], accounts[1]) == 10 ** 19 13 | 14 | 15 | def test_modify_approve(gauge_v2, accounts): 16 | gauge_v2.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 17 | gauge_v2.approve(accounts[1], 12345678, {"from": accounts[0]}) 18 | 19 | assert gauge_v2.allowance(accounts[0], accounts[1]) == 12345678 20 | 21 | 22 | def test_revoke_approve(gauge_v2, accounts): 23 | gauge_v2.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 24 | gauge_v2.approve(accounts[1], 0, {"from": accounts[0]}) 25 | 26 | assert gauge_v2.allowance(accounts[0], accounts[1]) == 0 27 | 28 | 29 | def test_approve_self(gauge_v2, accounts): 30 | gauge_v2.approve(accounts[0], 10 ** 19, {"from": accounts[0]}) 31 | 32 | assert gauge_v2.allowance(accounts[0], accounts[0]) == 10 ** 19 33 | 34 | 35 | def test_only_affects_target(gauge_v2, accounts): 36 | gauge_v2.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 37 | 38 | assert gauge_v2.allowance(accounts[1], accounts[0]) == 0 39 | 40 | 41 | def test_returns_true(gauge_v2, accounts): 42 | tx = gauge_v2.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 43 | 44 | assert tx.return_value is True 45 | 46 | 47 | def test_approval_event_fires(accounts, gauge_v2): 48 | tx = gauge_v2.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 49 | 50 | assert len(tx.events) == 1 51 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10 ** 19] 52 | 53 | 54 | def test_increase_allowance(accounts, gauge_v2): 55 | gauge_v2.approve(accounts[1], 100, {"from": accounts[0]}) 56 | gauge_v2.increaseAllowance(accounts[1], 403, {"from": accounts[0]}) 57 | 58 | assert gauge_v2.allowance(accounts[0], accounts[1]) == 503 59 | 60 | 61 | def test_decrease_allowance(accounts, gauge_v2): 62 | gauge_v2.approve(accounts[1], 100, {"from": accounts[0]}) 63 | gauge_v2.decreaseAllowance(accounts[1], 34, {"from": accounts[0]}) 64 | 65 | assert gauge_v2.allowance(accounts[0], accounts[1]) == 66 66 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV2/test_checkpoint.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | YEAR = 86400 * 365 4 | 5 | 6 | def test_user_checkpoint(accounts, gauge_v2): 7 | gauge_v2.user_checkpoint(accounts[1], {"from": accounts[1]}) 8 | 9 | 10 | def test_user_checkpoint_new_period(accounts, chain, gauge_v2): 11 | gauge_v2.user_checkpoint(accounts[1], {"from": accounts[1]}) 12 | chain.sleep(int(YEAR * 1.1)) 13 | gauge_v2.user_checkpoint(accounts[1], {"from": accounts[1]}) 14 | 15 | 16 | def test_user_checkpoint_wrong_account(accounts, gauge_v2): 17 | with brownie.reverts("dev: unauthorized"): 18 | gauge_v2.user_checkpoint(accounts[2], {"from": accounts[1]}) 19 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV2/test_claim_rewards_none.py: -------------------------------------------------------------------------------- 1 | from brownie import ZERO_ADDRESS 2 | 3 | REWARD = 10 ** 20 4 | WEEK = 7 * 86400 5 | LP_AMOUNT = 10 ** 18 6 | 7 | 8 | def test_claim_no_deposit(alice, bob, chain, gauge_v2, mock_lp_token, reward_contract, coin_reward): 9 | # Fund 10 | mock_lp_token.approve(gauge_v2, LP_AMOUNT, {"from": alice}) 11 | gauge_v2.deposit(LP_AMOUNT, {"from": alice}) 12 | 13 | coin_reward._mint_for_testing(alice, REWARD) 14 | coin_reward.transfer(reward_contract, REWARD, {"from": alice}) 15 | reward_contract.notifyRewardAmount(REWARD, {"from": alice}) 16 | 17 | gauge_v2.set_rewards( 18 | reward_contract, 19 | "0xa694fc3a2e1a7d4d3d18b9120000000000000000000000000000000000000000", 20 | [coin_reward] + [ZERO_ADDRESS] * 7, 21 | {"from": alice}, 22 | ) 23 | 24 | chain.sleep(WEEK) 25 | 26 | gauge_v2.claim_rewards({"from": bob}) 27 | 28 | assert coin_reward.balanceOf(bob) == 0 29 | 30 | 31 | def test_claim_no_rewards(alice, bob, chain, gauge_v2, mock_lp_token, reward_contract, coin_reward): 32 | # Deposit 33 | mock_lp_token.transfer(bob, LP_AMOUNT, {"from": alice}) 34 | mock_lp_token.approve(gauge_v2, LP_AMOUNT, {"from": bob}) 35 | gauge_v2.deposit(LP_AMOUNT, {"from": bob}) 36 | 37 | chain.sleep(WEEK) 38 | 39 | gauge_v2.withdraw(LP_AMOUNT, {"from": bob}) 40 | gauge_v2.claim_rewards({"from": bob}) 41 | 42 | assert coin_reward.balanceOf(bob) == 0 43 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV2/test_deposit_for.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_deposit_for(accounts, gauge_v2, mock_lp_token): 5 | mock_lp_token.approve(gauge_v2, 2 ** 256 - 1, {"from": accounts[0]}) 6 | balance = mock_lp_token.balanceOf(accounts[0]) 7 | gauge_v2.set_approve_deposit(accounts[0], True, {"from": accounts[1]}) 8 | gauge_v2.deposit(100000, accounts[1], {"from": accounts[0]}) 9 | 10 | assert mock_lp_token.balanceOf(gauge_v2) == 100000 11 | assert mock_lp_token.balanceOf(accounts[0]) == balance - 100000 12 | assert gauge_v2.totalSupply() == 100000 13 | assert gauge_v2.balanceOf(accounts[1]) == 100000 14 | 15 | 16 | def test_set_approve_deposit_initial(accounts, gauge_v2): 17 | assert gauge_v2.approved_to_deposit(accounts[0], accounts[1]) is False 18 | 19 | 20 | def test_set_approve_deposit_true(accounts, gauge_v2): 21 | gauge_v2.set_approve_deposit(accounts[0], True, {"from": accounts[1]}) 22 | assert gauge_v2.approved_to_deposit(accounts[0], accounts[1]) is True 23 | 24 | 25 | def test_set_approve_deposit_false(accounts, gauge_v2): 26 | gauge_v2.set_approve_deposit(accounts[0], False, {"from": accounts[1]}) 27 | assert gauge_v2.approved_to_deposit(accounts[0], accounts[1]) is False 28 | 29 | 30 | def test_set_approve_deposit_toggle(accounts, gauge_v2): 31 | for value in [True, True, False, False, True, False, True]: 32 | gauge_v2.set_approve_deposit(accounts[0], value, {"from": accounts[1]}) 33 | assert gauge_v2.approved_to_deposit(accounts[0], accounts[1]) is value 34 | 35 | 36 | def test_not_approved(accounts, gauge_v2, mock_lp_token): 37 | mock_lp_token.approve(gauge_v2, 2 ** 256 - 1, {"from": accounts[0]}) 38 | with brownie.reverts("Not approved"): 39 | gauge_v2.deposit(100000, accounts[1], {"from": accounts[0]}) 40 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV2/test_kick.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | MAX_UINT256 = 2 ** 256 - 1 4 | WEEK = 7 * 86400 5 | 6 | 7 | def test_kick(chain, accounts, gauge_v2, voting_escrow, token, mock_lp_token): 8 | alice, bob = accounts[:2] 9 | chain.sleep(2 * WEEK + 5) 10 | 11 | token.approve(voting_escrow, MAX_UINT256, {"from": alice}) 12 | voting_escrow.create_lock(10 ** 20, chain.time() + 4 * WEEK, {"from": alice}) 13 | 14 | mock_lp_token.approve(gauge_v2.address, MAX_UINT256, {"from": alice}) 15 | gauge_v2.deposit(10 ** 21, {"from": alice}) 16 | 17 | assert gauge_v2.working_balances(alice) == 10 ** 21 18 | 19 | chain.sleep(WEEK) 20 | 21 | with brownie.reverts("dev: kick not allowed"): 22 | gauge_v2.kick(alice, {"from": bob}) 23 | 24 | chain.sleep(4 * WEEK) 25 | 26 | gauge_v2.kick(alice, {"from": bob}) 27 | assert gauge_v2.working_balances(alice) == 4 * 10 ** 20 28 | 29 | with brownie.reverts("dev: kick not needed"): 30 | gauge_v2.kick(alice, {"from": bob}) 31 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV3/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/unitary/LiquidityGaugeV3/__init__.py -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV3/test_approve.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.parametrize("idx", range(5)) 5 | def test_initial_approval_is_zero(gauge_v3, accounts, idx): 6 | assert gauge_v3.allowance(accounts[0], accounts[idx]) == 0 7 | 8 | 9 | def test_approve(gauge_v3, accounts): 10 | gauge_v3.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 11 | 12 | assert gauge_v3.allowance(accounts[0], accounts[1]) == 10 ** 19 13 | 14 | 15 | def test_modify_approve(gauge_v3, accounts): 16 | gauge_v3.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 17 | gauge_v3.approve(accounts[1], 12345678, {"from": accounts[0]}) 18 | 19 | assert gauge_v3.allowance(accounts[0], accounts[1]) == 12345678 20 | 21 | 22 | def test_revoke_approve(gauge_v3, accounts): 23 | gauge_v3.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 24 | gauge_v3.approve(accounts[1], 0, {"from": accounts[0]}) 25 | 26 | assert gauge_v3.allowance(accounts[0], accounts[1]) == 0 27 | 28 | 29 | def test_approve_self(gauge_v3, accounts): 30 | gauge_v3.approve(accounts[0], 10 ** 19, {"from": accounts[0]}) 31 | 32 | assert gauge_v3.allowance(accounts[0], accounts[0]) == 10 ** 19 33 | 34 | 35 | def test_only_affects_target(gauge_v3, accounts): 36 | gauge_v3.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 37 | 38 | assert gauge_v3.allowance(accounts[1], accounts[0]) == 0 39 | 40 | 41 | def test_returns_true(gauge_v3, accounts): 42 | tx = gauge_v3.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 43 | 44 | assert tx.return_value is True 45 | 46 | 47 | def test_approval_event_fires(accounts, gauge_v3): 48 | tx = gauge_v3.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 49 | 50 | assert len(tx.events) == 1 51 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10 ** 19] 52 | 53 | 54 | def test_increase_allowance(accounts, gauge_v3): 55 | gauge_v3.approve(accounts[1], 100, {"from": accounts[0]}) 56 | gauge_v3.increaseAllowance(accounts[1], 403, {"from": accounts[0]}) 57 | 58 | assert gauge_v3.allowance(accounts[0], accounts[1]) == 503 59 | 60 | 61 | def test_decrease_allowance(accounts, gauge_v3): 62 | gauge_v3.approve(accounts[1], 100, {"from": accounts[0]}) 63 | gauge_v3.decreaseAllowance(accounts[1], 34, {"from": accounts[0]}) 64 | 65 | assert gauge_v3.allowance(accounts[0], accounts[1]) == 66 66 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV3/test_checkpoint.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | YEAR = 86400 * 365 4 | 5 | 6 | def test_user_checkpoint(accounts, gauge_v3): 7 | gauge_v3.user_checkpoint(accounts[1], {"from": accounts[1]}) 8 | 9 | 10 | def test_user_checkpoint_new_period(accounts, chain, gauge_v3): 11 | gauge_v3.user_checkpoint(accounts[1], {"from": accounts[1]}) 12 | chain.sleep(int(YEAR * 1.1)) 13 | gauge_v3.user_checkpoint(accounts[1], {"from": accounts[1]}) 14 | 15 | 16 | def test_user_checkpoint_wrong_account(accounts, gauge_v3): 17 | with brownie.reverts("dev: unauthorized"): 18 | gauge_v3.user_checkpoint(accounts[2], {"from": accounts[1]}) 19 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV3/test_claim_rewards_none.py: -------------------------------------------------------------------------------- 1 | from brownie import ZERO_ADDRESS 2 | 3 | REWARD = 10 ** 20 4 | WEEK = 7 * 86400 5 | LP_AMOUNT = 10 ** 18 6 | 7 | 8 | def test_claim_no_deposit(alice, bob, chain, gauge_v3, mock_lp_token, reward_contract, coin_reward): 9 | # Fund 10 | mock_lp_token.approve(gauge_v3, LP_AMOUNT, {"from": alice}) 11 | gauge_v3.deposit(LP_AMOUNT, {"from": alice}) 12 | 13 | coin_reward._mint_for_testing(reward_contract, REWARD) 14 | reward_contract.notifyRewardAmount(REWARD, {"from": alice}) 15 | 16 | gauge_v3.set_rewards( 17 | reward_contract, 18 | "0xa694fc3a2e1a7d4d3d18b9120000000000000000000000000000000000000000", 19 | [coin_reward] + [ZERO_ADDRESS] * 7, 20 | {"from": alice}, 21 | ) 22 | 23 | chain.sleep(WEEK) 24 | 25 | gauge_v3.claim_rewards({"from": bob}) 26 | 27 | assert coin_reward.balanceOf(bob) == 0 28 | 29 | 30 | def test_claim_no_rewards(alice, bob, chain, gauge_v3, mock_lp_token, reward_contract, coin_reward): 31 | # Deposit 32 | mock_lp_token.transfer(bob, LP_AMOUNT, {"from": alice}) 33 | mock_lp_token.approve(gauge_v3, LP_AMOUNT, {"from": bob}) 34 | gauge_v3.deposit(LP_AMOUNT, {"from": bob}) 35 | 36 | chain.sleep(WEEK) 37 | 38 | gauge_v3.withdraw(LP_AMOUNT, {"from": bob}) 39 | gauge_v3.claim_rewards({"from": bob}) 40 | 41 | assert coin_reward.balanceOf(bob) == 0 42 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV3/test_claim_rewards_transfer.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import pytest 4 | from brownie import ZERO_ADDRESS 5 | 6 | REWARD = 10 ** 20 7 | WEEK = 7 * 86400 8 | 9 | 10 | @pytest.fixture(scope="module", autouse=True) 11 | def initial_setup( 12 | alice, 13 | chain, 14 | coin_reward, 15 | reward_contract, 16 | token, 17 | mock_lp_token, 18 | gauge_v3, 19 | gauge_controller, 20 | minter, 21 | ): 22 | # gauge setup 23 | token.set_minter(minter, {"from": alice}) 24 | gauge_controller.add_type(b"Liquidity", 10 ** 10, {"from": alice}) 25 | gauge_controller.add_gauge(gauge_v3, 0, 0, {"from": alice}) 26 | 27 | # deposit into gauge 28 | mock_lp_token.approve(gauge_v3, 2 ** 256 - 1, {"from": alice}) 29 | gauge_v3.deposit(10 ** 18, {"from": alice}) 30 | 31 | # add rewards 32 | sigs = [ 33 | reward_contract.stake.signature[2:], 34 | reward_contract.withdraw.signature[2:], 35 | reward_contract.getReward.signature[2:], 36 | ] 37 | sigs = f"0x{sigs[0]}{sigs[1]}{sigs[2]}{'00' * 20}" 38 | 39 | gauge_v3.set_rewards(reward_contract, sigs, [coin_reward] + [ZERO_ADDRESS] * 7, {"from": alice}) 40 | 41 | # fund rewards 42 | coin_reward._mint_for_testing(reward_contract, REWARD) 43 | reward_contract.notifyRewardAmount(REWARD, {"from": alice}) 44 | 45 | # sleep half way through the reward period 46 | chain.sleep(int(86400 * 3.5)) 47 | 48 | 49 | def test_transfer_does_not_trigger_claim_for_sender(alice, bob, chain, gauge_v3, coin_reward): 50 | amount = gauge_v3.balanceOf(alice) 51 | 52 | gauge_v3.transfer(bob, amount, {"from": alice}) 53 | 54 | reward = coin_reward.balanceOf(alice) 55 | assert reward == 0 56 | 57 | 58 | def test_transfer_does_not_trigger_claim_for_receiver(alice, bob, chain, gauge_v3, coin_reward): 59 | amount = gauge_v3.balanceOf(alice) // 2 60 | 61 | gauge_v3.transfer(bob, amount, {"from": alice}) 62 | chain.sleep(WEEK) 63 | gauge_v3.transfer(alice, amount, {"from": bob}) 64 | 65 | for acct in (alice, bob): 66 | assert coin_reward.balanceOf(acct) == 0 67 | 68 | 69 | def test_claim_rewards_stil_accurate(alice, bob, chain, gauge_v3, coin_reward): 70 | amount = gauge_v3.balanceOf(alice) 71 | 72 | gauge_v3.transfer(bob, amount, {"from": alice}) 73 | 74 | # sleep half way through the reward period 75 | chain.sleep(int(86400 * 3.5)) 76 | 77 | for acct in (alice, bob): 78 | gauge_v3.claim_rewards({"from": acct}) 79 | 80 | assert math.isclose(coin_reward.balanceOf(acct), REWARD // 2, rel_tol=0.01) 81 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV3/test_deposit_for.py: -------------------------------------------------------------------------------- 1 | from math import isclose 2 | 3 | from brownie import ZERO_ADDRESS 4 | 5 | REWARD = 10 ** 20 6 | WEEK = 7 * 86400 7 | LP_AMOUNT = 10 ** 18 8 | 9 | 10 | def test_no_approval_needed(alice, bob, gauge_v3, mock_lp_token): 11 | mock_lp_token.approve(gauge_v3, 2 ** 256 - 1, {"from": alice}) 12 | gauge_v3.deposit(100_000, bob, {"from": alice}) 13 | 14 | assert gauge_v3.balanceOf(bob) == 100_000 15 | 16 | 17 | def test_deposit_for_and_claim_rewards( 18 | alice, bob, chain, gauge_v3, mock_lp_token, reward_contract, coin_reward 19 | ): 20 | mock_lp_token.approve(gauge_v3, 2 ** 256 - 1, {"from": alice}) 21 | gauge_v3.deposit(LP_AMOUNT, bob, {"from": alice}) 22 | 23 | coin_reward._mint_for_testing(reward_contract, REWARD) 24 | reward_contract.notifyRewardAmount(REWARD, {"from": alice}) 25 | 26 | gauge_v3.set_rewards( 27 | reward_contract, 28 | "0xa694fc3a2e1a7d4d3d18b9120000000000000000000000000000000000000000", 29 | [coin_reward] + [ZERO_ADDRESS] * 7, 30 | {"from": alice}, 31 | ) 32 | 33 | chain.mine(timedelta=WEEK) 34 | 35 | # alice deposits for bob and claims rewards for him 36 | gauge_v3.deposit(LP_AMOUNT, bob, True, {"from": alice}) 37 | 38 | assert isclose(REWARD, coin_reward.balanceOf(bob), rel_tol=0.001) 39 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV3/test_kick.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | MAX_UINT256 = 2 ** 256 - 1 4 | WEEK = 7 * 86400 5 | 6 | 7 | def test_kick(chain, accounts, gauge_v3, voting_escrow, token, mock_lp_token): 8 | alice, bob = accounts[:2] 9 | chain.sleep(2 * WEEK + 5) 10 | 11 | token.approve(voting_escrow, MAX_UINT256, {"from": alice}) 12 | voting_escrow.create_lock(10 ** 20, chain.time() + 4 * WEEK, {"from": alice}) 13 | 14 | mock_lp_token.approve(gauge_v3.address, MAX_UINT256, {"from": alice}) 15 | gauge_v3.deposit(10 ** 21, {"from": alice}) 16 | 17 | assert gauge_v3.working_balances(alice) == 10 ** 21 18 | 19 | chain.sleep(WEEK) 20 | 21 | with brownie.reverts("dev: kick not allowed"): 22 | gauge_v3.kick(alice, {"from": bob}) 23 | 24 | chain.sleep(4 * WEEK) 25 | 26 | gauge_v3.kick(alice, {"from": bob}) 27 | assert gauge_v3.working_balances(alice) == 4 * 10 ** 20 28 | 29 | with brownie.reverts("dev: kick not needed"): 30 | gauge_v3.kick(alice, {"from": bob}) 31 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV3/test_mass_exit_reward_claim.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import pytest 4 | from brownie import ZERO_ADDRESS 5 | 6 | REWARD = 10 ** 20 7 | WEEK = 7 * 86400 8 | 9 | 10 | @pytest.fixture(scope="module", autouse=True) 11 | def initial_setup( 12 | alice, 13 | accounts, 14 | chain, 15 | coin_reward, 16 | reward_contract, 17 | token, 18 | mock_lp_token, 19 | gauge_v3, 20 | gauge_controller, 21 | minter, 22 | ): 23 | # gauge setup 24 | token.set_minter(minter, {"from": alice}) 25 | gauge_controller.add_type(b"Liquidity", 10 ** 10, {"from": alice}) 26 | gauge_controller.add_gauge(gauge_v3, 0, 0, {"from": alice}) 27 | 28 | # deposit into gauge 29 | mock_lp_token.approve(gauge_v3, 2 ** 256 - 1, {"from": alice}) 30 | 31 | for acct in accounts[:10]: 32 | gauge_v3.deposit(10 ** 18, acct, {"from": alice}) 33 | 34 | # add rewards 35 | sigs = [ 36 | reward_contract.stake.signature[2:], 37 | reward_contract.withdraw.signature[2:], 38 | reward_contract.getReward.signature[2:], 39 | ] 40 | sigs = f"0x{sigs[0]}{sigs[1]}{sigs[2]}{'00' * 20}" 41 | 42 | gauge_v3.set_rewards(reward_contract, sigs, [coin_reward] + [ZERO_ADDRESS] * 7, {"from": alice}) 43 | 44 | # fund rewards 45 | coin_reward._mint_for_testing(reward_contract, REWARD) 46 | reward_contract.notifyRewardAmount(REWARD, {"from": alice}) 47 | 48 | # sleep half way through the reward period 49 | chain.sleep(WEEK) 50 | 51 | 52 | def test_mass_withdraw_claim_rewards(accounts, gauge_v3, coin_reward, mock_lp_token): 53 | for account in accounts[:10]: 54 | gauge_v3.withdraw(gauge_v3.balanceOf(account), {"from": account}) 55 | assert gauge_v3.claimed_reward(account, coin_reward) == 0 56 | assert gauge_v3.claimable_reward_write.call(account, coin_reward) > 0 57 | 58 | for account in accounts[:10]: 59 | gauge_v3.claim_rewards({"from": account}) 60 | assert math.isclose(coin_reward.balanceOf(account), REWARD / 10) 61 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeV3/test_set_rewards_receiver.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import ZERO_ADDRESS, compile_source 3 | 4 | from tests.conftest import approx 5 | 6 | REWARD = 10 ** 20 7 | WEEK = 7 * 86400 8 | LP_AMOUNT = 10 ** 18 9 | 10 | 11 | code = """ 12 | # @version 0.2.7 13 | 14 | from vyper.interfaces import ERC20 15 | 16 | first: address 17 | second: address 18 | 19 | @external 20 | def __init__(_first: address, _second: address): 21 | self.first = _first 22 | self.second = _second 23 | 24 | @external 25 | def claim_tokens() -> bool: 26 | ERC20(self.first).transfer(msg.sender, ERC20(self.first).balanceOf(self) / 2) 27 | ERC20(self.second).transfer(msg.sender, ERC20(self.second).balanceOf(self) / 2) 28 | 29 | return True 30 | """ 31 | 32 | 33 | @pytest.fixture(scope="module") 34 | def reward_contract(alice, coin_a, coin_b): 35 | contract = compile_source(code).Vyper.deploy(coin_a, coin_b, {"from": alice}) 36 | coin_a._mint_for_testing(contract, REWARD * 2) 37 | coin_b._mint_for_testing(contract, REWARD * 2) 38 | 39 | yield contract 40 | 41 | 42 | @pytest.fixture(scope="module", autouse=True) 43 | def initial_setup( 44 | alice, bob, chain, gauge_v3, reward_contract, coin_reward, coin_a, coin_b, mock_lp_token 45 | ): 46 | 47 | sigs = f"0x{'00' * 4}{'00' * 4}{reward_contract.claim_tokens.signature[2:]}{'00' * 20}" 48 | 49 | gauge_v3.set_rewards( 50 | reward_contract, sigs, [coin_a, coin_reward, coin_b] + [ZERO_ADDRESS] * 5, {"from": alice} 51 | ) 52 | 53 | # Deposit 54 | mock_lp_token.transfer(bob, LP_AMOUNT, {"from": alice}) 55 | mock_lp_token.approve(gauge_v3, LP_AMOUNT, {"from": bob}) 56 | gauge_v3.deposit(LP_AMOUNT, {"from": bob}) 57 | 58 | chain.sleep(WEEK) 59 | 60 | 61 | def test_claim_one_lp(alice, bob, chain, gauge_v3, coin_a, coin_b): 62 | chain.sleep(WEEK) 63 | 64 | gauge_v3.set_rewards_receiver(alice, {"from": bob}) 65 | 66 | gauge_v3.withdraw(LP_AMOUNT, {"from": bob}) 67 | gauge_v3.claim_rewards({"from": bob}) 68 | 69 | for coin in (coin_a, coin_b): 70 | reward = coin.balanceOf(alice) 71 | assert reward <= REWARD 72 | assert approx(REWARD, reward, 1.001 / WEEK) # ganache-cli jitter of 1 s 73 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeWrapper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/unitary/LiquidityGaugeWrapper/__init__.py -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeWrapper/test_approve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | 6 | @pytest.mark.parametrize("idx", range(5)) 7 | def test_initial_approval_is_zero(gauge_wrapper, accounts, idx): 8 | assert gauge_wrapper.allowance(accounts[0], accounts[idx]) == 0 9 | 10 | 11 | def test_approve(gauge_wrapper, accounts): 12 | gauge_wrapper.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 13 | 14 | assert gauge_wrapper.allowance(accounts[0], accounts[1]) == 10 ** 19 15 | 16 | 17 | def test_modify_approve(gauge_wrapper, accounts): 18 | gauge_wrapper.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 19 | gauge_wrapper.approve(accounts[1], 12345678, {"from": accounts[0]}) 20 | 21 | assert gauge_wrapper.allowance(accounts[0], accounts[1]) == 12345678 22 | 23 | 24 | def test_revoke_approve(gauge_wrapper, accounts): 25 | gauge_wrapper.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 26 | gauge_wrapper.approve(accounts[1], 0, {"from": accounts[0]}) 27 | 28 | assert gauge_wrapper.allowance(accounts[0], accounts[1]) == 0 29 | 30 | 31 | def test_approve_self(gauge_wrapper, accounts): 32 | gauge_wrapper.approve(accounts[0], 10 ** 19, {"from": accounts[0]}) 33 | 34 | assert gauge_wrapper.allowance(accounts[0], accounts[0]) == 10 ** 19 35 | 36 | 37 | def test_only_affects_target(gauge_wrapper, accounts): 38 | gauge_wrapper.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 39 | 40 | assert gauge_wrapper.allowance(accounts[1], accounts[0]) == 0 41 | 42 | 43 | def test_returns_true(gauge_wrapper, accounts): 44 | tx = gauge_wrapper.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 45 | 46 | assert tx.return_value is True 47 | 48 | 49 | def test_approval_event_fires(accounts, gauge_wrapper): 50 | tx = gauge_wrapper.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 51 | 52 | assert len(tx.events) == 1 53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10 ** 19] 54 | 55 | 56 | def test_increase_allowance(accounts, gauge_wrapper): 57 | gauge_wrapper.approve(accounts[1], 100, {"from": accounts[0]}) 58 | gauge_wrapper.increaseAllowance(accounts[1], 403, {"from": accounts[0]}) 59 | 60 | assert gauge_wrapper.allowance(accounts[0], accounts[1]) == 503 61 | 62 | 63 | def test_decrease_allowance(accounts, gauge_wrapper): 64 | gauge_wrapper.approve(accounts[1], 100, {"from": accounts[0]}) 65 | gauge_wrapper.decreaseAllowance(accounts[1], 34, {"from": accounts[0]}) 66 | 67 | assert gauge_wrapper.allowance(accounts[0], accounts[1]) == 66 68 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeWrapper/test_checkpoint.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | YEAR = 86400 * 365 5 | 6 | 7 | @pytest.fixture(scope="module", autouse=True) 8 | def setup(accounts, gauge_controller, minter, liquidity_gauge, token): 9 | token.set_minter(minter, {"from": accounts[0]}) 10 | 11 | gauge_controller.add_type(b"Liquidity", 10 ** 10, {"from": accounts[0]}) 12 | gauge_controller.add_gauge(liquidity_gauge, 0, 0, {"from": accounts[0]}) 13 | 14 | 15 | def test_user_checkpoint(accounts, gauge_wrapper): 16 | gauge_wrapper.user_checkpoint(accounts[1], {"from": accounts[1]}) 17 | 18 | 19 | def test_user_checkpoint_new_period(accounts, chain, gauge_wrapper): 20 | gauge_wrapper.user_checkpoint(accounts[1], {"from": accounts[1]}) 21 | chain.sleep(int(YEAR * 1.1)) 22 | gauge_wrapper.user_checkpoint(accounts[1], {"from": accounts[1]}) 23 | 24 | 25 | def test_user_checkpoint_wrong_account(accounts, gauge_wrapper): 26 | with brownie.reverts("dev: unauthorized"): 27 | gauge_wrapper.user_checkpoint(accounts[2], {"from": accounts[1]}) 28 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeWrapperUnit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/unitary/LiquidityGaugeWrapperUnit/__init__.py -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeWrapperUnit/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="module") 5 | def unit_gauge(LiquidityGaugeWrapperUnit, alice, liquidity_gauge, vault): 6 | contract = LiquidityGaugeWrapperUnit.deploy( 7 | "Tokenized Gauge", "TG", liquidity_gauge, {"from": alice} 8 | ) 9 | vault.set_token(contract, {"from": alice}) 10 | yield contract 11 | 12 | 13 | @pytest.fixture(scope="module") 14 | def vault(UnitVault, accounts): 15 | deployer = accounts.at("0xf827ac3a510eca8d7f356c9c9d78699d5848cabf", force=True) 16 | # increment the nonce to our mock vault has the hardcoded address 17 | for i in range(4): 18 | deployer.transfer(deployer, 0) 19 | 20 | yield UnitVault.deploy({"from": deployer}) 21 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeWrapperUnit/test_approve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pytest 4 | 5 | 6 | @pytest.mark.parametrize("idx", range(5)) 7 | def test_initial_approval_is_zero(unit_gauge, accounts, idx): 8 | assert unit_gauge.allowance(accounts[0], accounts[idx]) == 0 9 | 10 | 11 | def test_approve(unit_gauge, accounts): 12 | unit_gauge.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 13 | 14 | assert unit_gauge.allowance(accounts[0], accounts[1]) == 10 ** 19 15 | 16 | 17 | def test_modify_approve(unit_gauge, accounts): 18 | unit_gauge.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 19 | unit_gauge.approve(accounts[1], 12345678, {"from": accounts[0]}) 20 | 21 | assert unit_gauge.allowance(accounts[0], accounts[1]) == 12345678 22 | 23 | 24 | def test_revoke_approve(unit_gauge, accounts): 25 | unit_gauge.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 26 | unit_gauge.approve(accounts[1], 0, {"from": accounts[0]}) 27 | 28 | assert unit_gauge.allowance(accounts[0], accounts[1]) == 0 29 | 30 | 31 | def test_approve_self(unit_gauge, accounts): 32 | unit_gauge.approve(accounts[0], 10 ** 19, {"from": accounts[0]}) 33 | 34 | assert unit_gauge.allowance(accounts[0], accounts[0]) == 10 ** 19 35 | 36 | 37 | def test_only_affects_target(unit_gauge, accounts): 38 | unit_gauge.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 39 | 40 | assert unit_gauge.allowance(accounts[1], accounts[0]) == 0 41 | 42 | 43 | def test_returns_true(unit_gauge, accounts): 44 | tx = unit_gauge.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 45 | 46 | assert tx.return_value is True 47 | 48 | 49 | def test_approval_event_fires(accounts, unit_gauge): 50 | tx = unit_gauge.approve(accounts[1], 10 ** 19, {"from": accounts[0]}) 51 | 52 | assert len(tx.events) == 1 53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10 ** 19] 54 | 55 | 56 | def test_increase_allowance(accounts, unit_gauge): 57 | unit_gauge.approve(accounts[1], 100, {"from": accounts[0]}) 58 | unit_gauge.increaseAllowance(accounts[1], 403, {"from": accounts[0]}) 59 | 60 | assert unit_gauge.allowance(accounts[0], accounts[1]) == 503 61 | 62 | 63 | def test_decrease_allowance(accounts, unit_gauge): 64 | unit_gauge.approve(accounts[1], 100, {"from": accounts[0]}) 65 | unit_gauge.decreaseAllowance(accounts[1], 34, {"from": accounts[0]}) 66 | 67 | assert unit_gauge.allowance(accounts[0], accounts[1]) == 66 68 | -------------------------------------------------------------------------------- /tests/unitary/LiquidityGaugeWrapperUnit/test_checkpoint.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | YEAR = 86400 * 365 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def setup(accounts, gauge_controller, minter, liquidity_gauge, token): 8 | token.set_minter(minter, {"from": accounts[0]}) 9 | 10 | gauge_controller.add_type(b"Liquidity", 10 ** 10, {"from": accounts[0]}) 11 | gauge_controller.add_gauge(liquidity_gauge, 0, 0, {"from": accounts[0]}) 12 | 13 | 14 | def test_user_checkpoint(accounts, unit_gauge): 15 | unit_gauge.user_checkpoint(accounts[1], {"from": accounts[1]}) 16 | 17 | 18 | def test_user_checkpoint_new_period(accounts, chain, unit_gauge): 19 | unit_gauge.user_checkpoint(accounts[1], {"from": accounts[1]}) 20 | chain.sleep(int(YEAR * 1.1)) 21 | unit_gauge.user_checkpoint(accounts[1], {"from": accounts[1]}) 22 | 23 | 24 | def test_user_checkpoint_wrong_account(accounts, unit_gauge): 25 | # this is allowed, because anyone can also transfer tokens to trigger 26 | unit_gauge.user_checkpoint(accounts[2], {"from": accounts[1]}) 27 | -------------------------------------------------------------------------------- /tests/unitary/PoolProxy/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="module", autouse=True) 5 | def admin_setup(accounts, pool_proxy, pool): 6 | # set admins as accounts[:3] 7 | pool_proxy.commit_set_admins(accounts[0], accounts[1], accounts[2], {"from": accounts[0]}) 8 | pool_proxy.apply_set_admins({"from": accounts[0]}) 9 | 10 | # set pool_proxy as owner of pool 11 | pool.commit_transfer_ownership(pool_proxy, {"from": accounts[0]}) 12 | pool.apply_transfer_ownership({"from": accounts[0]}) 13 | -------------------------------------------------------------------------------- /tests/unitary/PoolProxy/test_emergency_admin.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 5 | 6 | 7 | def test_kill(accounts, pool_proxy, pool): 8 | pool_proxy.kill_me(pool, {"from": accounts[2]}) 9 | 10 | 11 | @pytest.mark.parametrize("idx", [0, 2]) 12 | def test_unkill(accounts, pool_proxy, pool, idx): 13 | pool_proxy.unkill_me(pool, {"from": accounts[idx]}) 14 | 15 | 16 | @pytest.mark.parametrize("idx", [0, 1, 3]) 17 | def test_kill_no_access(accounts, pool_proxy, pool, idx): 18 | with brownie.reverts("Access denied"): 19 | pool_proxy.kill_me(pool, {"from": accounts[idx]}) 20 | 21 | 22 | @pytest.mark.parametrize("idx", [1, 3]) 23 | def test_unkill_no_access(accounts, pool_proxy, pool, idx): 24 | with brownie.reverts("Access denied"): 25 | pool_proxy.unkill_me(pool, {"from": accounts[idx]}) 26 | -------------------------------------------------------------------------------- /tests/unitary/PoolProxy/test_proxy_burn.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | from brownie import ETH_ADDRESS, ZERO_ADDRESS 4 | 5 | burner_mock = """ 6 | # @version 0.2.7 7 | 8 | is_burned: public(HashMap[address, bool]) 9 | 10 | @payable 11 | @external 12 | def burn(coin: address) -> bool: 13 | self.is_burned[coin] = True 14 | return True 15 | """ 16 | 17 | 18 | @pytest.fixture(scope="module") 19 | def burner(accounts, pool_proxy, coin_a): 20 | contract = brownie.compile_source(burner_mock).Vyper.deploy({"from": accounts[0]}) 21 | pool_proxy.set_burner(coin_a, contract, {"from": accounts[0]}) 22 | pool_proxy.set_burner(ETH_ADDRESS, contract, {"from": accounts[0]}) 23 | accounts[0].transfer(pool_proxy, 31337) 24 | 25 | yield contract 26 | 27 | 28 | @pytest.mark.parametrize("idx", range(2)) 29 | def test_burn(accounts, pool_proxy, burner, coin_a, idx): 30 | pool_proxy.burn(coin_a, {"from": accounts[idx]}) 31 | 32 | assert burner.is_burned(coin_a) 33 | assert pool_proxy.balance() == 31337 34 | assert burner.balance() == 0 35 | 36 | 37 | @pytest.mark.parametrize("idx", range(2)) 38 | def test_burn_eth(accounts, pool_proxy, burner, idx): 39 | pool_proxy.burn(ETH_ADDRESS, {"from": accounts[idx]}) 40 | 41 | assert burner.is_burned(ETH_ADDRESS) 42 | assert pool_proxy.balance() == 0 43 | assert burner.balance() == 31337 44 | 45 | 46 | @pytest.mark.parametrize("idx", range(2)) 47 | def test_burn_many(accounts, pool_proxy, burner, coin_a, idx): 48 | pool_proxy.burn_many([coin_a, ETH_ADDRESS] + [ZERO_ADDRESS] * 18, {"from": accounts[0]}) 49 | 50 | assert burner.is_burned(coin_a) 51 | assert burner.is_burned(ETH_ADDRESS) 52 | 53 | assert pool_proxy.balance() == 0 54 | assert burner.balance() == 31337 55 | 56 | 57 | def test_burn_not_exists(accounts, pool_proxy): 58 | with brownie.reverts("dev: should implement burn()"): 59 | pool_proxy.burn(accounts[1], {"from": accounts[0]}) 60 | 61 | 62 | def test_burn_many_not_exists(accounts, pool_proxy, burner, coin_a): 63 | with brownie.reverts("dev: should implement burn()"): 64 | pool_proxy.burn_many( 65 | [coin_a, ETH_ADDRESS, accounts[1]] + [ZERO_ADDRESS] * 17, {"from": accounts[0]} 66 | ) 67 | -------------------------------------------------------------------------------- /tests/unitary/PoolProxy/test_set_admins.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 5 | 6 | 7 | def test_set_admins(accounts, pool_proxy): 8 | pool_proxy.commit_set_admins(accounts[1], accounts[2], accounts[3], {"from": accounts[0]}) 9 | pool_proxy.apply_set_admins() 10 | 11 | assert pool_proxy.ownership_admin() == accounts[1] 12 | assert pool_proxy.parameter_admin() == accounts[2] 13 | assert pool_proxy.emergency_admin() == accounts[3] 14 | 15 | 16 | @pytest.mark.parametrize("idx", range(1, 4)) 17 | def test_set_admins_no_access(accounts, pool_proxy, idx): 18 | with brownie.reverts("Access denied"): 19 | pool_proxy.commit_set_admins(accounts[1], accounts[2], accounts[3], {"from": accounts[idx]}) 20 | with brownie.reverts("Access denied"): 21 | pool_proxy.apply_set_admins({"from": accounts[idx]}) 22 | -------------------------------------------------------------------------------- /tests/unitary/RewardStream/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie_tokens import ERC20 3 | 4 | 5 | @pytest.fixture(scope="module") 6 | def reward_token(bob): 7 | token = ERC20() 8 | token._mint_for_testing(bob, 10 ** 19) 9 | return token 10 | 11 | 12 | @pytest.fixture(scope="module") 13 | def stream(RewardStream, alice, bob, reward_token): 14 | contract = RewardStream.deploy(alice, bob, reward_token, 86400 * 10, {"from": alice}) 15 | reward_token.approve(contract, 2 ** 256 - 1, {"from": bob}) 16 | return contract 17 | -------------------------------------------------------------------------------- /tests/unitary/RewardStream/test_add_receiver.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_receiver_count_increases(alice, charlie, stream): 5 | pre_receiver_count = stream.receiver_count() 6 | stream.add_receiver(charlie, {"from": alice}) 7 | 8 | assert stream.receiver_count() == pre_receiver_count + 1 9 | 10 | 11 | def test_receiver_activation(alice, charlie, stream): 12 | pre_activation = stream.reward_receivers(charlie) 13 | stream.add_receiver(charlie, {"from": alice}) 14 | 15 | assert pre_activation is False 16 | assert stream.reward_receivers(charlie) is True 17 | 18 | 19 | def test_reverts_for_non_owner(bob, charlie, stream): 20 | with brownie.reverts("dev: only owner"): 21 | stream.add_receiver(charlie, {"from": bob}) 22 | 23 | 24 | def test_reverts_for_active_receiver(alice, charlie, stream): 25 | stream.add_receiver(charlie, {"from": alice}) 26 | with brownie.reverts("dev: receiver is active"): 27 | stream.add_receiver(charlie, {"from": alice}) 28 | -------------------------------------------------------------------------------- /tests/unitary/RewardStream/test_get_reward.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_only_active_receiver_can_call(charlie, stream): 5 | with brownie.reverts("dev: caller is not receiver"): 6 | stream.get_reward({"from": charlie}) 7 | -------------------------------------------------------------------------------- /tests/unitary/RewardStream/test_notify_reward_amount.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import brownie 4 | from brownie import chain 5 | 6 | 7 | def test_only_distributor_allowed(alice, stream): 8 | with brownie.reverts("dev: only distributor"): 9 | stream.notify_reward_amount(10 ** 18, {"from": alice}) 10 | 11 | 12 | def test_retrieves_reward_token(bob, stream, reward_token): 13 | stream.notify_reward_amount(10 ** 18, {"from": bob}) 14 | post_notify = reward_token.balanceOf(stream) 15 | 16 | assert post_notify == 10 ** 18 17 | 18 | 19 | def test_reward_rate_updates(bob, stream): 20 | stream.notify_reward_amount(10 ** 18, {"from": bob}) 21 | post_notify = stream.reward_rate() 22 | 23 | assert post_notify > 0 24 | assert post_notify == 10 ** 18 / (86400 * 10) 25 | 26 | 27 | def test_reward_rate_updates_mid_duration(bob, stream): 28 | stream.notify_reward_amount(10 ** 18, {"from": bob}) 29 | chain.sleep(86400 * 5) # half of the duration 30 | 31 | # top up the balance to be 10 ** 18 again 32 | stream.notify_reward_amount(10 ** 18 / 2, {"from": bob}) 33 | post_notify = stream.reward_rate() 34 | 35 | # should relatively close .00001 seems about good of a heuristic 36 | assert math.isclose(post_notify, 10 ** 18 / (86400 * 10), rel_tol=0.00001) 37 | 38 | 39 | def test_period_finish_updates(bob, stream): 40 | tx = stream.notify_reward_amount(10 ** 18, {"from": bob}) 41 | 42 | assert stream.period_finish() == tx.timestamp + 86400 * 10 43 | 44 | 45 | def test_update_last_update_time(bob, stream): 46 | tx = stream.notify_reward_amount(10 ** 18, {"from": bob}) 47 | 48 | assert stream.last_update_time() == tx.timestamp 49 | -------------------------------------------------------------------------------- /tests/unitary/RewardStream/test_ownership_change.py: -------------------------------------------------------------------------------- 1 | from itertools import product 2 | 3 | import brownie 4 | import pytest 5 | 6 | 7 | @pytest.mark.parametrize("commit,accept", product([0, 1], repeat=2)) 8 | def test_change_ownership(alice, charlie, commit, accept, stream): 9 | if commit: 10 | stream.commit_transfer_ownership(charlie, {"from": alice}) 11 | 12 | if accept: 13 | stream.accept_transfer_ownership({"from": charlie}) 14 | 15 | assert stream.owner() == charlie if commit and accept else alice 16 | 17 | 18 | def test_commit_only_owner(charlie, stream): 19 | with brownie.reverts("dev: only owner"): 20 | stream.commit_transfer_ownership(charlie, {"from": charlie}) 21 | 22 | 23 | def test_accept_only_owner(alice, bob, stream): 24 | stream.commit_transfer_ownership(bob, {"from": alice}) 25 | 26 | with brownie.reverts("dev: only new owner"): 27 | stream.accept_transfer_ownership({"from": alice}) 28 | -------------------------------------------------------------------------------- /tests/unitary/RewardStream/test_remove_receiver.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | from brownie import chain 4 | 5 | 6 | @pytest.fixture(autouse=True) 7 | def module_setup(alice, bob, charlie, stream, reward_token): 8 | stream.add_receiver(charlie, {"from": alice}) 9 | stream.notify_reward_amount(10 ** 18, {"from": bob}) 10 | chain.sleep(86400 * 10) 11 | 12 | 13 | def test_receiver_deactivates(alice, charlie, stream): 14 | pre_remove = stream.reward_receivers(charlie) 15 | stream.remove_receiver(charlie, {"from": alice}) 16 | 17 | assert pre_remove is True 18 | assert stream.reward_receivers(charlie) is False 19 | 20 | 21 | def test_receiver_count_decreases(alice, charlie, stream): 22 | pre_remove = stream.receiver_count() 23 | stream.remove_receiver(charlie, {"from": alice}) 24 | post_remove = stream.receiver_count() 25 | 26 | assert post_remove == pre_remove - 1 27 | 28 | 29 | def test_updates_last_update_time(alice, charlie, stream): 30 | pre_remove = stream.last_update_time() 31 | tx = stream.remove_receiver(charlie, {"from": alice}) 32 | post_remove = stream.last_update_time() 33 | 34 | assert pre_remove < post_remove 35 | # handle network jitter 36 | assert abs(post_remove - tx.timestamp) <= 1 37 | 38 | 39 | def test_updates_reward_per_receiver_total(alice, charlie, stream): 40 | pre_remove = stream.reward_per_receiver_total() 41 | stream.remove_receiver(charlie, {"from": alice}) 42 | post_remove = stream.reward_per_receiver_total() 43 | 44 | assert pre_remove < post_remove 45 | assert 0.9999 < post_remove / 10 ** 18 <= 1 46 | 47 | 48 | def test_reverts_for_non_owner(bob, charlie, stream): 49 | with brownie.reverts("dev: only owner"): 50 | stream.remove_receiver(charlie, {"from": bob}) 51 | 52 | 53 | def test_reverts_for_inactive_receiver(alice, bob, stream): 54 | with brownie.reverts("dev: receiver is inactive"): 55 | stream.remove_receiver(bob, {"from": alice}) 56 | -------------------------------------------------------------------------------- /tests/unitary/RewardStream/test_set_reward_distributor.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_successful_change(alice, charlie, stream): 5 | stream.set_reward_distributor(charlie, {"from": alice}) 6 | 7 | assert stream.distributor() == charlie 8 | 9 | 10 | def test_only_owner(bob, charlie, stream): 11 | with brownie.reverts("dev: only owner"): 12 | stream.set_reward_distributor(charlie, {"from": bob}) 13 | -------------------------------------------------------------------------------- /tests/unitary/RewardStream/test_set_reward_duration.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_updates_reward_duration(alice, stream): 5 | pre_update = stream.reward_duration() 6 | stream.set_reward_duration(86400 * 365, {"from": alice}) 7 | 8 | assert pre_update == 86400 * 10 9 | assert stream.reward_duration() == 86400 * 365 10 | 11 | 12 | def test_only_owner(charlie, stream): 13 | with brownie.reverts("dev: only owner"): 14 | stream.set_reward_duration(10, {"from": charlie}) 15 | 16 | 17 | def test_mid_reward_distribution_period(alice, bob, stream): 18 | stream.notify_reward_amount(10 ** 18, {"from": bob}) 19 | 20 | with brownie.reverts("dev: reward period currently active"): 21 | stream.set_reward_duration(10, {"from": alice}) 22 | -------------------------------------------------------------------------------- /tests/unitary/RewardsOnlyGauge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/unitary/RewardsOnlyGauge/__init__.py -------------------------------------------------------------------------------- /tests/unitary/RewardsOnlyGauge/test_claim_rewards_none.py: -------------------------------------------------------------------------------- 1 | from brownie import ZERO_ADDRESS 2 | 3 | REWARD = 10 ** 20 4 | WEEK = 7 * 86400 5 | LP_AMOUNT = 10 ** 18 6 | 7 | 8 | def test_claim_no_deposit( 9 | alice, bob, chain, rewards_only_gauge, mock_lp_token, reward_contract, coin_reward 10 | ): 11 | # Fund 12 | mock_lp_token.approve(rewards_only_gauge, LP_AMOUNT, {"from": alice}) 13 | rewards_only_gauge.deposit(LP_AMOUNT, {"from": alice}) 14 | 15 | coin_reward._mint_for_testing(reward_contract, REWARD) 16 | reward_contract.notifyRewardAmount(REWARD, {"from": alice}) 17 | 18 | rewards_only_gauge.set_rewards( 19 | reward_contract, 20 | "0x3d18b912", 21 | [coin_reward] + [ZERO_ADDRESS] * 7, 22 | {"from": alice}, 23 | ) 24 | 25 | chain.sleep(WEEK) 26 | 27 | rewards_only_gauge.claim_rewards({"from": bob}) 28 | 29 | assert coin_reward.balanceOf(bob) == 0 30 | 31 | 32 | def test_claim_no_rewards( 33 | alice, bob, chain, rewards_only_gauge, mock_lp_token, reward_contract, coin_reward 34 | ): 35 | # Deposit 36 | mock_lp_token.transfer(bob, LP_AMOUNT, {"from": alice}) 37 | mock_lp_token.approve(rewards_only_gauge, LP_AMOUNT, {"from": bob}) 38 | rewards_only_gauge.deposit(LP_AMOUNT, {"from": bob}) 39 | 40 | chain.sleep(WEEK) 41 | 42 | rewards_only_gauge.withdraw(LP_AMOUNT, {"from": bob}) 43 | rewards_only_gauge.claim_rewards({"from": bob}) 44 | 45 | assert coin_reward.balanceOf(bob) == 0 46 | -------------------------------------------------------------------------------- /tests/unitary/RewardsOnlyGauge/test_mass_exit_reward_claim.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import pytest 4 | from brownie import ZERO_ADDRESS 5 | 6 | REWARD = 10 ** 20 7 | WEEK = 7 * 86400 8 | 9 | 10 | @pytest.fixture(scope="module") 11 | def reward_contract(RewardStream, alice, charlie, rewards_only_gauge, coin_reward): 12 | contract = RewardStream.deploy(alice, charlie, coin_reward, 86400 * 7, {"from": alice}) 13 | contract.add_receiver(rewards_only_gauge, {"from": alice}) 14 | coin_reward.approve(contract, 2 ** 256 - 1, {"from": charlie}) 15 | coin_reward._mint_for_testing(charlie, REWARD, {"from": alice}) 16 | return contract 17 | 18 | 19 | @pytest.fixture(scope="module", autouse=True) 20 | def initial_setup( 21 | alice, 22 | charlie, 23 | accounts, 24 | chain, 25 | coin_reward, 26 | reward_contract, 27 | token, 28 | mock_lp_token, 29 | rewards_only_gauge, 30 | gauge_controller, 31 | minter, 32 | ): 33 | # gauge setup 34 | token.set_minter(minter, {"from": alice}) 35 | gauge_controller.add_type(b"Liquidity", 10 ** 10, {"from": alice}) 36 | gauge_controller.add_gauge(rewards_only_gauge, 0, 0, {"from": alice}) 37 | 38 | # deposit into gauge 39 | mock_lp_token.approve(rewards_only_gauge, 2 ** 256 - 1, {"from": alice}) 40 | 41 | for acct in accounts[:10]: 42 | rewards_only_gauge.deposit(10 ** 18, acct, {"from": alice}) 43 | 44 | # add rewards 45 | sigs = reward_contract.get_reward.signature 46 | rewards_only_gauge.set_rewards( 47 | reward_contract, sigs, [coin_reward] + [ZERO_ADDRESS] * 7, {"from": alice} 48 | ) 49 | 50 | # fund rewards 51 | reward_contract.notify_reward_amount(REWARD, {"from": charlie}) 52 | 53 | # sleep half way through the reward period 54 | chain.sleep(WEEK) 55 | 56 | 57 | def test_mass_withdraw_claim_rewards(accounts, rewards_only_gauge, coin_reward, mock_lp_token): 58 | for account in accounts[:10]: 59 | rewards_only_gauge.withdraw(rewards_only_gauge.balanceOf(account), {"from": account}) 60 | assert rewards_only_gauge.claimed_reward(account, coin_reward) == 0 61 | assert rewards_only_gauge.claimable_reward_write.call(account, coin_reward) > 0 62 | 63 | for account in accounts[:10]: 64 | rewards_only_gauge.claim_rewards({"from": account}) 65 | assert math.isclose(coin_reward.balanceOf(account), REWARD / 10) 66 | -------------------------------------------------------------------------------- /tests/unitary/RewardsOnlyGauge/test_set_rewards_receiver.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import ZERO_ADDRESS, compile_source 3 | 4 | from tests.conftest import approx 5 | 6 | REWARD = 10 ** 20 7 | WEEK = 7 * 86400 8 | LP_AMOUNT = 10 ** 18 9 | 10 | 11 | code = """ 12 | # @version 0.2.7 13 | 14 | from vyper.interfaces import ERC20 15 | 16 | first: address 17 | second: address 18 | 19 | @external 20 | def __init__(_first: address, _second: address): 21 | self.first = _first 22 | self.second = _second 23 | 24 | @external 25 | def claim_tokens() -> bool: 26 | ERC20(self.first).transfer(msg.sender, ERC20(self.first).balanceOf(self) / 2) 27 | ERC20(self.second).transfer(msg.sender, ERC20(self.second).balanceOf(self) / 2) 28 | 29 | return True 30 | """ 31 | 32 | 33 | @pytest.fixture(scope="module") 34 | def reward_contract(alice, coin_a, coin_b): 35 | contract = compile_source(code).Vyper.deploy(coin_a, coin_b, {"from": alice}) 36 | coin_a._mint_for_testing(contract, REWARD * 2) 37 | coin_b._mint_for_testing(contract, REWARD * 2) 38 | 39 | yield contract 40 | 41 | 42 | @pytest.fixture(scope="module", autouse=True) 43 | def initial_setup( 44 | alice, 45 | bob, 46 | chain, 47 | rewards_only_gauge, 48 | reward_contract, 49 | coin_reward, 50 | coin_a, 51 | coin_b, 52 | mock_lp_token, 53 | ): 54 | 55 | sigs = reward_contract.claim_tokens.signature 56 | 57 | rewards_only_gauge.set_rewards( 58 | reward_contract, sigs, [coin_a, coin_reward, coin_b] + [ZERO_ADDRESS] * 5, {"from": alice} 59 | ) 60 | 61 | # Deposit 62 | mock_lp_token.transfer(bob, LP_AMOUNT, {"from": alice}) 63 | mock_lp_token.approve(rewards_only_gauge, LP_AMOUNT, {"from": bob}) 64 | rewards_only_gauge.deposit(LP_AMOUNT, {"from": bob}) 65 | 66 | chain.sleep(WEEK) 67 | 68 | 69 | def test_claim_one_lp(alice, bob, chain, rewards_only_gauge, coin_a, coin_b): 70 | chain.sleep(WEEK) 71 | 72 | rewards_only_gauge.set_rewards_receiver(alice, {"from": bob}) 73 | 74 | rewards_only_gauge.withdraw(LP_AMOUNT, {"from": bob}) 75 | rewards_only_gauge.claim_rewards({"from": bob}) 76 | 77 | for coin in (coin_a, coin_b): 78 | reward = coin.balanceOf(alice) 79 | assert reward <= REWARD 80 | assert approx(REWARD, reward, 1.001 / WEEK) # ganache-cli jitter of 1 s 81 | -------------------------------------------------------------------------------- /tests/unitary/Sidechain/child_chain_streamer/test_add_reward.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_only_owner(bob, child_chain_streamer, token): 5 | with brownie.reverts("dev: owner only"): 6 | child_chain_streamer.add_reward(token, bob, 86400 * 7, {"from": bob}) 7 | 8 | 9 | def test_reward_data_updated(alice, charlie, child_chain_streamer, token): 10 | 11 | child_chain_streamer.add_reward(token, charlie, 86400 * 7, {"from": alice}) 12 | expected_data = (charlie, 0, 0, 86400 * 7, 0, 0) 13 | 14 | assert child_chain_streamer.reward_count() == 2 15 | assert child_chain_streamer.reward_tokens(1) == token 16 | assert child_chain_streamer.reward_data(token) == expected_data 17 | 18 | 19 | def test_reverts_for_double_adding(alice, charlie, child_chain_streamer, token): 20 | child_chain_streamer.add_reward(token, charlie, 86400 * 7, {"from": alice}) 21 | 22 | with brownie.reverts("Reward token already added"): 23 | child_chain_streamer.add_reward(token, charlie, 86400 * 7, {"from": alice}) 24 | -------------------------------------------------------------------------------- /tests/unitary/Sidechain/child_chain_streamer/test_admin_functions.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_set_reward_duration_admin_only(alice, bob, token, child_chain_streamer): 5 | with brownie.reverts("dev: only owner"): 6 | child_chain_streamer.set_reward_duration(token, 86400 * 7, {"from": bob}) 7 | 8 | child_chain_streamer.set_reward_duration(token, 86400 * 7, {"from": alice}) 9 | 10 | assert child_chain_streamer.reward_data(token)["duration"] == 86400 * 7 11 | 12 | 13 | def test_set_reward_distributor_admin_only(alice, bob, charlie, token, child_chain_streamer): 14 | with brownie.reverts("dev: only owner"): 15 | child_chain_streamer.set_reward_distributor(token, bob, {"from": bob}) 16 | 17 | child_chain_streamer.set_reward_distributor(token, charlie, {"from": alice}) 18 | 19 | assert child_chain_streamer.reward_data(token)["distributor"] == charlie 20 | 21 | 22 | def test_commit_transfer_ownership_admin_only(alice, bob, charlie, child_chain_streamer): 23 | with brownie.reverts("dev: only owner"): 24 | child_chain_streamer.commit_transfer_ownership(bob, {"from": bob}) 25 | 26 | child_chain_streamer.commit_transfer_ownership(charlie, {"from": alice}) 27 | 28 | assert child_chain_streamer.future_owner() == charlie 29 | 30 | 31 | def test_accept_transfer_ownership_future_admin_only(alice, bob, charlie, child_chain_streamer): 32 | child_chain_streamer.commit_transfer_ownership(charlie, {"from": alice}) 33 | 34 | with brownie.reverts("dev: only new owner"): 35 | child_chain_streamer.accept_transfer_ownership({"from": bob}) 36 | 37 | child_chain_streamer.accept_transfer_ownership({"from": charlie}) 38 | 39 | assert child_chain_streamer.owner() == charlie 40 | 41 | 42 | def test_set_receiver_admin_only(alice, bob, charlie, child_chain_streamer): 43 | with brownie.reverts("dev: only owner"): 44 | child_chain_streamer.set_receiver(bob, {"from": bob}) 45 | 46 | child_chain_streamer.set_receiver(charlie, {"from": alice}) 47 | assert child_chain_streamer.reward_receiver() == charlie 48 | -------------------------------------------------------------------------------- /tests/unitary/Sidechain/child_chain_streamer/test_get_reward_child_chain.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import pytest 4 | 5 | DAY = 86400 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def local_setup(alice, bob, charlie, coin_reward, child_chain_streamer): 10 | coin_reward._mint_for_testing(alice, 100 * 10 ** 18) 11 | coin_reward.transfer(child_chain_streamer, 100 * 10 ** 18, {"from": alice}) 12 | child_chain_streamer.notify_reward_amount(coin_reward, {"from": charlie}) 13 | 14 | 15 | @pytest.mark.parametrize("idx", range(5)) 16 | def test_unguarded(accounts, child_chain_streamer, idx): 17 | child_chain_streamer.get_reward({"from": accounts[idx]}) 18 | 19 | 20 | def test_last_update_time(bob, chain, child_chain_streamer): 21 | chain.sleep(1000) 22 | tx = child_chain_streamer.get_reward({"from": bob}) 23 | 24 | assert tx.timestamp == child_chain_streamer.last_update_time() 25 | 26 | 27 | def test_update_reward(bob, coin_reward, chain, child_chain_streamer): 28 | chain.mine(timedelta=DAY * 3) 29 | tx = child_chain_streamer.get_reward({"from": bob}) 30 | 31 | assert tx.subcalls[-1]["to"] == coin_reward 32 | assert tx.subcalls[-1]["function"] == "transfer(address,uint256)" 33 | assert math.isclose(coin_reward.balanceOf(bob), 3 * 100 * 10 ** 18 // 7, rel_tol=0.0001) 34 | -------------------------------------------------------------------------------- /tests/unitary/Sidechain/child_chain_streamer/test_remove_reward.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | from brownie import ZERO_ADDRESS 4 | 5 | DAY = 86400 6 | 7 | 8 | @pytest.fixture(scope="module", autouse=True) 9 | def local_setup(alice, charlie, coin_reward, child_chain_streamer): 10 | coin_reward._mint_for_testing(alice, 100 * 10 ** 18) 11 | coin_reward.transfer(child_chain_streamer, 100 * 10 ** 18, {"from": alice}) 12 | 13 | 14 | def test_admin_only(charlie, child_chain_streamer, coin_reward): 15 | with brownie.reverts("dev: only owner"): 16 | child_chain_streamer.remove_reward(coin_reward, {"from": charlie}) 17 | 18 | 19 | def test_reward_not_active(alice, child_chain_streamer, token): 20 | with brownie.reverts("Reward token not added"): 21 | child_chain_streamer.remove_reward(token, {"from": alice}) 22 | 23 | 24 | def test_returns_reward_to_owner(alice, child_chain_streamer, coin_reward): 25 | child_chain_streamer.remove_reward(coin_reward, {"from": alice}) 26 | 27 | assert coin_reward.balanceOf(alice) == 100 * 10 ** 18 28 | 29 | 30 | def test_reward_data_cleared(alice, child_chain_streamer, coin_reward): 31 | child_chain_streamer.remove_reward(coin_reward, {"from": alice}) 32 | 33 | assert child_chain_streamer.reward_data(coin_reward) == (ZERO_ADDRESS, 0, 0, 0, 0, 0) 34 | 35 | 36 | def test_reward_indexes_corrected(alice, child_chain_streamer, token, coin_reward): 37 | child_chain_streamer.add_reward(token, alice, DAY * 18, {"from": alice}) 38 | child_chain_streamer.remove_reward(coin_reward, {"from": alice}) 39 | 40 | child_chain_streamer.reward_tokens(0) == token 41 | -------------------------------------------------------------------------------- /tests/unitary/Sidechain/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import ETH_ADDRESS, ZERO_ADDRESS 3 | 4 | 5 | @pytest.fixture(scope="module") 6 | def _root_gauge_setup(token, minter, chain, alice): 7 | token.set_minter(minter, {"from": alice}) 8 | chain.mine(timedelta=604800) 9 | token.update_mining_parameters({"from": alice}) 10 | 11 | 12 | @pytest.fixture(scope="module") 13 | def anyswap_root_gauge(_root_gauge_setup, RootGaugeAnyswap, minter, alice): 14 | return RootGaugeAnyswap.deploy(minter, alice, ETH_ADDRESS, ZERO_ADDRESS, {"from": alice}) 15 | 16 | 17 | @pytest.fixture(scope="module") 18 | def polygon_root_gauge(_root_gauge_setup, RootGaugePolygon, minter, alice): 19 | return RootGaugePolygon.deploy(minter, alice, {"from": alice}) 20 | 21 | 22 | @pytest.fixture(scope="module") 23 | def xdai_root_gauge(_root_gauge_setup, RootGaugeXdai, minter, alice): 24 | return RootGaugeXdai.deploy(minter, alice, {"from": alice}) 25 | 26 | 27 | @pytest.fixture(scope="module") 28 | def child_chain_streamer(alice, bob, ChildChainStreamer, coin_reward): 29 | return ChildChainStreamer.deploy(alice, bob, coin_reward, {"from": alice}) 30 | 31 | 32 | @pytest.fixture(scope="module") 33 | def checkpoint_proxy(alice, CheckpointProxy): 34 | return CheckpointProxy.deploy({"from": alice}) 35 | 36 | 37 | @pytest.fixture(scope="module", params=range(3), ids=["Anyswap", "Polygon", "XDAI"]) 38 | def root_gauge(request, anyswap_root_gauge, polygon_root_gauge, xdai_root_gauge): 39 | if request.param == 0: 40 | return anyswap_root_gauge 41 | elif request.param == 1: 42 | return polygon_root_gauge 43 | else: 44 | return xdai_root_gauge 45 | -------------------------------------------------------------------------------- /tests/unitary/Sidechain/test_accept_transfer_ownership.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | 5 | @pytest.fixture(scope="module", autouse=True) 6 | def local_setup(alice, bob, root_gauge): 7 | root_gauge.commit_transfer_ownership(bob, {"from": alice}) 8 | 9 | 10 | @pytest.mark.parametrize("idx", range(2, 6)) 11 | def test_future_admin_only(root_gauge, accounts, idx): 12 | with brownie.reverts("dev: future admin only"): 13 | root_gauge.accept_transfer_ownership({"from": accounts[idx]}) 14 | 15 | 16 | def test_admin_updated(root_gauge, bob): 17 | before = root_gauge.admin() 18 | root_gauge.accept_transfer_ownership({"from": bob}) 19 | 20 | assert before != bob 21 | assert root_gauge.admin() == bob 22 | 23 | 24 | def test_event_emitted(bob, root_gauge): 25 | tx = root_gauge.accept_transfer_ownership({"from": bob}) 26 | 27 | assert "ApplyOwnership" in tx.events 28 | assert tx.events["ApplyOwnership"]["admin"] == bob 29 | -------------------------------------------------------------------------------- /tests/unitary/Sidechain/test_checkpoint_proxy.py: -------------------------------------------------------------------------------- 1 | def test_checkpoint_single(alice, root_gauge, checkpoint_proxy): 2 | tx = checkpoint_proxy.checkpoint(root_gauge, {"from": alice}) 3 | 4 | assert tx.return_value is True 5 | 6 | assert tx.subcalls[0]["function"] == "checkpoint()" 7 | assert tx.subcalls[0]["to"] == root_gauge 8 | -------------------------------------------------------------------------------- /tests/unitary/Sidechain/test_commit_transfer_ownership.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize("idx", range(1, 6)) 6 | def test_admin_only(root_gauge, accounts, idx): 7 | with brownie.reverts("dev: admin only"): 8 | root_gauge.commit_transfer_ownership(accounts[idx], {"from": accounts[idx]}) 9 | 10 | 11 | def test_admin_remains_the_same_future_admin_changed(alice, bob, root_gauge): 12 | before = root_gauge.future_admin() 13 | root_gauge.commit_transfer_ownership(bob, {"from": alice}) 14 | 15 | assert root_gauge.admin() == alice 16 | assert root_gauge.future_admin() == bob and before != bob 17 | 18 | 19 | def test_future_admin_not_admin_until_accepted(alice, bob, charlie, root_gauge): 20 | root_gauge.commit_transfer_ownership(bob, {"from": alice}) 21 | 22 | with brownie.reverts("dev: admin only"): 23 | root_gauge.commit_transfer_ownership(charlie, {"from": bob}) 24 | 25 | 26 | def test_event_emitted(alice, bob, root_gauge): 27 | tx = root_gauge.commit_transfer_ownership(bob, {"from": alice}) 28 | 29 | assert "CommitOwnership" in tx.events 30 | assert tx.events["CommitOwnership"]["admin"] == bob 31 | -------------------------------------------------------------------------------- /tests/unitary/Sidechain/test_set_checkpoint_admin.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize("idx", range(1, 6)) 6 | def test_admin_only(root_gauge, accounts, idx): 7 | with brownie.reverts("dev: admin only"): 8 | root_gauge.set_checkpoint_admin(accounts[idx], {"from": accounts[idx]}) 9 | 10 | 11 | def test_immediate_change_of_admin(alice, bob, root_gauge): 12 | root_gauge.set_checkpoint_admin(bob, {"from": alice}) 13 | 14 | assert root_gauge.checkpoint_admin() == bob 15 | -------------------------------------------------------------------------------- /tests/unitary/Sidechain/test_set_killed.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | import pytest 3 | 4 | 5 | @pytest.mark.parametrize("idx", range(1, 6)) 6 | def test_admin_only(root_gauge, accounts, idx): 7 | with brownie.reverts("dev: admin only"): 8 | root_gauge.set_killed(True, {"from": accounts[idx]}) 9 | 10 | 11 | def test_set_killed_updates_state(alice, root_gauge): 12 | root_gauge.set_killed(True, {"from": alice}) 13 | 14 | assert root_gauge.is_killed() is True 15 | -------------------------------------------------------------------------------- /tests/unitary/VestingEscrow/test_claim.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def initial_funding(vesting, accounts): 8 | recipients = [accounts[1]] + [ZERO_ADDRESS] * 99 9 | vesting.add_tokens(10 ** 21, {"from": accounts[0]}) 10 | vesting.fund(recipients, [10 ** 20] + [0] * 99, {"from": accounts[0]}) 11 | 12 | 13 | def test_claim_full(vesting, coin_a, accounts, chain, end_time): 14 | chain.sleep(end_time - chain.time()) 15 | vesting.claim({"from": accounts[1]}) 16 | 17 | assert coin_a.balanceOf(accounts[1]) == 10 ** 20 18 | 19 | 20 | def test_claim_for_another(vesting, coin_a, accounts, chain, end_time): 21 | chain.sleep(end_time - chain.time()) 22 | vesting.claim(accounts[1], {"from": accounts[2]}) 23 | 24 | assert coin_a.balanceOf(accounts[1]) == 10 ** 20 25 | 26 | 27 | def test_claim_for_self(vesting, coin_a, accounts, chain, end_time): 28 | chain.sleep(end_time - chain.time()) 29 | vesting.claim(accounts[1], {"from": accounts[1]}) 30 | 31 | assert coin_a.balanceOf(accounts[1]) == 10 ** 20 32 | 33 | 34 | def test_claim_before_start(vesting, coin_a, accounts, chain, start_time): 35 | chain.sleep(start_time - chain.time() - 5) 36 | vesting.claim({"from": accounts[1]}) 37 | 38 | assert coin_a.balanceOf(accounts[1]) == 0 39 | 40 | 41 | def test_claim_partial(vesting, coin_a, accounts, chain, start_time, end_time): 42 | chain.sleep(start_time - chain.time() + 31337) 43 | tx = vesting.claim({"from": accounts[1]}) 44 | expected_amount = 10 ** 20 * (tx.timestamp - start_time) // (end_time - start_time) 45 | 46 | assert coin_a.balanceOf(accounts[1]) == expected_amount 47 | assert vesting.total_claimed(accounts[1]) == expected_amount 48 | 49 | 50 | def test_claim_multiple(vesting, coin_a, accounts, chain, start_time, end_time): 51 | chain.sleep(start_time - chain.time() - 1000) 52 | balance = 0 53 | for i in range(11): 54 | chain.sleep((end_time - start_time) // 10) 55 | vesting.claim({"from": accounts[1]}) 56 | new_balance = coin_a.balanceOf(accounts[1]) 57 | assert new_balance > balance 58 | balance = new_balance 59 | 60 | assert coin_a.balanceOf(accounts[1]) == 10 ** 20 61 | -------------------------------------------------------------------------------- /tests/unitary/VestingEscrow/test_disable.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_toggle_admin_only(vesting, accounts): 5 | with brownie.reverts("dev: admin only"): 6 | vesting.toggle_disable(accounts[2], {"from": accounts[1]}) 7 | 8 | 9 | def test_disable_can_disable_admin_only(vesting, accounts): 10 | with brownie.reverts("dev: admin only"): 11 | vesting.disable_can_disable({"from": accounts[1]}) 12 | 13 | 14 | def test_disabled_at_is_initially_zero(vesting, accounts): 15 | assert vesting.disabled_at(accounts[1]) == 0 16 | 17 | 18 | def test_disable(vesting, accounts): 19 | tx = vesting.toggle_disable(accounts[1], {"from": accounts[0]}) 20 | 21 | assert vesting.disabled_at(accounts[1]) == tx.timestamp 22 | 23 | 24 | def test_disable_reenable(vesting, accounts): 25 | vesting.toggle_disable(accounts[1], {"from": accounts[0]}) 26 | vesting.toggle_disable(accounts[1], {"from": accounts[0]}) 27 | 28 | assert vesting.disabled_at(accounts[1]) == 0 29 | 30 | 31 | def test_disable_can_disable(vesting, accounts): 32 | vesting.disable_can_disable({"from": accounts[0]}) 33 | assert vesting.can_disable() is False 34 | 35 | with brownie.reverts("Cannot disable"): 36 | vesting.toggle_disable(accounts[1], {"from": accounts[0]}) 37 | 38 | 39 | def test_disable_can_disable_cannot_reenable(vesting, accounts): 40 | vesting.disable_can_disable({"from": accounts[0]}) 41 | vesting.disable_can_disable({"from": accounts[0]}) 42 | assert vesting.can_disable() is False 43 | -------------------------------------------------------------------------------- /tests/unitary/VestingEscrow/test_getters.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | amounts = [10 ** 17 * i for i in range(1, 101)] 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def initial_funding(vesting, accounts): 8 | vesting.add_tokens(10 ** 21, {"from": accounts[0]}) 9 | vesting.fund(accounts[:100], amounts, {"from": accounts[0]}) 10 | 11 | 12 | def test_vested_supply(chain, vesting, end_time): 13 | assert vesting.vestedSupply() == 0 14 | chain.sleep(end_time - chain.time()) 15 | chain.mine() 16 | assert vesting.vestedSupply() == sum(amounts) 17 | 18 | 19 | def test_locked_supply(chain, vesting, end_time): 20 | assert vesting.lockedSupply() == sum(amounts) 21 | chain.sleep(end_time - chain.time()) 22 | chain.mine() 23 | assert vesting.lockedSupply() == 0 24 | 25 | 26 | def test_vested_of(chain, vesting, accounts, end_time): 27 | assert vesting.vestedOf(accounts[0]) == 0 28 | chain.sleep(end_time - chain.time()) 29 | chain.mine() 30 | assert vesting.vestedOf(accounts[0]) == amounts[0] 31 | 32 | 33 | def test_locked_of(chain, vesting, accounts, end_time): 34 | assert vesting.lockedOf(accounts[0]) == amounts[0] 35 | chain.sleep(end_time - chain.time()) 36 | chain.mine() 37 | assert vesting.lockedOf(accounts[0]) == 0 38 | 39 | 40 | def test_balance_of(chain, vesting, accounts, end_time): 41 | assert vesting.balanceOf(accounts[0]) == 0 42 | chain.sleep(end_time - chain.time()) 43 | chain.mine() 44 | assert vesting.balanceOf(accounts[0]) == amounts[0] 45 | vesting.claim({"from": accounts[0]}) 46 | assert vesting.balanceOf(accounts[0]) == 0 47 | -------------------------------------------------------------------------------- /tests/unitary/VestingEscrow/test_vesting_escrow_admin.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_commit_admin_only(vesting, accounts): 5 | with brownie.reverts("dev: admin only"): 6 | vesting.commit_transfer_ownership(accounts[1], {"from": accounts[1]}) 7 | 8 | 9 | def test_apply_admin_only(vesting, accounts): 10 | with brownie.reverts("dev: admin only"): 11 | vesting.apply_transfer_ownership({"from": accounts[1]}) 12 | 13 | 14 | def test_commit_transfer_ownership(vesting, accounts): 15 | vesting.commit_transfer_ownership(accounts[1], {"from": accounts[0]}) 16 | 17 | assert vesting.admin() == accounts[0] 18 | assert vesting.future_admin() == accounts[1] 19 | 20 | 21 | def test_apply_transfer_ownership(vesting, accounts): 22 | vesting.commit_transfer_ownership(accounts[1], {"from": accounts[0]}) 23 | vesting.apply_transfer_ownership({"from": accounts[0]}) 24 | 25 | assert vesting.admin() == accounts[1] 26 | 27 | 28 | def test_apply_without_commit(vesting, accounts): 29 | with brownie.reverts("dev: admin not set"): 30 | vesting.apply_transfer_ownership({"from": accounts[0]}) 31 | -------------------------------------------------------------------------------- /tests/unitary/VestingEscrowFactory/test_admin_factory.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_commit_admin_only(vesting_factory, accounts): 5 | with brownie.reverts("dev: admin only"): 6 | vesting_factory.commit_transfer_ownership(accounts[1], {"from": accounts[1]}) 7 | 8 | 9 | def test_apply_admin_only(vesting_factory, accounts): 10 | with brownie.reverts("dev: admin only"): 11 | vesting_factory.apply_transfer_ownership({"from": accounts[1]}) 12 | 13 | 14 | def test_commit_transfer_ownership(vesting_factory, accounts): 15 | vesting_factory.commit_transfer_ownership(accounts[1], {"from": accounts[0]}) 16 | 17 | assert vesting_factory.admin() == accounts[0] 18 | assert vesting_factory.future_admin() == accounts[1] 19 | 20 | 21 | def test_apply_transfer_ownership(vesting_factory, accounts): 22 | vesting_factory.commit_transfer_ownership(accounts[1], {"from": accounts[0]}) 23 | vesting_factory.apply_transfer_ownership({"from": accounts[0]}) 24 | 25 | assert vesting_factory.admin() == accounts[1] 26 | 27 | 28 | def test_apply_without_commit(vesting_factory, accounts): 29 | with brownie.reverts("dev: admin not set"): 30 | vesting_factory.apply_transfer_ownership({"from": accounts[0]}) 31 | -------------------------------------------------------------------------------- /tests/unitary/VestingEscrowFactory/test_admin_simple.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_commit_admin_only(vesting_simple, accounts): 5 | with brownie.reverts(): 6 | vesting_simple.commit_transfer_ownership(accounts[1], {"from": accounts[1]}) 7 | 8 | 9 | def test_apply_admin_only(vesting_simple, accounts): 10 | with brownie.reverts(): 11 | vesting_simple.apply_transfer_ownership({"from": accounts[1]}) 12 | 13 | 14 | def test_commit_transfer_ownership(vesting_simple, accounts): 15 | vesting_simple.commit_transfer_ownership(accounts[1], {"from": accounts[0]}) 16 | 17 | assert vesting_simple.admin() == accounts[0] 18 | assert vesting_simple.future_admin() == accounts[1] 19 | 20 | 21 | def test_apply_transfer_ownership(vesting_simple, accounts): 22 | vesting_simple.commit_transfer_ownership(accounts[1], {"from": accounts[0]}) 23 | vesting_simple.apply_transfer_ownership({"from": accounts[0]}) 24 | 25 | assert vesting_simple.admin() == accounts[1] 26 | 27 | 28 | def test_apply_without_commit(vesting_simple, accounts): 29 | with brownie.reverts(): 30 | vesting_simple.apply_transfer_ownership({"from": accounts[0]}) 31 | -------------------------------------------------------------------------------- /tests/unitary/VestingEscrowFactory/test_claim_simple.py: -------------------------------------------------------------------------------- 1 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 2 | 3 | 4 | def test_claim_full(vesting_simple, coin_a, accounts, chain, end_time): 5 | chain.sleep(end_time - chain.time()) 6 | vesting_simple.claim({"from": accounts[1]}) 7 | 8 | assert coin_a.balanceOf(accounts[1]) == 10 ** 20 9 | 10 | 11 | def test_claim_for_another(vesting_simple, coin_a, accounts, chain, end_time): 12 | chain.sleep(end_time - chain.time()) 13 | vesting_simple.claim(accounts[1], {"from": accounts[2]}) 14 | 15 | assert coin_a.balanceOf(accounts[1]) == 10 ** 20 16 | 17 | 18 | def test_claim_for_self(vesting_simple, coin_a, accounts, chain, end_time): 19 | chain.sleep(end_time - chain.time()) 20 | vesting_simple.claim(accounts[1], {"from": accounts[1]}) 21 | 22 | assert coin_a.balanceOf(accounts[1]) == 10 ** 20 23 | 24 | 25 | def test_claim_before_start(vesting_simple, coin_a, accounts, chain, start_time): 26 | chain.sleep(start_time - chain.time() - 5) 27 | vesting_simple.claim({"from": accounts[1]}) 28 | 29 | assert coin_a.balanceOf(accounts[1]) == 0 30 | 31 | 32 | def test_claim_partial(vesting_simple, coin_a, accounts, chain, start_time, end_time): 33 | chain.sleep(start_time - chain.time() + 31337) 34 | tx = vesting_simple.claim({"from": accounts[1]}) 35 | expected_amount = 10 ** 20 * (tx.timestamp - start_time) // (end_time - start_time) 36 | 37 | assert coin_a.balanceOf(accounts[1]) == expected_amount 38 | assert vesting_simple.total_claimed(accounts[1]) == expected_amount 39 | 40 | 41 | def test_claim_multiple(vesting_simple, coin_a, accounts, chain, start_time, end_time): 42 | chain.sleep(start_time - chain.time() - 1000) 43 | balance = 0 44 | for i in range(11): 45 | chain.sleep((end_time - start_time) // 10) 46 | vesting_simple.claim({"from": accounts[1]}) 47 | new_balance = coin_a.balanceOf(accounts[1]) 48 | assert new_balance > balance 49 | balance = new_balance 50 | 51 | assert coin_a.balanceOf(accounts[1]) == 10 ** 20 52 | -------------------------------------------------------------------------------- /tests/unitary/VestingEscrowFactory/test_disable_and_claim_simple.py: -------------------------------------------------------------------------------- 1 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 2 | 3 | 4 | def test_disable_after_end_time(vesting_simple, coin_a, accounts, chain, end_time): 5 | chain.sleep(end_time - chain.time()) 6 | vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 7 | vesting_simple.claim({"from": accounts[1]}) 8 | 9 | assert coin_a.balanceOf(accounts[1]) == 10 ** 20 10 | 11 | 12 | def test_disable_before_start_time(vesting_simple, coin_a, accounts, chain, end_time): 13 | vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 14 | chain.sleep(end_time - chain.time()) 15 | vesting_simple.claim({"from": accounts[1]}) 16 | 17 | assert coin_a.balanceOf(accounts[1]) == 0 18 | 19 | 20 | def test_disable_before_start_time_and_reenable(vesting_simple, coin_a, accounts, chain, end_time): 21 | vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 22 | chain.sleep(end_time - chain.time()) 23 | vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 24 | vesting_simple.claim({"from": accounts[1]}) 25 | 26 | assert coin_a.balanceOf(accounts[1]) == 10 ** 20 27 | 28 | 29 | def test_disable_partially_unvested(vesting_simple, coin_a, accounts, chain, start_time, end_time): 30 | chain.sleep(start_time - chain.time() + 31337) 31 | tx = vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 32 | chain.sleep(end_time - chain.time()) 33 | vesting_simple.claim({"from": accounts[1]}) 34 | 35 | expected_amount = 10 ** 20 * (tx.timestamp - start_time) // (end_time - start_time) 36 | assert coin_a.balanceOf(accounts[1]) == expected_amount 37 | assert vesting_simple.total_claimed(accounts[1]) == expected_amount 38 | 39 | 40 | def test_disable_multiple_partial(vesting_simple, coin_a, accounts, chain, start_time, end_time): 41 | chain.sleep(start_time - chain.time() + 31337) 42 | vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 43 | chain.sleep(31337) 44 | vesting_simple.claim({"from": accounts[1]}) 45 | vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 46 | tx = vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 47 | chain.sleep(end_time - chain.time()) 48 | vesting_simple.claim({"from": accounts[1]}) 49 | 50 | expected_amount = 10 ** 20 * (tx.timestamp - start_time) // (end_time - start_time) 51 | assert coin_a.balanceOf(accounts[1]) == expected_amount 52 | assert vesting_simple.total_claimed(accounts[1]) == expected_amount 53 | -------------------------------------------------------------------------------- /tests/unitary/VestingEscrowFactory/test_disable_simple.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_toggle_admin_only(vesting_simple, accounts): 5 | with brownie.reverts(): 6 | vesting_simple.toggle_disable(accounts[2], {"from": accounts[1]}) 7 | 8 | 9 | def test_disable_can_disable_admin_only(vesting_simple, accounts): 10 | with brownie.reverts(): 11 | vesting_simple.disable_can_disable({"from": accounts[1]}) 12 | 13 | 14 | def test_disabled_at_is_initially_zero(vesting_simple, accounts): 15 | assert vesting_simple.disabled_at(accounts[1]) == 0 16 | 17 | 18 | def test_disable(vesting_simple, accounts): 19 | tx = vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 20 | 21 | assert vesting_simple.disabled_at(accounts[1]) == tx.timestamp 22 | 23 | 24 | def test_disable_reenable(vesting_simple, accounts): 25 | vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 26 | vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 27 | 28 | assert vesting_simple.disabled_at(accounts[1]) == 0 29 | 30 | 31 | def test_disable_can_disable(vesting_simple, accounts): 32 | vesting_simple.disable_can_disable({"from": accounts[0]}) 33 | assert vesting_simple.can_disable() is False 34 | 35 | with brownie.reverts(): 36 | vesting_simple.toggle_disable(accounts[1], {"from": accounts[0]}) 37 | 38 | 39 | def test_disable_can_disable_cannot_reenable(vesting_simple, accounts): 40 | vesting_simple.disable_can_disable({"from": accounts[0]}) 41 | vesting_simple.disable_can_disable({"from": accounts[0]}) 42 | assert vesting_simple.can_disable() is False 43 | -------------------------------------------------------------------------------- /tests/unitary/VestingEscrowFactory/test_getters_simple.py: -------------------------------------------------------------------------------- 1 | def test_vested_supply(chain, vesting_simple, end_time): 2 | assert vesting_simple.vestedSupply() == 0 3 | chain.sleep(end_time - chain.time()) 4 | chain.mine() 5 | assert vesting_simple.vestedSupply() == 10 ** 20 6 | 7 | 8 | def test_locked_supply(chain, vesting_simple, end_time): 9 | assert vesting_simple.lockedSupply() == 10 ** 20 10 | chain.sleep(end_time - chain.time()) 11 | chain.mine() 12 | assert vesting_simple.lockedSupply() == 0 13 | 14 | 15 | def test_vested_of(chain, vesting_simple, accounts, end_time): 16 | assert vesting_simple.vestedOf(accounts[1]) == 0 17 | chain.sleep(end_time - chain.time()) 18 | chain.mine() 19 | assert vesting_simple.vestedOf(accounts[1]) == 10 ** 20 20 | 21 | 22 | def test_locked_of(chain, vesting_simple, accounts, end_time): 23 | assert vesting_simple.lockedOf(accounts[1]) == 10 ** 20 24 | chain.sleep(end_time - chain.time()) 25 | chain.mine() 26 | assert vesting_simple.lockedOf(accounts[1]) == 0 27 | 28 | 29 | def test_balance_of(chain, vesting_simple, accounts, end_time): 30 | assert vesting_simple.balanceOf(accounts[1]) == 0 31 | chain.sleep(end_time - chain.time()) 32 | chain.mine() 33 | assert vesting_simple.balanceOf(accounts[1]) == 10 ** 20 34 | vesting_simple.claim({"from": accounts[1]}) 35 | assert vesting_simple.balanceOf(accounts[1]) == 0 36 | -------------------------------------------------------------------------------- /tests/unitary/VotingEscrow/test_votingescrow_admin.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_commit_admin_only(voting_escrow, accounts): 5 | with brownie.reverts("dev: admin only"): 6 | voting_escrow.commit_transfer_ownership(accounts[1], {"from": accounts[1]}) 7 | 8 | 9 | def test_apply_admin_only(voting_escrow, accounts): 10 | with brownie.reverts("dev: admin only"): 11 | voting_escrow.apply_transfer_ownership({"from": accounts[1]}) 12 | 13 | 14 | def test_commit_transfer_ownership(voting_escrow, accounts): 15 | voting_escrow.commit_transfer_ownership(accounts[1], {"from": accounts[0]}) 16 | 17 | assert voting_escrow.admin() == accounts[0] 18 | assert voting_escrow.future_admin() == accounts[1] 19 | 20 | 21 | def test_apply_transfer_ownership(voting_escrow, accounts): 22 | voting_escrow.commit_transfer_ownership(accounts[1], {"from": accounts[0]}) 23 | voting_escrow.apply_transfer_ownership({"from": accounts[0]}) 24 | 25 | assert voting_escrow.admin() == accounts[1] 26 | 27 | 28 | def test_apply_without_commit(voting_escrow, accounts): 29 | with brownie.reverts("dev: admin not set"): 30 | voting_escrow.apply_transfer_ownership({"from": accounts[0]}) 31 | -------------------------------------------------------------------------------- /tests/unitary/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-dao-contracts/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/tests/unitary/__init__.py --------------------------------------------------------------------------------