├── .gitattributes ├── .github └── workflows │ ├── aave.yaml │ ├── lint.yaml │ └── ren.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── brownie-config.yaml ├── contracts ├── CurveTokenV3.vy ├── bridge │ ├── ChildBurner.vy │ └── RootForwarder.vy ├── burners │ ├── ABurner.vy │ └── BTCBurner.vy ├── pools │ ├── aave │ │ ├── StableSwapAave.vy │ │ └── pooldata.json │ └── ren │ │ ├── README.md │ │ ├── StableSwapREN.vy │ │ └── pooldata.json └── registry │ ├── README.md │ ├── Registry.vy │ └── Swaps.vy ├── interfaces ├── AToken.json ├── AaveLendingPool.json ├── BridgeToken.json ├── ChildERC20.json ├── ProxyAdmin.json ├── RootChain.json ├── RootChainManager.json ├── WMatic.json └── renERC20.json ├── network-config.yaml ├── package.json ├── pyproject.toml ├── requirements.in ├── requirements.txt ├── scripts ├── burn_fees.py ├── exit.py └── fetch_deployment_data.py ├── setup.cfg └── tests ├── conftest.py ├── fixtures ├── __init__.py ├── accounts.py ├── coins.py ├── deployments.py ├── functions.py ├── pooldata.py └── setup.py └── forked ├── test_reverts.py └── test_success.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.vy linguist-language=Python 3 | -------------------------------------------------------------------------------- /.github/workflows/aave.yaml: -------------------------------------------------------------------------------- 1 | name: aave 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "tests/**/*.py" 7 | - "contracts/pools/aave/**.vy" 8 | push: 9 | paths: 10 | - "tests/**/*.py" 11 | - "contracts/pools/aave/**.vy" 12 | 13 | env: 14 | pool: "aave" 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | NODE_OPTIONS: --max_old_space_size=4096 17 | 18 | jobs: 19 | test: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Cache Compiler Installations 26 | uses: actions/cache@v2 27 | with: 28 | path: | 29 | ~/.solcx 30 | ~/.vvm 31 | key: compiler-cache 32 | 33 | - name: Setup Node.js 34 | uses: actions/setup-node@v1 35 | 36 | - name: Install Ganache 37 | run: npm install 38 | 39 | - name: Setup Python 3.8 40 | uses: actions/setup-python@v2 41 | with: 42 | python-version: 3.8 43 | 44 | - name: Install Requirements 45 | run: | 46 | pip install wheel 47 | pip install -r requirements.txt 48 | brownie networks import network-config.yaml True 49 | 50 | - name: Run Tests 51 | run: brownie test tests/forked/ --network polygon-main-fork --pool aave 52 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | on: ["push", "pull_request"] 2 | 3 | name: linting 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Setup Python 3.8 13 | uses: actions/setup-python@v2 14 | with: 15 | python-version: 3.8 16 | 17 | - name: Install Requirements 18 | run: pip install -r requirements.txt 19 | 20 | - name: Run black 21 | run: black --check scripts tests 22 | 23 | - name: Run flake8 24 | run: flake8 scripts tests 25 | 26 | - name: Run isort 27 | run: isort --check-only --diff --recursive scripts tests 28 | -------------------------------------------------------------------------------- /.github/workflows/ren.yaml: -------------------------------------------------------------------------------- 1 | name: ren 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "tests/**/*.py" 7 | - "contracts/pools/ren/**.vy" 8 | push: 9 | paths: 10 | - "tests/**/*.py" 11 | - "contracts/pools/ren/**.vy" 12 | 13 | env: 14 | pool: "ren" 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | NODE_OPTIONS: --max_old_space_size=4096 17 | 18 | jobs: 19 | test: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Cache Compiler Installations 26 | uses: actions/cache@v2 27 | with: 28 | path: | 29 | ~/.solcx 30 | ~/.vvm 31 | key: compiler-cache 32 | 33 | - name: Setup Node.js 34 | uses: actions/setup-node@v1 35 | 36 | - name: Install Ganache 37 | run: npm install 38 | 39 | - name: Setup Python 3.8 40 | uses: actions/setup-python@v2 41 | with: 42 | python-version: 3.8 43 | 44 | - name: Install Requirements 45 | run: | 46 | pip install wheel 47 | pip install -r requirements.txt 48 | brownie networks import network-config.yaml True 49 | 50 | - name: Run Tests 51 | run: brownie test tests/forked/ --network polygon-main-fork --pool ren 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .history 3 | .hypothesis/ 4 | build/ 5 | reports/ 6 | venv/ 7 | .venv/ 8 | .idea 9 | *-*.json -------------------------------------------------------------------------------- /.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 | (c) Curve.Fi, 2021 2 | 3 | Except as otherwise provided in the header of a specific source code 4 | file in this repository: (a) all intellectual property (including all 5 | source code, designs and protocols) contained in this repository has been 6 | published for informational purposes only; (b) no license, right of 7 | reproduction or distribution or other right with respect thereto is 8 | granted or implied; and (c) all moral, intellectual property and other 9 | rights are hereby reserved by the copyright holder. 10 | 11 | THE SOFTWARE AND INTELLECTUAL PROPERTY INCLUDED IN THIS REPOSITORY 12 | IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS," AND ANY EXPRESS 13 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR 16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE OR 18 | INTELLECTUAL PROPERTY (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 19 | OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 20 | BUSINESS INTERRUPTION), HOWEVER CAUSED OR CLAIMED (WHETHER IN CONTRACT, 21 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)), EVEN IF 22 | SUCH DAMAGES WERE REASONABLY FORESEEABLE OR THE COPYRIGHT HOLDERS WERE 23 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # curve-contract-polygon 2 | Curve.fi exchange implementation for Polygon. 3 | 4 | ## Testing and Development 5 | 6 | ### Dependencies 7 | 8 | * [python3](https://www.python.org/downloads/release/python-368/) from version 3.6 to 3.8, python3-dev 9 | * [brownie](https://github.com/iamdefinitelyahuman/brownie) - tested with version [1.14.5](https://github.com/eth-brownie/brownie/releases/tag/v1.14.5) 10 | * [ganache-cli](https://github.com/trufflesuite/ganache-cli) - tested with version [6.12.1](https://github.com/trufflesuite/ganache-cli/releases/tag/v6.12.1) 11 | 12 | ## Setup 13 | 14 | 1. To get started, first create and initialize a Python [virtual environment](https://docs.python.org/3/library/venv.html). 15 | 16 | 2. clone the repo and install the developer dependencies: 17 | 18 | ```bash 19 | git clone https://github.com/curvefi/curve-contract-polygon.git 20 | cd curve-contract-polygon 21 | pip install -r requirements.txt 22 | ``` 23 | 24 | 3. Add Polygon to your local brownie networks: 25 | 26 | ```bash 27 | brownie networks import network-config.yaml 28 | ``` 29 | 30 | ### Running the Tests 31 | 32 | Testing is done against a forked mainnet. To run the entire suite: 33 | 34 | ```bash 35 | brownie test 36 | ``` 37 | 38 | To run tests on a specific pool: 39 | 40 | ```bash 41 | brownie test --pool 42 | ``` 43 | 44 | Valid pool names are the names of the subdirectories within [`contracts/pools`](contracts/pools). 45 | 46 | You can optionally include the `--coverage` flag to view a coverage report upon completion of the tests. 47 | 48 | ## License 49 | 50 | (c) Curve.Fi, 2021 - [All rights reserved](LICENSE). 51 | -------------------------------------------------------------------------------- /brownie-config.yaml: -------------------------------------------------------------------------------- 1 | networks: 2 | default: polygon-main-fork 3 | 4 | autofetch_sources: true 5 | -------------------------------------------------------------------------------- /contracts/CurveTokenV3.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.2.0 2 | """ 3 | @title Curve LP Token 4 | @author Curve.Fi 5 | @notice Base implementation for an LP token provided for 6 | supplying liquidity to `StableSwap` 7 | @dev Follows the ERC-20 token standard as defined at 8 | https://eips.ethereum.org/EIPS/eip-20 9 | """ 10 | 11 | from vyper.interfaces import ERC20 12 | 13 | implements: ERC20 14 | 15 | interface Curve: 16 | def owner() -> address: view 17 | 18 | 19 | event Transfer: 20 | _from: indexed(address) 21 | _to: indexed(address) 22 | _value: uint256 23 | 24 | event Approval: 25 | _owner: indexed(address) 26 | _spender: indexed(address) 27 | _value: uint256 28 | 29 | 30 | name: public(String[64]) 31 | symbol: public(String[32]) 32 | 33 | balanceOf: public(HashMap[address, uint256]) 34 | allowance: public(HashMap[address, HashMap[address, uint256]]) 35 | totalSupply: public(uint256) 36 | 37 | minter: public(address) 38 | 39 | 40 | @external 41 | def __init__(_name: String[64], _symbol: String[32]): 42 | self.name = _name 43 | self.symbol = _symbol 44 | self.minter = msg.sender 45 | log Transfer(ZERO_ADDRESS, msg.sender, 0) 46 | 47 | 48 | @view 49 | @external 50 | def decimals() -> uint256: 51 | """ 52 | @notice Get the number of decimals for this token 53 | @dev Implemented as a view method to reduce gas costs 54 | @return uint256 decimal places 55 | """ 56 | return 18 57 | 58 | 59 | @external 60 | def transfer(_to : address, _value : uint256) -> bool: 61 | """ 62 | @dev Transfer token for a specified address 63 | @param _to The address to transfer to. 64 | @param _value The amount to be transferred. 65 | """ 66 | # NOTE: vyper does not allow underflows 67 | # so the following subtraction would revert on insufficient balance 68 | self.balanceOf[msg.sender] -= _value 69 | self.balanceOf[_to] += _value 70 | 71 | log Transfer(msg.sender, _to, _value) 72 | return True 73 | 74 | 75 | @external 76 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 77 | """ 78 | @dev Transfer tokens from one address to another. 79 | @param _from address The address which you want to send tokens from 80 | @param _to address The address which you want to transfer to 81 | @param _value uint256 the amount of tokens to be transferred 82 | """ 83 | self.balanceOf[_from] -= _value 84 | self.balanceOf[_to] += _value 85 | 86 | _allowance: uint256 = self.allowance[_from][msg.sender] 87 | if _allowance != MAX_UINT256: 88 | self.allowance[_from][msg.sender] = _allowance - _value 89 | 90 | log Transfer(_from, _to, _value) 91 | return True 92 | 93 | 94 | @external 95 | def approve(_spender : address, _value : uint256) -> bool: 96 | """ 97 | @notice Approve the passed address to transfer the specified amount of 98 | tokens on behalf of msg.sender 99 | @dev Beware that changing an allowance via this method brings the risk 100 | that someone may use both the old and new allowance by unfortunate 101 | transaction ordering. This may be mitigated with the use of 102 | {increaseAllowance} and {decreaseAllowance}. 103 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 104 | @param _spender The address which will transfer the funds 105 | @param _value The amount of tokens that may be transferred 106 | @return bool success 107 | """ 108 | self.allowance[msg.sender][_spender] = _value 109 | 110 | log Approval(msg.sender, _spender, _value) 111 | return True 112 | 113 | 114 | @external 115 | def increaseAllowance(_spender: address, _added_value: uint256) -> bool: 116 | """ 117 | @notice Increase the allowance granted to `_spender` by the caller 118 | @dev This is alternative to {approve} that can be used as a mitigation for 119 | the potential race condition 120 | @param _spender The address which will transfer the funds 121 | @param _added_value The amount of to increase the allowance 122 | @return bool success 123 | """ 124 | allowance: uint256 = self.allowance[msg.sender][_spender] + _added_value 125 | self.allowance[msg.sender][_spender] = allowance 126 | 127 | log Approval(msg.sender, _spender, allowance) 128 | return True 129 | 130 | 131 | @external 132 | def decreaseAllowance(_spender: address, _subtracted_value: uint256) -> bool: 133 | """ 134 | @notice Decrease the allowance granted to `_spender` by the caller 135 | @dev This is alternative to {approve} that can be used as a mitigation for 136 | the potential race condition 137 | @param _spender The address which will transfer the funds 138 | @param _subtracted_value The amount of to decrease the allowance 139 | @return bool success 140 | """ 141 | allowance: uint256 = self.allowance[msg.sender][_spender] - _subtracted_value 142 | self.allowance[msg.sender][_spender] = allowance 143 | 144 | log Approval(msg.sender, _spender, allowance) 145 | return True 146 | 147 | 148 | @external 149 | def mint(_to: address, _value: uint256) -> bool: 150 | """ 151 | @dev Mint an amount of the token and assigns it to an account. 152 | This encapsulates the modification of balances such that the 153 | proper events are emitted. 154 | @param _to The account that will receive the created tokens. 155 | @param _value The amount that will be created. 156 | """ 157 | assert msg.sender == self.minter 158 | 159 | self.totalSupply += _value 160 | self.balanceOf[_to] += _value 161 | 162 | log Transfer(ZERO_ADDRESS, _to, _value) 163 | return True 164 | 165 | 166 | @external 167 | def burnFrom(_to: address, _value: uint256) -> bool: 168 | """ 169 | @dev Burn an amount of the token from a given account. 170 | @param _to The account whose tokens will be burned. 171 | @param _value The amount that will be burned. 172 | """ 173 | assert msg.sender == self.minter 174 | 175 | self.totalSupply -= _value 176 | self.balanceOf[_to] -= _value 177 | 178 | log Transfer(_to, ZERO_ADDRESS, _value) 179 | return True 180 | 181 | 182 | @external 183 | def set_minter(_minter: address): 184 | assert msg.sender == self.minter 185 | self.minter = _minter 186 | 187 | 188 | @external 189 | def set_name(_name: String[64], _symbol: String[32]): 190 | assert Curve(self.minter).owner() == msg.sender 191 | self.name = _name 192 | self.symbol = _symbol 193 | -------------------------------------------------------------------------------- /contracts/bridge/ChildBurner.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.12 2 | 3 | interface BridgeToken: 4 | def withdraw(_amount: uint256): nonpayable 5 | def balanceOf(_user: address) -> uint256: view 6 | 7 | 8 | owner: public(address) 9 | future_owner: public(address) 10 | 11 | 12 | @external 13 | def __init__(_owner: address): 14 | self.owner = _owner 15 | 16 | 17 | @external 18 | def withdraw(_token: address): 19 | assert msg.sender == self.owner 20 | amount: uint256 = BridgeToken(_token).balanceOf(self) 21 | BridgeToken(_token).withdraw(amount) 22 | 23 | 24 | @external 25 | def commit_transfer_ownership(_owner: address): 26 | assert msg.sender == self.owner 27 | self.future_owner = _owner 28 | 29 | 30 | @external 31 | def accept_transfer_ownership(): 32 | assert msg.sender == self.future_owner 33 | self.owner = self.future_owner 34 | -------------------------------------------------------------------------------- /contracts/bridge/RootForwarder.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.12 2 | 3 | from vyper.interfaces import ERC20 4 | 5 | 6 | owner: public(address) 7 | future_owner: public(address) 8 | 9 | pool_proxy: public(address) 10 | 11 | 12 | @external 13 | def __init__(_owner: address, _pool_proxy: address): 14 | self.owner = _owner 15 | self.pool_proxy = _pool_proxy 16 | 17 | 18 | @external 19 | def transfer(_token: address) -> bool: 20 | # transfer underlying coin from msg.sender to self 21 | amount: uint256 = ERC20(_token).balanceOf(self) 22 | response: Bytes[32] = raw_call( 23 | _token, 24 | concat( 25 | method_id("transfer(address,uint256)"), 26 | convert(self.pool_proxy, bytes32), 27 | convert(amount, bytes32), 28 | ), 29 | max_outsize=32 30 | ) 31 | if len(response) != 0: 32 | assert convert(response, bool) 33 | 34 | return True 35 | 36 | 37 | @external 38 | def transfer_many(_tokens: address[10]) -> bool: 39 | pool_proxy: address = self.pool_proxy 40 | for token in _tokens: 41 | if token == ZERO_ADDRESS: 42 | break 43 | 44 | # transfer underlying coin from msg.sender to self 45 | amount: uint256 = ERC20(token).balanceOf(self) 46 | response: Bytes[32] = raw_call( 47 | token, 48 | concat( 49 | method_id("transfer(address,uint256)"), 50 | convert(pool_proxy, bytes32), 51 | convert(amount, bytes32), 52 | ), 53 | max_outsize=32 54 | ) 55 | if len(response) != 0: 56 | assert convert(response, bool) 57 | 58 | return True 59 | 60 | 61 | @external 62 | def set_pool_proxy(_pool_proxy: address): 63 | assert msg.sender == self.owner 64 | self.pool_proxy = _pool_proxy 65 | 66 | 67 | @external 68 | def commit_transfer_ownership(_owner: address): 69 | assert msg.sender == self.owner 70 | self.future_owner = _owner 71 | 72 | 73 | @external 74 | def accept_transfer_ownership(): 75 | assert msg.sender == self.future_owner 76 | self.owner = self.future_owner 77 | -------------------------------------------------------------------------------- /contracts/burners/ABurner.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.12 2 | """ 3 | @title amToken Burner 4 | @notice Converts amToken lending coins to USDC and transfers to `ChildBurner` 5 | """ 6 | 7 | from vyper.interfaces import ERC20 8 | 9 | 10 | interface LendingPool: 11 | def withdraw(_underlying_asset: address, _amount: uint256, _receiver: address): nonpayable 12 | 13 | interface aToken: 14 | def UNDERLYING_ASSET_ADDRESS() -> address: view 15 | 16 | interface RegistrySwap: 17 | def exchange_with_best_rate( 18 | _from: address, 19 | _to: address, 20 | _amount: uint256, 21 | _expected: uint256, 22 | _receiver: address, 23 | ) -> uint256: payable 24 | 25 | interface AddressProvider: 26 | def get_address(_id: uint256) -> address: view 27 | 28 | 29 | receiver: public(address) 30 | is_killed: public(bool) 31 | 32 | owner: public(address) 33 | future_owner: public(address) 34 | 35 | is_approved: HashMap[address, HashMap[address, bool]] 36 | 37 | ADDRESS_PROVIDER: constant(address) = 0x0000000022D53366457F9d5E68Ec105046FC4383 38 | AAVE_LENDING_POOL: constant(address) = 0x8dFf5E27EA6b7AC08EbFdf9eB090F32ee9a30fcf 39 | USDC: constant(address) = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 40 | 41 | 42 | @external 43 | def __init__(_receiver: address, _owner: address): 44 | """ 45 | @notice Contract constructor 46 | @param _receiver Address that converted tokens are transferred to. 47 | Should be set to the `ChildBurner` deployment. 48 | @param _owner Owner address. Can kill the contract and recover tokens. 49 | """ 50 | self.receiver = _receiver 51 | self.owner = _owner 52 | 53 | 54 | @external 55 | def burn(_coin: address) -> bool: 56 | """ 57 | @notice Unwrap `_coin` and transfer to the receiver 58 | @param _coin Address of the coin being unwrapped 59 | @return bool success 60 | """ 61 | assert not self.is_killed # dev: is killed 62 | 63 | # transfer coins from caller 64 | amount: uint256 = ERC20(_coin).balanceOf(msg.sender) 65 | ERC20(_coin).transferFrom(msg.sender, self, amount) 66 | 67 | # get actual balance in case of transfer fee or pre-existing balance 68 | amount = ERC20(_coin).balanceOf(self) 69 | 70 | # unwrap aTokens for underlying asset and transfer to receiver 71 | underlying: address = aToken(_coin).UNDERLYING_ASSET_ADDRESS() 72 | if underlying == USDC: 73 | LendingPool(AAVE_LENDING_POOL).withdraw(underlying, amount, self.receiver) 74 | else: 75 | registry_swap: address = AddressProvider(ADDRESS_PROVIDER).get_address(2) 76 | LendingPool(AAVE_LENDING_POOL).withdraw(underlying, amount, self) 77 | if not self.is_approved[registry_swap][underlying]: 78 | response: Bytes[32] = raw_call( 79 | underlying, 80 | concat( 81 | method_id("approve(address,uint256)"), 82 | convert(registry_swap, bytes32), 83 | convert(MAX_UINT256, bytes32), 84 | ), 85 | max_outsize=32, 86 | ) 87 | if len(response) != 0: 88 | assert convert(response, bool) 89 | self.is_approved[registry_swap][underlying] = True 90 | 91 | # get actual balance in case of transfer fee or pre-existing balance 92 | amount = ERC20(underlying).balanceOf(self) 93 | RegistrySwap(registry_swap).exchange_with_best_rate(underlying, USDC, amount, 0, self.receiver) 94 | 95 | return True 96 | 97 | 98 | @external 99 | def recover_balance(_coin: address) -> bool: 100 | """ 101 | @notice Recover ERC20 tokens from this contract 102 | @param _coin Token address 103 | @return bool success 104 | """ 105 | assert msg.sender == self.owner # dev: only owner 106 | 107 | amount: uint256 = ERC20(_coin).balanceOf(self) 108 | response: Bytes[32] = raw_call( 109 | _coin, 110 | concat( 111 | method_id("transfer(address,uint256)"), 112 | convert(msg.sender, bytes32), 113 | convert(amount, bytes32), 114 | ), 115 | max_outsize=32, 116 | ) 117 | if len(response) != 0: 118 | assert convert(response, bool) 119 | 120 | return True 121 | 122 | 123 | @external 124 | def set_killed(_is_killed: bool) -> bool: 125 | """ 126 | @notice Set killed status for this contract 127 | @dev When killed, the `burn` function cannot be called 128 | @param _is_killed Killed status 129 | @return bool success 130 | """ 131 | assert msg.sender == self.owner # dev: only owner 132 | self.is_killed = _is_killed 133 | 134 | return True 135 | 136 | 137 | @external 138 | def commit_transfer_ownership(_future_owner: address) -> bool: 139 | """ 140 | @notice Commit a transfer of ownership 141 | @dev Must be accepted by the new owner via `accept_transfer_ownership` 142 | @param _future_owner New owner address 143 | @return bool success 144 | """ 145 | assert msg.sender == self.owner # dev: only owner 146 | self.future_owner = _future_owner 147 | 148 | return True 149 | 150 | 151 | @external 152 | def accept_transfer_ownership() -> bool: 153 | """ 154 | @notice Accept a transfer of ownership 155 | @return bool success 156 | """ 157 | assert msg.sender == self.future_owner # dev: only owner 158 | self.owner = msg.sender 159 | 160 | return True 161 | -------------------------------------------------------------------------------- /contracts/burners/BTCBurner.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.15 2 | """ 3 | @title BTC Burner 4 | @notice Converts BTC lending coins to USDC and transfers to `ChildBurner` 5 | """ 6 | 7 | from vyper.interfaces import ERC20 8 | 9 | 10 | interface RegistrySwap: 11 | def exchange_with_best_rate( 12 | _from: address, 13 | _to: address, 14 | _amount: uint256, 15 | _expected: uint256 16 | ) -> uint256: payable 17 | 18 | interface CryptoSwap: 19 | def exchange(i: uint256, j: uint256, dx: uint256, min_dy: uint256): nonpayable 20 | 21 | interface StableSwap: 22 | def remove_liquidity_one_coin( 23 | _token_amount: uint256, 24 | i: int128, 25 | _min_amount: uint256, 26 | _use_underlying: bool, 27 | ) -> uint256: nonpayable 28 | 29 | 30 | interface AddressProvider: 31 | def get_address(_id: uint256) -> address: view 32 | 33 | 34 | receiver: public(address) 35 | is_killed: public(bool) 36 | 37 | owner: public(address) 38 | future_owner: public(address) 39 | 40 | is_approved: HashMap[address, HashMap[address, bool]] 41 | 42 | ADDRESS_PROVIDER: constant(address) = 0x0000000022D53366457F9d5E68Ec105046FC4383 43 | AMWBTC: constant(address) = 0x5c2ed810328349100A66B82b78a1791B101C9D61 44 | USDC: constant(address) = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 45 | 46 | ATRICRYPTO3: constant(address) = 0x92215849c439E1f8612b6646060B4E3E5ef822cC 47 | AM3CRV: constant(address) = 0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171 48 | 49 | SS_AAVE: constant(address) = 0x445FE580eF8d70FF569aB36e80c647af338db351 50 | 51 | @external 52 | def __init__(_receiver: address, _owner: address): 53 | """ 54 | @notice Contract constructor 55 | @param _receiver Address that converted tokens are transferred to. 56 | Should be set to the `ChildBurner` deployment. 57 | @param _owner Owner address. Can kill the contract and recover tokens. 58 | """ 59 | self.receiver = _receiver 60 | self.owner = _owner 61 | ERC20(AMWBTC).approve(ATRICRYPTO3, MAX_UINT256) 62 | 63 | 64 | @internal 65 | def _approve(_coin: address, _spender: address): 66 | if not self.is_approved[_spender][_coin]: 67 | response: Bytes[32] = raw_call( 68 | _coin, 69 | concat( 70 | method_id("approve(address,uint256)"), 71 | convert(_spender, bytes32), 72 | convert(MAX_UINT256, bytes32), 73 | ), 74 | max_outsize=32, 75 | ) 76 | if len(response) != 0: 77 | assert convert(response, bool) 78 | self.is_approved[_spender][_coin] = True 79 | 80 | 81 | @external 82 | def burn(_coin: address) -> bool: 83 | """ 84 | @notice Unwrap `_coin` and transfer to the receiver 85 | @param _coin Address of the coin being unwrapped 86 | @return bool success 87 | """ 88 | assert not self.is_killed # dev: is killed 89 | 90 | # transfer coins from caller 91 | amount: uint256 = ERC20(_coin).balanceOf(msg.sender) 92 | if amount > 0: 93 | ERC20(_coin).transferFrom(msg.sender, self, amount) 94 | 95 | # get actual balance in case of transfer fee or pre-existing balance 96 | amount = ERC20(_coin).balanceOf(self) 97 | 98 | # swap for amWBTC 99 | if _coin != AMWBTC: 100 | registry_swap: address = AddressProvider(ADDRESS_PROVIDER).get_address(2) 101 | self._approve(_coin, registry_swap) 102 | 103 | RegistrySwap(registry_swap).exchange_with_best_rate(_coin, AMWBTC, amount, 0) 104 | amount = ERC20(AMWBTC).balanceOf(self) 105 | 106 | # amWBTC -> am3CRV 107 | CryptoSwap(ATRICRYPTO3).exchange(1, 0, amount, 0) 108 | 109 | # am3CRV -> USDC 110 | amount = ERC20(AM3CRV).balanceOf(self) 111 | StableSwap(SS_AAVE).remove_liquidity_one_coin(amount, 1, 0, True) 112 | 113 | # transfer USDC to receiver 114 | amount = ERC20(USDC).balanceOf(self) 115 | ERC20(USDC).transfer(self.receiver, amount) 116 | 117 | return True 118 | 119 | 120 | @external 121 | def recover_balance(_coin: address) -> bool: 122 | """ 123 | @notice Recover ERC20 tokens from this contract 124 | @param _coin Token address 125 | @return bool success 126 | """ 127 | assert msg.sender == self.owner # dev: only owner 128 | 129 | amount: uint256 = ERC20(_coin).balanceOf(self) 130 | response: Bytes[32] = raw_call( 131 | _coin, 132 | concat( 133 | method_id("transfer(address,uint256)"), 134 | convert(msg.sender, bytes32), 135 | convert(amount, bytes32), 136 | ), 137 | max_outsize=32, 138 | ) 139 | if len(response) != 0: 140 | assert convert(response, bool) 141 | 142 | return True 143 | 144 | 145 | @external 146 | def set_killed(_is_killed: bool) -> bool: 147 | """ 148 | @notice Set killed status for this contract 149 | @dev When killed, the `burn` function cannot be called 150 | @param _is_killed Killed status 151 | @return bool success 152 | """ 153 | assert msg.sender == self.owner # dev: only owner 154 | self.is_killed = _is_killed 155 | 156 | return True 157 | 158 | 159 | @external 160 | def commit_transfer_ownership(_future_owner: address) -> bool: 161 | """ 162 | @notice Commit a transfer of ownership 163 | @dev Must be accepted by the new owner via `accept_transfer_ownership` 164 | @param _future_owner New owner address 165 | @return bool success 166 | """ 167 | assert msg.sender == self.owner # dev: only owner 168 | self.future_owner = _future_owner 169 | 170 | return True 171 | 172 | 173 | @external 174 | def accept_transfer_ownership() -> bool: 175 | """ 176 | @notice Accept a transfer of ownership 177 | @return bool success 178 | """ 179 | assert msg.sender == self.future_owner # dev: only owner 180 | self.owner = msg.sender 181 | 182 | return True 183 | -------------------------------------------------------------------------------- /contracts/pools/aave/StableSwapAave.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.12 2 | """ 3 | @title Curve aPool for use on Polygon 4 | @author Curve.Fi 5 | @license Copyright (c) Curve.Fi, 2020 - all rights reserved 6 | @notice Pool implementation with aToken-style lending 7 | """ 8 | 9 | from vyper.interfaces import ERC20 10 | 11 | 12 | interface LendingPool: 13 | def withdraw(_underlying_asset: address, _amount: uint256, _receiver: address): nonpayable 14 | 15 | interface CurveToken: 16 | def mint(_to: address, _value: uint256) -> bool: nonpayable 17 | def burnFrom(_to: address, _value: uint256) -> bool: nonpayable 18 | 19 | 20 | # Events 21 | event TokenExchange: 22 | buyer: indexed(address) 23 | sold_id: int128 24 | tokens_sold: uint256 25 | bought_id: int128 26 | tokens_bought: uint256 27 | 28 | event TokenExchangeUnderlying: 29 | buyer: indexed(address) 30 | sold_id: int128 31 | tokens_sold: uint256 32 | bought_id: int128 33 | tokens_bought: uint256 34 | 35 | event AddLiquidity: 36 | provider: indexed(address) 37 | token_amounts: uint256[N_COINS] 38 | fees: uint256[N_COINS] 39 | invariant: uint256 40 | token_supply: uint256 41 | 42 | event RemoveLiquidity: 43 | provider: indexed(address) 44 | token_amounts: uint256[N_COINS] 45 | fees: uint256[N_COINS] 46 | token_supply: uint256 47 | 48 | event RemoveLiquidityOne: 49 | provider: indexed(address) 50 | token_amount: uint256 51 | coin_amount: uint256 52 | 53 | event RemoveLiquidityImbalance: 54 | provider: indexed(address) 55 | token_amounts: uint256[N_COINS] 56 | fees: uint256[N_COINS] 57 | invariant: uint256 58 | token_supply: uint256 59 | 60 | event CommitNewAdmin: 61 | deadline: indexed(uint256) 62 | admin: indexed(address) 63 | 64 | event NewAdmin: 65 | admin: indexed(address) 66 | 67 | event CommitNewFee: 68 | deadline: indexed(uint256) 69 | fee: uint256 70 | admin_fee: uint256 71 | offpeg_fee_multiplier: uint256 72 | 73 | event NewFee: 74 | fee: uint256 75 | admin_fee: uint256 76 | offpeg_fee_multiplier: uint256 77 | 78 | event RampA: 79 | old_A: uint256 80 | new_A: uint256 81 | initial_time: uint256 82 | future_time: uint256 83 | 84 | event StopRampA: 85 | A: uint256 86 | t: uint256 87 | 88 | 89 | N_COINS: constant(int128) = 3 90 | PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1000000000000, 1000000000000] 91 | PRECISION: constant(uint256) = 10 ** 18 92 | 93 | FEE_DENOMINATOR: constant(uint256) = 10 ** 10 94 | MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 95 | MAX_FEE: constant(uint256) = 5 * 10 ** 9 96 | 97 | MAX_A: constant(uint256) = 10 ** 6 98 | MAX_A_CHANGE: constant(uint256) = 10 99 | A_PRECISION: constant(uint256) = 100 100 | 101 | ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 102 | MIN_RAMP_TIME: constant(uint256) = 86400 103 | 104 | MATIC_REWARDS: constant(address) = 0x357D51124f59836DeD84c8a1730D72B749d8BC23 105 | AAVE_LENDING_POOL: constant(address) = 0x8dFf5E27EA6b7AC08EbFdf9eB090F32ee9a30fcf 106 | WMATIC: constant(address) = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270 107 | 108 | coins: public(address[N_COINS]) 109 | underlying_coins: public(address[N_COINS]) 110 | admin_balances: public(uint256[N_COINS]) 111 | 112 | fee: public(uint256) # fee * 1e10 113 | offpeg_fee_multiplier: public(uint256) # * 1e10 114 | admin_fee: public(uint256) # admin_fee * 1e10 115 | 116 | owner: public(address) 117 | lp_token: public(address) 118 | 119 | aave_lending_pool: address 120 | aave_referral: uint256 121 | 122 | initial_A: public(uint256) 123 | future_A: public(uint256) 124 | initial_A_time: public(uint256) 125 | future_A_time: public(uint256) 126 | 127 | admin_actions_deadline: public(uint256) 128 | transfer_ownership_deadline: public(uint256) 129 | future_fee: public(uint256) 130 | future_admin_fee: public(uint256) 131 | future_offpeg_fee_multiplier: public(uint256) # * 1e10 132 | future_owner: public(address) 133 | 134 | is_killed: bool 135 | kill_deadline: uint256 136 | KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 137 | 138 | reward_receiver: public(address) 139 | admin_fee_receiver: public(address) 140 | 141 | @external 142 | def __init__( 143 | _coins: address[N_COINS], 144 | _underlying_coins: address[N_COINS], 145 | _pool_token: address, 146 | _A: uint256, 147 | _fee: uint256, 148 | _admin_fee: uint256, 149 | _offpeg_fee_multiplier: uint256, 150 | ): 151 | """ 152 | @notice Contract constructor 153 | @param _coins List of wrapped coin addresses 154 | @param _underlying_coins List of underlying coin addresses 155 | @param _pool_token Pool LP token address 156 | @param _A Amplification coefficient multiplied by n * (n - 1) 157 | @param _fee Swap fee expressed as an integer with 1e10 precision 158 | @param _admin_fee Percentage of fee taken as an admin fee, 159 | expressed as an integer with 1e10 precision 160 | @param _offpeg_fee_multiplier Offpeg fee multiplier 161 | """ 162 | for i in range(N_COINS): 163 | assert _coins[i] != ZERO_ADDRESS 164 | assert _underlying_coins[i] != ZERO_ADDRESS 165 | 166 | self.coins = _coins 167 | self.underlying_coins = _underlying_coins 168 | self.initial_A = _A * A_PRECISION 169 | self.future_A = _A * A_PRECISION 170 | self.fee = _fee 171 | self.admin_fee = _admin_fee 172 | self.offpeg_fee_multiplier = _offpeg_fee_multiplier 173 | self.owner = msg.sender 174 | self.admin_fee_receiver = msg.sender 175 | self.kill_deadline = block.timestamp + KILL_DEADLINE_DT 176 | self.lp_token = _pool_token 177 | 178 | # approve transfer of underlying coin to aave lending pool 179 | for coin in _underlying_coins: 180 | _response: Bytes[32] = raw_call( 181 | coin, 182 | concat( 183 | method_id("approve(address,uint256)"), 184 | convert(AAVE_LENDING_POOL, bytes32), 185 | convert(MAX_UINT256, bytes32) 186 | ), 187 | max_outsize=32 188 | ) 189 | if len(_response) != 0: 190 | assert convert(_response, bool) 191 | 192 | 193 | @view 194 | @internal 195 | def _A() -> uint256: 196 | t1: uint256 = self.future_A_time 197 | A1: uint256 = self.future_A 198 | 199 | if block.timestamp < t1: 200 | # handle ramping up and down of A 201 | A0: uint256 = self.initial_A 202 | t0: uint256 = self.initial_A_time 203 | # Expressions in uint256 cannot have negative numbers, thus "if" 204 | if A1 > A0: 205 | return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) 206 | else: 207 | return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) 208 | 209 | else: # when t1 == 0 or block.timestamp >= t1 210 | return A1 211 | 212 | 213 | @view 214 | @external 215 | def A() -> uint256: 216 | return self._A() / A_PRECISION 217 | 218 | 219 | @view 220 | @external 221 | def A_precise() -> uint256: 222 | return self._A() 223 | 224 | 225 | @pure 226 | @internal 227 | def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _feemul: uint256) -> uint256: 228 | if _feemul <= FEE_DENOMINATOR: 229 | return _fee 230 | else: 231 | xps2: uint256 = (xpi + xpj) 232 | xps2 *= xps2 # Doing just ** 2 can overflow apparently 233 | return (_feemul * _fee) / ( 234 | (_feemul - FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + \ 235 | FEE_DENOMINATOR) 236 | 237 | 238 | @view 239 | @external 240 | def dynamic_fee(i: int128, j: int128) -> uint256: 241 | """ 242 | @notice Return the fee for swapping between `i` and `j` 243 | @param i Index value for the coin to send 244 | @param j Index value of the coin to recieve 245 | @return Swap fee expressed as an integer with 1e10 precision 246 | """ 247 | precisions: uint256[N_COINS] = PRECISION_MUL 248 | xpi: uint256 = (ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i]) * precisions[i] 249 | xpj: uint256 = (ERC20(self.coins[j]).balanceOf(self) - self.admin_balances[j]) * precisions[j] 250 | return self._dynamic_fee(xpi, xpj, self.fee, self.offpeg_fee_multiplier) 251 | 252 | 253 | @view 254 | @external 255 | def balances(i: uint256) -> uint256: 256 | """ 257 | @notice Get the current balance of a coin within the 258 | pool, less the accrued admin fees 259 | @param i Index value for the coin to query balance of 260 | @return Token balance 261 | """ 262 | return ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] 263 | 264 | 265 | @view 266 | @internal 267 | def _balances() -> uint256[N_COINS]: 268 | result: uint256[N_COINS] = empty(uint256[N_COINS]) 269 | for i in range(N_COINS): 270 | result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] 271 | return result 272 | 273 | 274 | @pure 275 | @internal 276 | def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256: 277 | """ 278 | D invariant calculation in non-overflowing integer operations 279 | iteratively 280 | 281 | A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) 282 | 283 | Converging solution: 284 | D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) 285 | """ 286 | S: uint256 = 0 287 | 288 | for _x in xp: 289 | S += _x 290 | if S == 0: 291 | return 0 292 | 293 | Dprev: uint256 = 0 294 | D: uint256 = S 295 | Ann: uint256 = amp * N_COINS 296 | for _i in range(255): 297 | D_P: uint256 = D 298 | for _x in xp: 299 | D_P = D_P * D / (_x * N_COINS + 1) # +1 is to prevent /0 300 | Dprev = D 301 | D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) 302 | # Equality with the precision of 1 303 | if D > Dprev: 304 | if D - Dprev <= 1: 305 | return D 306 | else: 307 | if Dprev - D <= 1: 308 | return D 309 | # convergence typically occurs in 4 rounds or less, this should be unreachable! 310 | # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` 311 | raise 312 | 313 | 314 | 315 | @view 316 | @internal 317 | def get_D_precisions(coin_balances: uint256[N_COINS], amp: uint256) -> uint256: 318 | xp: uint256[N_COINS] = PRECISION_MUL 319 | for i in range(N_COINS): 320 | xp[i] *= coin_balances[i] 321 | return self.get_D(xp, amp) 322 | 323 | 324 | @view 325 | @external 326 | def get_virtual_price() -> uint256: 327 | """ 328 | @notice The current virtual price of the pool LP token 329 | @dev Useful for calculating profits 330 | @return LP token virtual price normalized to 1e18 331 | """ 332 | D: uint256 = self.get_D_precisions(self._balances(), self._A()) 333 | # D is in the units similar to DAI (e.g. converted to precision 1e18) 334 | # When balanced, D = n * x_u - total virtual value of the portfolio 335 | token_supply: uint256 = ERC20(self.lp_token).totalSupply() 336 | return D * PRECISION / token_supply 337 | 338 | 339 | @view 340 | @external 341 | def calc_token_amount(_amounts: uint256[N_COINS], is_deposit: bool) -> uint256: 342 | """ 343 | @notice Calculate addition or reduction in token supply from a deposit or withdrawal 344 | @dev This calculation accounts for slippage, but not fees. 345 | Needed to prevent front-running, not for precise calculations! 346 | @param _amounts Amount of each coin being deposited 347 | @param is_deposit set True for deposits, False for withdrawals 348 | @return Expected amount of LP tokens received 349 | """ 350 | coin_balances: uint256[N_COINS] = self._balances() 351 | amp: uint256 = self._A() 352 | D0: uint256 = self.get_D_precisions(coin_balances, amp) 353 | for i in range(N_COINS): 354 | if is_deposit: 355 | coin_balances[i] += _amounts[i] 356 | else: 357 | coin_balances[i] -= _amounts[i] 358 | D1: uint256 = self.get_D_precisions(coin_balances, amp) 359 | token_amount: uint256 = ERC20(self.lp_token).totalSupply() 360 | diff: uint256 = 0 361 | if is_deposit: 362 | diff = D1 - D0 363 | else: 364 | diff = D0 - D1 365 | return diff * token_amount / D0 366 | 367 | 368 | @internal 369 | def _claim_rewards(): 370 | # push wMatic rewards into the reward receiver 371 | reward_receiver: address = self.reward_receiver 372 | if reward_receiver != ZERO_ADDRESS: 373 | response: Bytes[32] = raw_call( 374 | MATIC_REWARDS, 375 | concat( 376 | method_id("claimRewards(address[],uint256,address)"), 377 | convert(32 * 3, bytes32), 378 | convert(MAX_UINT256, bytes32), 379 | convert(self, bytes32), 380 | convert(3, bytes32), 381 | convert(self.coins[0], bytes32), 382 | convert(self.coins[1], bytes32), 383 | convert(self.coins[2], bytes32), 384 | ), 385 | max_outsize=32 386 | ) 387 | amount: uint256 = convert(response, uint256) 388 | if amount > 0: 389 | assert ERC20(WMATIC).transfer(reward_receiver, amount) 390 | 391 | 392 | @external 393 | @nonreentrant('lock') 394 | def add_liquidity(_amounts: uint256[N_COINS], _min_mint_amount: uint256, _use_underlying: bool = False) -> uint256: 395 | """ 396 | @notice Deposit coins into the pool 397 | @param _amounts List of amounts of coins to deposit 398 | @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit 399 | @param _use_underlying If True, deposit underlying assets instead of aTokens 400 | @return Amount of LP tokens received by depositing 401 | """ 402 | 403 | assert not self.is_killed # dev: is killed 404 | self._claim_rewards() 405 | 406 | # Initial invariant 407 | amp: uint256 = self._A() 408 | old_balances: uint256[N_COINS] = self._balances() 409 | lp_token: address = self.lp_token 410 | token_supply: uint256 = ERC20(lp_token).totalSupply() 411 | D0: uint256 = 0 412 | if token_supply != 0: 413 | D0 = self.get_D_precisions(old_balances, amp) 414 | 415 | new_balances: uint256[N_COINS] = old_balances 416 | for i in range(N_COINS): 417 | if token_supply == 0: 418 | assert _amounts[i] != 0 # dev: initial deposit requires all coins 419 | new_balances[i] += _amounts[i] 420 | 421 | # Invariant after change 422 | D1: uint256 = self.get_D_precisions(new_balances, amp) 423 | assert D1 > D0 424 | 425 | # We need to recalculate the invariant accounting for fees 426 | # to calculate fair user's share 427 | fees: uint256[N_COINS] = empty(uint256[N_COINS]) 428 | mint_amount: uint256 = 0 429 | if token_supply != 0: 430 | # Only account for fees if we are not the first to deposit 431 | ys: uint256 = (D0 + D1) / N_COINS 432 | _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) 433 | _feemul: uint256 = self.offpeg_fee_multiplier 434 | _admin_fee: uint256 = self.admin_fee 435 | difference: uint256 = 0 436 | for i in range(N_COINS): 437 | ideal_balance: uint256 = D1 * old_balances[i] / D0 438 | new_balance: uint256 = new_balances[i] 439 | if ideal_balance > new_balance: 440 | difference = ideal_balance - new_balance 441 | else: 442 | difference = new_balance - ideal_balance 443 | xs: uint256 = old_balances[i] + new_balance 444 | fees[i] = self._dynamic_fee(xs, ys, _fee, _feemul) * difference / FEE_DENOMINATOR 445 | if _admin_fee != 0: 446 | self.admin_balances[i] += fees[i] * _admin_fee / FEE_DENOMINATOR 447 | new_balances[i] = new_balance - fees[i] 448 | D2: uint256 = self.get_D_precisions(new_balances, amp) 449 | mint_amount = token_supply * (D2 - D0) / D0 450 | else: 451 | mint_amount = D1 # Take the dust if there was any 452 | 453 | assert mint_amount >= _min_mint_amount, "Slippage screwed you" 454 | 455 | # Take coins from the sender 456 | if _use_underlying: 457 | aave_referral: bytes32 = convert(self.aave_referral, bytes32) 458 | 459 | # Take coins from the sender 460 | for i in range(N_COINS): 461 | amount: uint256 = _amounts[i] 462 | if amount != 0: 463 | coin: address = self.underlying_coins[i] 464 | # transfer underlying coin from msg.sender to self 465 | _response: Bytes[32] = raw_call( 466 | coin, 467 | concat( 468 | method_id("transferFrom(address,address,uint256)"), 469 | convert(msg.sender, bytes32), 470 | convert(self, bytes32), 471 | convert(amount, bytes32) 472 | ), 473 | max_outsize=32 474 | ) 475 | if len(_response) != 0: 476 | assert convert(_response, bool) 477 | 478 | # deposit to aave lending pool 479 | raw_call( 480 | AAVE_LENDING_POOL, 481 | concat( 482 | method_id("deposit(address,uint256,address,uint16)"), 483 | convert(coin, bytes32), 484 | convert(amount, bytes32), 485 | convert(self, bytes32), 486 | aave_referral, 487 | ) 488 | ) 489 | else: 490 | for i in range(N_COINS): 491 | amount: uint256 = _amounts[i] 492 | if amount != 0: 493 | assert ERC20(self.coins[i]).transferFrom(msg.sender, self, amount) # dev: failed transfer 494 | 495 | # Mint pool tokens 496 | CurveToken(lp_token).mint(msg.sender, mint_amount) 497 | 498 | log AddLiquidity(msg.sender, _amounts, fees, D1, token_supply + mint_amount) 499 | 500 | return mint_amount 501 | 502 | 503 | @view 504 | @internal 505 | def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS]) -> uint256: 506 | """ 507 | Calculate x[j] if one makes x[i] = x 508 | 509 | Done by solving quadratic equation iteratively. 510 | x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) 511 | x_1**2 + b*x_1 = c 512 | 513 | x_1 = (x_1**2 + c) / (2*x_1 + b) 514 | """ 515 | # x in the input is converted to the same price/precision 516 | 517 | assert i != j # dev: same coin 518 | assert j >= 0 # dev: j below zero 519 | assert j < N_COINS # dev: j above N_COINS 520 | 521 | # should be unreachable, but good for safety 522 | assert i >= 0 523 | assert i < N_COINS 524 | 525 | amp: uint256 = self._A() 526 | D: uint256 = self.get_D(xp, amp) 527 | Ann: uint256 = amp * N_COINS 528 | c: uint256 = D 529 | S_: uint256 = 0 530 | _x: uint256 = 0 531 | y_prev: uint256 = 0 532 | 533 | for _i in range(N_COINS): 534 | if _i == i: 535 | _x = x 536 | elif _i != j: 537 | _x = xp[_i] 538 | else: 539 | continue 540 | S_ += _x 541 | c = c * D / (_x * N_COINS) 542 | c = c * D * A_PRECISION / (Ann * N_COINS) 543 | b: uint256 = S_ + D * A_PRECISION / Ann # - D 544 | y: uint256 = D 545 | for _i in range(255): 546 | y_prev = y 547 | y = (y*y + c) / (2 * y + b - D) 548 | # Equality with the precision of 1 549 | if y > y_prev: 550 | if y - y_prev <= 1: 551 | return y 552 | else: 553 | if y_prev - y <= 1: 554 | return y 555 | raise 556 | 557 | 558 | @view 559 | @internal 560 | def _get_dy(i: int128, j: int128, dx: uint256) -> uint256: 561 | xp: uint256[N_COINS] = self._balances() 562 | precisions: uint256[N_COINS] = PRECISION_MUL 563 | for k in range(N_COINS): 564 | xp[k] *= precisions[k] 565 | 566 | x: uint256 = xp[i] + dx * precisions[i] 567 | y: uint256 = self.get_y(i, j, x, xp) 568 | dy: uint256 = (xp[j] - y) / precisions[j] 569 | _fee: uint256 = self._dynamic_fee( 570 | (xp[i] + x) / 2, (xp[j] + y) / 2, self.fee, self.offpeg_fee_multiplier 571 | ) * dy / FEE_DENOMINATOR 572 | return dy - _fee 573 | 574 | 575 | @view 576 | @external 577 | def get_dy(i: int128, j: int128, dx: uint256) -> uint256: 578 | return self._get_dy(i, j, dx) 579 | 580 | 581 | @view 582 | @external 583 | def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: 584 | return self._get_dy(i, j, dx) 585 | 586 | 587 | @internal 588 | def _exchange(i: int128, j: int128, dx: uint256) -> uint256: 589 | assert not self.is_killed # dev: is killed 590 | # dx and dy are in aTokens 591 | 592 | self._claim_rewards() 593 | xp: uint256[N_COINS] = self._balances() 594 | precisions: uint256[N_COINS] = PRECISION_MUL 595 | for k in range(N_COINS): 596 | xp[k] *= precisions[k] 597 | 598 | x: uint256 = xp[i] + dx * precisions[i] 599 | y: uint256 = self.get_y(i, j, x, xp) 600 | dy: uint256 = xp[j] - y 601 | dy_fee: uint256 = dy * self._dynamic_fee( 602 | (xp[i] + x) / 2, (xp[j] + y) / 2, self.fee, self.offpeg_fee_multiplier 603 | ) / FEE_DENOMINATOR 604 | 605 | admin_fee: uint256 = self.admin_fee 606 | if admin_fee != 0: 607 | dy_admin_fee: uint256 = dy_fee * admin_fee / FEE_DENOMINATOR 608 | if dy_admin_fee != 0: 609 | self.admin_balances[j] += dy_admin_fee / precisions[j] 610 | 611 | return (dy - dy_fee) / precisions[j] 612 | 613 | 614 | @external 615 | @nonreentrant('lock') 616 | def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: 617 | """ 618 | @notice Perform an exchange between two coins 619 | @dev Index values can be found via the `coins` public getter method 620 | @param i Index value for the coin to send 621 | @param j Index valie of the coin to recieve 622 | @param dx Amount of `i` being exchanged 623 | @param min_dy Minimum amount of `j` to receive 624 | @return Actual amount of `j` received 625 | """ 626 | dy: uint256 = self._exchange(i, j, dx) 627 | assert dy >= min_dy, "Exchange resulted in fewer coins than expected" 628 | 629 | assert ERC20(self.coins[i]).transferFrom(msg.sender, self, dx) 630 | assert ERC20(self.coins[j]).transfer(msg.sender, dy) 631 | 632 | log TokenExchange(msg.sender, i, dx, j, dy) 633 | 634 | return dy 635 | 636 | 637 | @external 638 | @nonreentrant('lock') 639 | def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256) -> uint256: 640 | """ 641 | @notice Perform an exchange between two underlying coins 642 | @dev Index values can be found via the `underlying_coins` public getter method 643 | @param i Index value for the underlying coin to send 644 | @param j Index valie of the underlying coin to recieve 645 | @param dx Amount of `i` being exchanged 646 | @param min_dy Minimum amount of `j` to receive 647 | @return Actual amount of `j` received 648 | """ 649 | dy: uint256 = self._exchange(i, j, dx) 650 | assert dy >= min_dy, "Exchange resulted in fewer coins than expected" 651 | 652 | u_coin_i: address = self.underlying_coins[i] 653 | 654 | # transfer underlying coin from msg.sender to self 655 | _response: Bytes[32] = raw_call( 656 | u_coin_i, 657 | concat( 658 | method_id("transferFrom(address,address,uint256)"), 659 | convert(msg.sender, bytes32), 660 | convert(self, bytes32), 661 | convert(dx, bytes32) 662 | ), 663 | max_outsize=32 664 | ) 665 | if len(_response) != 0: 666 | assert convert(_response, bool) 667 | 668 | # deposit to aave lending pool 669 | raw_call( 670 | AAVE_LENDING_POOL, 671 | concat( 672 | method_id("deposit(address,uint256,address,uint16)"), 673 | convert(u_coin_i, bytes32), 674 | convert(dx, bytes32), 675 | convert(self, bytes32), 676 | convert(self.aave_referral, bytes32), 677 | ) 678 | ) 679 | # withdraw `j` underlying from lending pool and transfer to caller 680 | LendingPool(AAVE_LENDING_POOL).withdraw(self.underlying_coins[j], dy, msg.sender) 681 | 682 | log TokenExchangeUnderlying(msg.sender, i, dx, j, dy) 683 | 684 | return dy 685 | 686 | 687 | @external 688 | @nonreentrant('lock') 689 | def remove_liquidity( 690 | _amount: uint256, 691 | _min_amounts: uint256[N_COINS], 692 | _use_underlying: bool = False, 693 | ) -> uint256[N_COINS]: 694 | """ 695 | @notice Withdraw coins from the pool 696 | @dev Withdrawal amounts are based on current deposit ratios 697 | @param _amount Quantity of LP tokens to burn in the withdrawal 698 | @param _min_amounts Minimum amounts of underlying coins to receive 699 | @param _use_underlying If True, withdraw underlying assets instead of aTokens 700 | @return List of amounts of coins that were withdrawn 701 | """ 702 | if not self.is_killed: 703 | self._claim_rewards() 704 | amounts: uint256[N_COINS] = self._balances() 705 | lp_token: address = self.lp_token 706 | total_supply: uint256 = ERC20(lp_token).totalSupply() 707 | CurveToken(lp_token).burnFrom(msg.sender, _amount) # dev: insufficient funds 708 | 709 | for i in range(N_COINS): 710 | value: uint256 = amounts[i] * _amount / total_supply 711 | assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" 712 | amounts[i] = value 713 | if _use_underlying: 714 | LendingPool(AAVE_LENDING_POOL).withdraw(self.underlying_coins[i], value, msg.sender) 715 | else: 716 | assert ERC20(self.coins[i]).transfer(msg.sender, value) 717 | 718 | log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) 719 | 720 | return amounts 721 | 722 | 723 | @external 724 | @nonreentrant('lock') 725 | def remove_liquidity_imbalance( 726 | _amounts: uint256[N_COINS], 727 | _max_burn_amount: uint256, 728 | _use_underlying: bool = False 729 | ) -> uint256: 730 | """ 731 | @notice Withdraw coins from the pool in an imbalanced amount 732 | @param _amounts List of amounts of underlying coins to withdraw 733 | @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal 734 | @param _use_underlying If True, withdraw underlying assets instead of aTokens 735 | @return Actual amount of the LP token burned in the withdrawal 736 | """ 737 | assert not self.is_killed # dev: is killed 738 | 739 | self._claim_rewards() 740 | amp: uint256 = self._A() 741 | old_balances: uint256[N_COINS] = self._balances() 742 | D0: uint256 = self.get_D_precisions(old_balances, amp) 743 | new_balances: uint256[N_COINS] = old_balances 744 | for i in range(N_COINS): 745 | new_balances[i] -= _amounts[i] 746 | D1: uint256 = self.get_D_precisions(new_balances, amp) 747 | ys: uint256 = (D0 + D1) / N_COINS 748 | 749 | lp_token: address = self.lp_token 750 | token_supply: uint256 = ERC20(lp_token).totalSupply() 751 | assert token_supply != 0 # dev: zero total supply 752 | 753 | _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) 754 | _feemul: uint256 = self.offpeg_fee_multiplier 755 | _admin_fee: uint256 = self.admin_fee 756 | fees: uint256[N_COINS] = empty(uint256[N_COINS]) 757 | for i in range(N_COINS): 758 | ideal_balance: uint256 = D1 * old_balances[i] / D0 759 | new_balance: uint256 = new_balances[i] 760 | difference: uint256 = 0 761 | if ideal_balance > new_balance: 762 | difference = ideal_balance - new_balance 763 | else: 764 | difference = new_balance - ideal_balance 765 | xs: uint256 = new_balance + old_balances[i] 766 | fees[i] = self._dynamic_fee(xs, ys, _fee, _feemul) * difference / FEE_DENOMINATOR 767 | if _admin_fee != 0: 768 | self.admin_balances[i] += fees[i] * _admin_fee / FEE_DENOMINATOR 769 | new_balances[i] -= fees[i] 770 | D2: uint256 = self.get_D_precisions(new_balances, amp) 771 | 772 | token_amount: uint256 = (D0 - D2) * token_supply / D0 773 | assert token_amount != 0 # dev: zero tokens burned 774 | assert token_amount <= _max_burn_amount, "Slippage screwed you" 775 | 776 | CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds 777 | 778 | for i in range(N_COINS): 779 | amount: uint256 = _amounts[i] 780 | if amount != 0: 781 | if _use_underlying: 782 | LendingPool(AAVE_LENDING_POOL).withdraw(self.underlying_coins[i], amount, msg.sender) 783 | else: 784 | assert ERC20(self.coins[i]).transfer(msg.sender, amount) 785 | 786 | log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, token_supply - token_amount) 787 | 788 | return token_amount 789 | 790 | 791 | @pure 792 | @internal 793 | def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: 794 | """ 795 | Calculate x[i] if one reduces D from being calculated for xp to D 796 | 797 | Done by solving quadratic equation iteratively. 798 | x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) 799 | x_1**2 + b*x_1 = c 800 | 801 | x_1 = (x_1**2 + c) / (2*x_1 + b) 802 | """ 803 | # x in the input is converted to the same price/precision 804 | 805 | assert i >= 0 # dev: i below zero 806 | assert i < N_COINS # dev: i above N_COINS 807 | 808 | Ann: uint256 = A_ * N_COINS 809 | c: uint256 = D 810 | S_: uint256 = 0 811 | _x: uint256 = 0 812 | y_prev: uint256 = 0 813 | 814 | for _i in range(N_COINS): 815 | if _i != i: 816 | _x = xp[_i] 817 | else: 818 | continue 819 | S_ += _x 820 | c = c * D / (_x * N_COINS) 821 | c = c * D * A_PRECISION / (Ann * N_COINS) 822 | b: uint256 = S_ + D * A_PRECISION / Ann 823 | y: uint256 = D 824 | 825 | for _i in range(255): 826 | y_prev = y 827 | y = (y*y + c) / (2 * y + b - D) 828 | # Equality with the precision of 1 829 | if y > y_prev: 830 | if y - y_prev <= 1: 831 | return y 832 | else: 833 | if y_prev - y <= 1: 834 | return y 835 | raise 836 | 837 | 838 | @view 839 | @internal 840 | def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: 841 | # First, need to calculate 842 | # * Get current D 843 | # * Solve Eqn against y_i for D - _token_amount 844 | amp: uint256 = self._A() 845 | xp: uint256[N_COINS] = self._balances() 846 | precisions: uint256[N_COINS] = PRECISION_MUL 847 | 848 | for j in range(N_COINS): 849 | xp[j] *= precisions[j] 850 | 851 | D0: uint256 = self.get_D(xp, amp) 852 | D1: uint256 = D0 - _token_amount * D0 / ERC20(self.lp_token).totalSupply() 853 | new_y: uint256 = self.get_y_D(amp, i, xp, D1) 854 | 855 | xp_reduced: uint256[N_COINS] = xp 856 | ys: uint256 = (D0 + D1) / (2 * N_COINS) 857 | 858 | _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) 859 | feemul: uint256 = self.offpeg_fee_multiplier 860 | for j in range(N_COINS): 861 | dx_expected: uint256 = 0 862 | xavg: uint256 = 0 863 | if j == i: 864 | dx_expected = xp[j] * D1 / D0 - new_y 865 | xavg = (xp[j] + new_y) / 2 866 | else: 867 | dx_expected = xp[j] - xp[j] * D1 / D0 868 | xavg = xp[j] 869 | xp_reduced[j] -= self._dynamic_fee(xavg, ys, _fee, feemul) * dx_expected / FEE_DENOMINATOR 870 | 871 | dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) 872 | 873 | return (dy - 1) / precisions[i] 874 | 875 | 876 | @view 877 | @external 878 | def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: 879 | """ 880 | @notice Calculate the amount received when withdrawing a single coin 881 | @dev Result is the same for underlying or wrapped asset withdrawals 882 | @param _token_amount Amount of LP tokens to burn in the withdrawal 883 | @param i Index value of the coin to withdraw 884 | @return Amount of coin received 885 | """ 886 | return self._calc_withdraw_one_coin(_token_amount, i) 887 | 888 | 889 | @external 890 | @nonreentrant('lock') 891 | def remove_liquidity_one_coin( 892 | _token_amount: uint256, 893 | i: int128, 894 | _min_amount: uint256, 895 | _use_underlying: bool = False 896 | ) -> uint256: 897 | """ 898 | @notice Withdraw a single coin from the pool 899 | @param _token_amount Amount of LP tokens to burn in the withdrawal 900 | @param i Index value of the coin to withdraw 901 | @param _min_amount Minimum amount of coin to receive 902 | @param _use_underlying If True, withdraw underlying assets instead of aTokens 903 | @return Amount of coin received 904 | """ 905 | assert not self.is_killed # dev: is killed 906 | 907 | self._claim_rewards() 908 | dy: uint256 = self._calc_withdraw_one_coin(_token_amount, i) 909 | assert dy >= _min_amount, "Not enough coins removed" 910 | 911 | CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds 912 | 913 | if _use_underlying: 914 | LendingPool(AAVE_LENDING_POOL).withdraw(self.underlying_coins[i], dy, msg.sender) 915 | else: 916 | assert ERC20(self.coins[i]).transfer(msg.sender, dy) 917 | 918 | log RemoveLiquidityOne(msg.sender, _token_amount, dy) 919 | 920 | return dy 921 | 922 | 923 | ### Admin functions ### 924 | 925 | @external 926 | def ramp_A(_future_A: uint256, _future_time: uint256): 927 | assert msg.sender == self.owner # dev: only owner 928 | assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME 929 | assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time 930 | 931 | _initial_A: uint256 = self._A() 932 | _future_A_p: uint256 = _future_A * A_PRECISION 933 | 934 | assert _future_A > 0 and _future_A < MAX_A 935 | if _future_A_p < _initial_A: 936 | assert _future_A_p * MAX_A_CHANGE >= _initial_A 937 | else: 938 | assert _future_A_p <= _initial_A * MAX_A_CHANGE 939 | 940 | self.initial_A = _initial_A 941 | self.future_A = _future_A_p 942 | self.initial_A_time = block.timestamp 943 | self.future_A_time = _future_time 944 | 945 | log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) 946 | 947 | 948 | @external 949 | def stop_ramp_A(): 950 | assert msg.sender == self.owner # dev: only owner 951 | 952 | current_A: uint256 = self._A() 953 | self.initial_A = current_A 954 | self.future_A = current_A 955 | self.initial_A_time = block.timestamp 956 | self.future_A_time = block.timestamp 957 | # now (block.timestamp < t1) is always False, so we return saved A 958 | 959 | log StopRampA(current_A, block.timestamp) 960 | 961 | 962 | @external 963 | def commit_new_fee(new_fee: uint256, new_admin_fee: uint256, new_offpeg_fee_multiplier: uint256): 964 | assert msg.sender == self.owner # dev: only owner 965 | assert self.admin_actions_deadline == 0 # dev: active action 966 | assert new_fee <= MAX_FEE # dev: fee exceeds maximum 967 | assert new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum 968 | assert new_offpeg_fee_multiplier * new_fee <= MAX_FEE * FEE_DENOMINATOR # dev: offpeg multiplier exceeds maximum 969 | 970 | _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY 971 | self.admin_actions_deadline = _deadline 972 | self.future_fee = new_fee 973 | self.future_admin_fee = new_admin_fee 974 | self.future_offpeg_fee_multiplier = new_offpeg_fee_multiplier 975 | 976 | log CommitNewFee(_deadline, new_fee, new_admin_fee, new_offpeg_fee_multiplier) 977 | 978 | 979 | @external 980 | def apply_new_fee(): 981 | assert msg.sender == self.owner # dev: only owner 982 | assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time 983 | assert self.admin_actions_deadline != 0 # dev: no active action 984 | 985 | self.admin_actions_deadline = 0 986 | _fee: uint256 = self.future_fee 987 | _admin_fee: uint256 = self.future_admin_fee 988 | _fml: uint256 = self.future_offpeg_fee_multiplier 989 | self.fee = _fee 990 | self.admin_fee = _admin_fee 991 | self.offpeg_fee_multiplier = _fml 992 | 993 | log NewFee(_fee, _admin_fee, _fml) 994 | 995 | 996 | @external 997 | def revert_new_parameters(): 998 | assert msg.sender == self.owner # dev: only owner 999 | 1000 | self.admin_actions_deadline = 0 1001 | 1002 | 1003 | @external 1004 | def commit_transfer_ownership(_owner: address): 1005 | assert msg.sender == self.owner # dev: only owner 1006 | assert self.transfer_ownership_deadline == 0 # dev: active transfer 1007 | 1008 | _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY 1009 | self.transfer_ownership_deadline = _deadline 1010 | self.future_owner = _owner 1011 | 1012 | log CommitNewAdmin(_deadline, _owner) 1013 | 1014 | 1015 | @external 1016 | def apply_transfer_ownership(): 1017 | assert msg.sender == self.owner # dev: only owner 1018 | assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time 1019 | assert self.transfer_ownership_deadline != 0 # dev: no active transfer 1020 | 1021 | self.transfer_ownership_deadline = 0 1022 | _owner: address = self.future_owner 1023 | self.owner = _owner 1024 | 1025 | log NewAdmin(_owner) 1026 | 1027 | 1028 | @external 1029 | def revert_transfer_ownership(): 1030 | assert msg.sender == self.owner # dev: only owner 1031 | 1032 | self.transfer_ownership_deadline = 0 1033 | 1034 | 1035 | @external 1036 | def withdraw_admin_fees(): 1037 | assert msg.sender == self.owner # dev: only owner 1038 | 1039 | for i in range(N_COINS): 1040 | value: uint256 = self.admin_balances[i] 1041 | if value != 0: 1042 | assert ERC20(self.coins[i]).transfer(self.admin_fee_receiver, value) 1043 | self.admin_balances[i] = 0 1044 | 1045 | 1046 | @external 1047 | def donate_admin_fees(): 1048 | """ 1049 | Just in case admin balances somehow become higher than total (rounding error?) 1050 | this can be used to fix the state, too 1051 | """ 1052 | assert msg.sender == self.owner # dev: only owner 1053 | self.admin_balances = empty(uint256[N_COINS]) 1054 | 1055 | 1056 | @external 1057 | def kill_me(): 1058 | assert msg.sender == self.owner # dev: only owner 1059 | assert self.kill_deadline > block.timestamp # dev: deadline has passed 1060 | self.is_killed = True 1061 | 1062 | 1063 | @external 1064 | def unkill_me(): 1065 | assert msg.sender == self.owner # dev: only owner 1066 | self.is_killed = False 1067 | 1068 | 1069 | @external 1070 | def set_aave_referral(referral_code: uint256): 1071 | assert msg.sender == self.owner # dev: only owner 1072 | assert referral_code < 2 ** 16 # dev: uint16 overflow 1073 | self.aave_referral = referral_code 1074 | 1075 | 1076 | @external 1077 | def set_reward_receiver(_reward_receiver: address): 1078 | assert msg.sender == self.owner 1079 | self.reward_receiver = _reward_receiver 1080 | 1081 | 1082 | @external 1083 | def set_admin_fee_receiver(_admin_fee_receiver: address): 1084 | assert msg.sender == self.owner 1085 | self.admin_fee_receiver = _admin_fee_receiver 1086 | -------------------------------------------------------------------------------- /contracts/pools/aave/pooldata.json: -------------------------------------------------------------------------------- 1 | { 2 | "lp_contract": "CurveTokenV3", 3 | "wrapped_interface": "AToken", 4 | "swap_address": "0x445FE580eF8d70FF569aB36e80c647af338db351", 5 | "lp_token_address": "0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171", 6 | "gauge_addresses": ["0xe381C25de995d62b453aF8B931aAc84fcCaa7A62"], 7 | "lp_constructor": { 8 | "symbol": "am3CRV", 9 | "name": "Curve.fi amDAI/amUSDC/amUSDT" 10 | }, 11 | "swap_constructor": { 12 | "_A": 200, 13 | "_fee": 4000000, 14 | "_admin_fee": 5000000000, 15 | "_offpeg_fee_multiplier": 20000000000 16 | }, 17 | "coins": [ 18 | { 19 | "name": "amDAI", 20 | "decimals": 18, 21 | "wrapped_decimals": 18, 22 | "underlying_address": "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", 23 | "wrapped_address": "0x27F8D03b3a2196956ED754baDc28D73be8830A6e", 24 | "underlying_interface": "BridgeToken", 25 | "wrapped_interface": "AToken" 26 | }, 27 | { 28 | "name": "amUSDC", 29 | "decimals": 6, 30 | "wrapped_decimals": 6, 31 | "underlying_address": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", 32 | "wrapped_address": "0x1a13F4Ca1d028320A707D99520AbFefca3998b7F", 33 | "underlying_interface": "BridgeToken", 34 | "wrapped_interface": "AToken" 35 | }, 36 | { 37 | "name": "amUSDT", 38 | "decimals": 6, 39 | "wrapped_decimals": 6, 40 | "underlying_address": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", 41 | "wrapped_address": "0x60D55F02A771d515e077c9C2403a1ef324885CeC", 42 | "underlying_interface": "BridgeToken", 43 | "wrapped_interface": "AToken" 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /contracts/pools/ren/README.md: -------------------------------------------------------------------------------- 1 | # curve-contract-polygon/contracts/pools/ren 2 | 3 | [Curve RenBTC pool](). This is a lending pool. 4 | 5 | ## Contracts 6 | 7 | * [`StableSwapRen`](StableSwapRen.vy): Curve stablecoin AMM contract 8 | 9 | ## Deployments 10 | 11 | * [`CurveContractV3`](../../tokens/CurveTokenV1.vy): []() 12 | * [`RewardsOnlyGauge`](../../gauges/RewardsOnlyGauge.vy): []() 13 | * [`StableSwapRen`](StableSwapRen.vy): []() 14 | 15 | ## Stablecoins 16 | 17 | Curve RenBTC pool supports swaps between the following stablecoins: 18 | 19 | * `renBTC`: [0xDBf31dF14B66535aF65AaC99C32e9eA844e14501](https://polygonscan.com/address/0xDBf31dF14B66535aF65AaC99C32e9eA844e14501) 20 | * `wBTC`: [0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6](https://polygonscan.com/address/0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6) -------------------------------------------------------------------------------- /contracts/pools/ren/pooldata.json: -------------------------------------------------------------------------------- 1 | { 2 | "lp_contract": "CurveTokenV3", 3 | "swap_address": "0xC2d95EEF97Ec6C17551d45e77B590dc1F9117C67", 4 | "lp_token_address": "0xf8a57c1d3b9629b77b6726a042ca48990A84Fb49", 5 | "gauge_addresses": [ 6 | "0x93B14f53528a59E655d8Ce39bdba443DcDdddc4c" 7 | ], 8 | "lp_constructor": { 9 | "symbol": "crv amWBTC/renBTC", 10 | "name": "Curve.fi amWBTC/renBTC" 11 | }, 12 | "swap_constructor": { 13 | "_A": 200, 14 | "_fee": 4000000, 15 | "_admin_fee": 5000000000, 16 | "_offpeg_fee_multiplier": 20000000000 17 | }, 18 | "coins": [ 19 | { 20 | "name": "amWBTC", 21 | "decimals": 8, 22 | "wrapped_decimals": 8, 23 | "underlying_address": "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", 24 | "wrapped_address": "0x5c2ed810328349100A66B82b78a1791B101C9D61", 25 | "underlying_interface": "BridgeToken", 26 | "wrapped_interface": "AToken" 27 | }, 28 | { 29 | "name": "renBTC", 30 | "decimals": 8, 31 | "underlying_address": "0xDBf31dF14B66535aF65AaC99C32e9eA844e14501", 32 | "underlying_interface": "renERC20" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /contracts/registry/README.md: -------------------------------------------------------------------------------- 1 | # curve-contract/contracts/registry 2 | 3 | A streamlined version of the Curve [pool registry](https://github.com/curvefi/curve-pool-registry) is available on Polygon. The address provider is deployed to the same address as on Ethereum: 4 | 5 | * [`0x0000000022D53366457F9d5E68Ec105046FC4383`](https://explorer-mainnet.maticvigil.com/address/0x0000000022D53366457F9d5E68Ec105046FC4383) 6 | 7 | Contracts that are unchanged between Ethereum and Polygon are not included within this repo. Contracts included here have been modified to better fit the requirements of Curve on Polygon. The public APIs are consistent between ETH and Polygon. 8 | -------------------------------------------------------------------------------- /contracts/registry/Swaps.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.12 2 | """ 3 | @title Curve Registry Exchange Contract 4 | @license MIT 5 | @author Curve.Fi 6 | @notice Find pools, query exchange rates and perform swaps 7 | """ 8 | 9 | from vyper.interfaces import ERC20 10 | 11 | 12 | interface AddressProvider: 13 | def admin() -> address: view 14 | def get_registry() -> address: view 15 | def get_address(idx: uint256) -> address: view 16 | 17 | interface CurvePool: 18 | def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): payable 19 | def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256): payable 20 | def get_dy(i: int128, j: int128, amount: uint256) -> uint256: view 21 | def get_dy_underlying(i: int128, j: int128, amount: uint256) -> uint256: view 22 | 23 | interface Registry: 24 | def address_provider() -> address: view 25 | def get_A(_pool: address) -> uint256: view 26 | def get_fees(_pool: address) -> uint256[2]: view 27 | def get_coin_indices(_pool: address, _from: address, _to: address) -> (int128, int128, bool): view 28 | def get_n_coins(_pool: address) -> uint256[2]: view 29 | def get_balances(_pool: address) -> uint256[MAX_COINS]: view 30 | def get_underlying_balances(_pool: address) -> uint256[MAX_COINS]: view 31 | def get_rates(_pool: address) -> uint256[MAX_COINS]: view 32 | def get_decimals(_pool: address) -> uint256[MAX_COINS]: view 33 | def get_underlying_decimals(_pool: address) -> uint256[MAX_COINS]: view 34 | def find_pool_for_coins(_from: address, _to: address, i: uint256) -> address: view 35 | def get_lp_token(_pool: address) -> address: view 36 | 37 | interface Calculator: 38 | def get_dx(n_coins: uint256, balances: uint256[MAX_COINS], amp: uint256, fee: uint256, 39 | rates: uint256[MAX_COINS], precisions: uint256[MAX_COINS], 40 | i: int128, j: int128, dx: uint256) -> uint256: view 41 | def get_dy(n_coins: uint256, balances: uint256[MAX_COINS], amp: uint256, fee: uint256, 42 | rates: uint256[MAX_COINS], precisions: uint256[MAX_COINS], 43 | i: int128, j: int128, dx: uint256[CALC_INPUT_SIZE]) -> uint256[CALC_INPUT_SIZE]: view 44 | 45 | 46 | event TokenExchange: 47 | buyer: indexed(address) 48 | receiver: indexed(address) 49 | pool: indexed(address) 50 | token_sold: address 51 | token_bought: address 52 | amount_sold: uint256 53 | amount_bought: uint256 54 | 55 | 56 | ETH_ADDRESS: constant(address) = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 57 | MAX_COINS: constant(int128) = 8 58 | CALC_INPUT_SIZE: constant(uint256) = 100 59 | EMPTY_POOL_LIST: constant(address[8]) = [ 60 | ZERO_ADDRESS, 61 | ZERO_ADDRESS, 62 | ZERO_ADDRESS, 63 | ZERO_ADDRESS, 64 | ZERO_ADDRESS, 65 | ZERO_ADDRESS, 66 | ZERO_ADDRESS, 67 | ZERO_ADDRESS, 68 | ] 69 | 70 | 71 | address_provider: AddressProvider 72 | registry: public(address) 73 | 74 | default_calculator: public(address) 75 | is_killed: public(bool) 76 | pool_calculator: HashMap[address, address] 77 | 78 | is_approved: HashMap[address, HashMap[address, bool]] 79 | 80 | 81 | @external 82 | def __init__(_address_provider: address, _calculator: address): 83 | """ 84 | @notice Constructor function 85 | """ 86 | self.address_provider = AddressProvider(_address_provider) 87 | self.registry = AddressProvider(_address_provider).get_registry() 88 | self.default_calculator = _calculator 89 | 90 | 91 | @external 92 | @payable 93 | def __default__(): 94 | pass 95 | 96 | 97 | @view 98 | @internal 99 | def _get_exchange_amount( 100 | _registry: address, 101 | _pool: address, 102 | _from: address, 103 | _to: address, 104 | _amount: uint256 105 | ) -> uint256: 106 | """ 107 | @notice Get the current number of coins received in an exchange 108 | @param _registry Registry address 109 | @param _pool Pool address 110 | @param _from Address of coin to be sent 111 | @param _to Address of coin to be received 112 | @param _amount Quantity of `_from` to be sent 113 | @return Quantity of `_to` to be received 114 | """ 115 | i: int128 = 0 116 | j: int128 = 0 117 | is_underlying: bool = False 118 | i, j, is_underlying = Registry(_registry).get_coin_indices(_pool, _from, _to) # dev: no market 119 | 120 | if is_underlying: 121 | return CurvePool(_pool).get_dy_underlying(i, j, _amount) 122 | 123 | return CurvePool(_pool).get_dy(i, j, _amount) 124 | 125 | 126 | @internal 127 | def _exchange( 128 | _registry: address, 129 | _pool: address, 130 | _from: address, 131 | _to: address, 132 | _amount: uint256, 133 | _expected: uint256, 134 | _sender: address, 135 | _receiver: address, 136 | ) -> uint256: 137 | 138 | assert not self.is_killed 139 | 140 | initial_balance: uint256 = 0 141 | eth_amount: uint256 = 0 142 | received_amount: uint256 = 0 143 | 144 | i: int128 = 0 145 | j: int128 = 0 146 | is_underlying: bool = False 147 | i, j, is_underlying = Registry(_registry).get_coin_indices(_pool, _from, _to) # dev: no market 148 | 149 | # record initial balance 150 | if _to == ETH_ADDRESS: 151 | initial_balance = self.balance 152 | else: 153 | initial_balance = ERC20(_to).balanceOf(self) 154 | 155 | # perform / verify input transfer 156 | if _from == ETH_ADDRESS: 157 | eth_amount = _amount 158 | else: 159 | response: Bytes[32] = raw_call( 160 | _from, 161 | concat( 162 | method_id("transferFrom(address,address,uint256)"), 163 | convert(_sender, bytes32), 164 | convert(self, bytes32), 165 | convert(_amount, bytes32), 166 | ), 167 | max_outsize=32, 168 | ) 169 | if len(response) != 0: 170 | assert convert(response, bool) 171 | 172 | # approve input token 173 | if not self.is_approved[_from][_pool]: 174 | response: Bytes[32] = raw_call( 175 | _from, 176 | concat( 177 | method_id("approve(address,uint256)"), 178 | convert(_pool, bytes32), 179 | convert(MAX_UINT256, bytes32), 180 | ), 181 | max_outsize=32, 182 | ) 183 | if len(response) != 0: 184 | assert convert(response, bool) 185 | self.is_approved[_from][_pool] = True 186 | 187 | # perform coin exchange 188 | if is_underlying: 189 | CurvePool(_pool).exchange_underlying(i, j, _amount, _expected, value=eth_amount) 190 | else: 191 | CurvePool(_pool).exchange(i, j, _amount, _expected, value=eth_amount) 192 | 193 | # perform output transfer 194 | if _to == ETH_ADDRESS: 195 | received_amount = self.balance - initial_balance 196 | raw_call(_receiver, b"", value=received_amount) 197 | else: 198 | received_amount = ERC20(_to).balanceOf(self) - initial_balance 199 | response: Bytes[32] = raw_call( 200 | _to, 201 | concat( 202 | method_id("transfer(address,uint256)"), 203 | convert(_receiver, bytes32), 204 | convert(received_amount, bytes32), 205 | ), 206 | max_outsize=32, 207 | ) 208 | if len(response) != 0: 209 | assert convert(response, bool) 210 | 211 | log TokenExchange(_sender, _receiver, _pool, _from, _to, _amount, received_amount) 212 | 213 | return received_amount 214 | 215 | 216 | @payable 217 | @external 218 | @nonreentrant("lock") 219 | def exchange_with_best_rate( 220 | _from: address, 221 | _to: address, 222 | _amount: uint256, 223 | _expected: uint256, 224 | _receiver: address = msg.sender, 225 | ) -> uint256: 226 | """ 227 | @notice Perform an exchange using the pool that offers the best rate 228 | @dev Prior to calling this function, the caller must approve 229 | this contract to transfer `_amount` coins from `_from` 230 | Does NOT check rates in factory-deployed pools 231 | @param _from Address of coin being sent 232 | @param _to Address of coin being received 233 | @param _amount Quantity of `_from` being sent 234 | @param _expected Minimum quantity of `_from` received 235 | in order for the transaction to succeed 236 | @param _receiver Address to transfer the received tokens to 237 | @return uint256 Amount received 238 | """ 239 | if _from == ETH_ADDRESS: 240 | assert _amount == msg.value, "Incorrect ETH amount" 241 | else: 242 | assert msg.value == 0, "Incorrect ETH amount" 243 | 244 | registry: address = self.registry 245 | best_pool: address = ZERO_ADDRESS 246 | max_dy: uint256 = 0 247 | for i in range(65536): 248 | pool: address = Registry(registry).find_pool_for_coins(_from, _to, i) 249 | if pool == ZERO_ADDRESS: 250 | break 251 | dy: uint256 = self._get_exchange_amount(registry, pool, _from, _to, _amount) 252 | if dy > max_dy: 253 | best_pool = pool 254 | max_dy = dy 255 | 256 | return self._exchange(registry, best_pool, _from, _to, _amount, _expected, msg.sender, _receiver) 257 | 258 | 259 | @payable 260 | @external 261 | @nonreentrant("lock") 262 | def exchange( 263 | _pool: address, 264 | _from: address, 265 | _to: address, 266 | _amount: uint256, 267 | _expected: uint256, 268 | _receiver: address = msg.sender, 269 | ) -> uint256: 270 | """ 271 | @notice Perform an exchange using a specific pool 272 | @dev Prior to calling this function, the caller must approve 273 | this contract to transfer `_amount` coins from `_from` 274 | Works for both regular and factory-deployed pools 275 | @param _pool Address of the pool to use for the swap 276 | @param _from Address of coin being sent 277 | @param _to Address of coin being received 278 | @param _amount Quantity of `_from` being sent 279 | @param _expected Minimum quantity of `_from` received 280 | in order for the transaction to succeed 281 | @param _receiver Address to transfer the received tokens to 282 | @return uint256 Amount received 283 | """ 284 | if _from == ETH_ADDRESS: 285 | assert _amount == msg.value, "Incorrect ETH amount" 286 | else: 287 | assert msg.value == 0, "Incorrect ETH amount" 288 | 289 | return self._exchange(self.registry, _pool, _from, _to, _amount, _expected, msg.sender, _receiver) 290 | 291 | 292 | @view 293 | @external 294 | def get_best_rate( 295 | _from: address, _to: address, _amount: uint256, _exclude_pools: address[8] = EMPTY_POOL_LIST 296 | ) -> (address, uint256): 297 | """ 298 | @notice Find the pool offering the best rate for a given swap. 299 | @dev Checks rates for regular and factory pools 300 | @param _from Address of coin being sent 301 | @param _to Address of coin being received 302 | @param _amount Quantity of `_from` being sent 303 | @param _exclude_pools A list of up to 8 addresses which shouldn't be returned 304 | @return Pool address, amount received 305 | """ 306 | best_pool: address = ZERO_ADDRESS 307 | max_dy: uint256 = 0 308 | registry: address = self.registry 309 | for i in range(65536): 310 | pool: address = Registry(registry).find_pool_for_coins(_from, _to, i) 311 | if pool == ZERO_ADDRESS: 312 | break 313 | elif pool in _exclude_pools: 314 | continue 315 | dy: uint256 = self._get_exchange_amount(registry, pool, _from, _to, _amount) 316 | if dy > max_dy: 317 | best_pool = pool 318 | max_dy = dy 319 | 320 | return best_pool, max_dy 321 | 322 | 323 | @view 324 | @external 325 | def get_exchange_amount(_pool: address, _from: address, _to: address, _amount: uint256) -> uint256: 326 | """ 327 | @notice Get the current number of coins received in an exchange 328 | @dev Works for both regular and factory-deployed pools 329 | @param _pool Pool address 330 | @param _from Address of coin to be sent 331 | @param _to Address of coin to be received 332 | @param _amount Quantity of `_from` to be sent 333 | @return Quantity of `_to` to be received 334 | """ 335 | return self._get_exchange_amount(self.registry, _pool, _from, _to, _amount) 336 | 337 | 338 | @view 339 | @external 340 | def get_input_amount(_pool: address, _from: address, _to: address, _amount: uint256) -> uint256: 341 | """ 342 | @notice Get the current number of coins required to receive the given amount in an exchange 343 | @param _pool Pool address 344 | @param _from Address of coin to be sent 345 | @param _to Address of coin to be received 346 | @param _amount Quantity of `_to` to be received 347 | @return Quantity of `_from` to be sent 348 | """ 349 | registry: address = self.registry 350 | 351 | i: int128 = 0 352 | j: int128 = 0 353 | is_underlying: bool = False 354 | i, j, is_underlying = Registry(registry).get_coin_indices(_pool, _from, _to) 355 | amp: uint256 = Registry(registry).get_A(_pool) 356 | fee: uint256 = Registry(registry).get_fees(_pool)[0] 357 | 358 | balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) 359 | rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) 360 | decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) 361 | n_coins: uint256 = Registry(registry).get_n_coins(_pool)[convert(is_underlying, uint256)] 362 | if is_underlying: 363 | balances = Registry(registry).get_underlying_balances(_pool) 364 | decimals = Registry(registry).get_underlying_decimals(_pool) 365 | for x in range(MAX_COINS): 366 | if x == n_coins: 367 | break 368 | rates[x] = 10**18 369 | else: 370 | balances = Registry(registry).get_balances(_pool) 371 | decimals = Registry(registry).get_decimals(_pool) 372 | rates = Registry(registry).get_rates(_pool) 373 | 374 | for x in range(MAX_COINS): 375 | if x == n_coins: 376 | break 377 | decimals[x] = 10 ** (18 - decimals[x]) 378 | 379 | calculator: address = self.pool_calculator[_pool] 380 | if calculator == ZERO_ADDRESS: 381 | calculator = self.default_calculator 382 | return Calculator(calculator).get_dx(n_coins, balances, amp, fee, rates, decimals, i, j, _amount) 383 | 384 | 385 | @view 386 | @external 387 | def get_exchange_amounts( 388 | _pool: address, 389 | _from: address, 390 | _to: address, 391 | _amounts: uint256[CALC_INPUT_SIZE] 392 | ) -> uint256[CALC_INPUT_SIZE]: 393 | """ 394 | @notice Get the current number of coins required to receive the given amount in an exchange 395 | @param _pool Pool address 396 | @param _from Address of coin to be sent 397 | @param _to Address of coin to be received 398 | @param _amounts Quantity of `_to` to be received 399 | @return Quantity of `_from` to be sent 400 | """ 401 | registry: address = self.registry 402 | 403 | i: int128 = 0 404 | j: int128 = 0 405 | is_underlying: bool = False 406 | balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) 407 | rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) 408 | decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) 409 | 410 | amp: uint256 = Registry(registry).get_A(_pool) 411 | fee: uint256 = Registry(registry).get_fees(_pool)[0] 412 | i, j, is_underlying = Registry(registry).get_coin_indices(_pool, _from, _to) 413 | n_coins: uint256 = Registry(registry).get_n_coins(_pool)[convert(is_underlying, uint256)] 414 | 415 | if is_underlying: 416 | balances = Registry(registry).get_underlying_balances(_pool) 417 | decimals = Registry(registry).get_underlying_decimals(_pool) 418 | for x in range(MAX_COINS): 419 | if x == n_coins: 420 | break 421 | rates[x] = 10**18 422 | else: 423 | balances = Registry(registry).get_balances(_pool) 424 | decimals = Registry(registry).get_decimals(_pool) 425 | rates = Registry(registry).get_rates(_pool) 426 | 427 | for x in range(MAX_COINS): 428 | if x == n_coins: 429 | break 430 | decimals[x] = 10 ** (18 - decimals[x]) 431 | 432 | calculator: address = self.pool_calculator[_pool] 433 | if calculator == ZERO_ADDRESS: 434 | calculator = self.default_calculator 435 | return Calculator(calculator).get_dy(n_coins, balances, amp, fee, rates, decimals, i, j, _amounts) 436 | 437 | 438 | @view 439 | @external 440 | def get_calculator(_pool: address) -> address: 441 | """ 442 | @notice Set calculator contract 443 | @dev Used to calculate `get_dy` for a pool 444 | @param _pool Pool address 445 | @return `CurveCalc` address 446 | """ 447 | calculator: address = self.pool_calculator[_pool] 448 | if calculator == ZERO_ADDRESS: 449 | return self.default_calculator 450 | else: 451 | return calculator 452 | 453 | 454 | @external 455 | def update_registry_address() -> bool: 456 | """ 457 | @notice Update registry address 458 | @dev The registry address is kept in storage to reduce gas costs. 459 | If a new registry is deployed this function should be called 460 | to update the local address from the address provider. 461 | @return bool success 462 | """ 463 | address_provider: address = self.address_provider.address 464 | self.registry = AddressProvider(address_provider).get_registry() 465 | 466 | return True 467 | 468 | 469 | @external 470 | def set_calculator(_pool: address, _calculator: address) -> bool: 471 | """ 472 | @notice Set calculator contract 473 | @dev Used to calculate `get_dy` for a pool 474 | @param _pool Pool address 475 | @param _calculator `CurveCalc` address 476 | @return bool success 477 | """ 478 | assert msg.sender == self.address_provider.admin() # dev: admin-only function 479 | 480 | self.pool_calculator[_pool] = _calculator 481 | 482 | return True 483 | 484 | 485 | @external 486 | def set_default_calculator(_calculator: address) -> bool: 487 | """ 488 | @notice Set default calculator contract 489 | @dev Used to calculate `get_dy` for a pool 490 | @param _calculator `CurveCalc` address 491 | @return bool success 492 | """ 493 | assert msg.sender == self.address_provider.admin() # dev: admin-only function 494 | 495 | self.default_calculator = _calculator 496 | 497 | return True 498 | 499 | 500 | @external 501 | def claim_balance(_token: address) -> bool: 502 | """ 503 | @notice Transfer an ERC20 or ETH balance held by this contract 504 | @dev The entire balance is transferred to the owner 505 | @param _token Token address 506 | @return bool success 507 | """ 508 | assert msg.sender == self.address_provider.admin() # dev: admin-only function 509 | 510 | if _token == ETH_ADDRESS: 511 | raw_call(msg.sender, b"", value=self.balance) 512 | else: 513 | amount: uint256 = ERC20(_token).balanceOf(self) 514 | response: Bytes[32] = raw_call( 515 | _token, 516 | concat( 517 | method_id("transfer(address,uint256)"), 518 | convert(msg.sender, bytes32), 519 | convert(amount, bytes32), 520 | ), 521 | max_outsize=32, 522 | ) 523 | if len(response) != 0: 524 | assert convert(response, bool) 525 | 526 | return True 527 | 528 | 529 | @external 530 | def set_killed(_is_killed: bool) -> bool: 531 | """ 532 | @notice Kill or unkill the contract 533 | @param _is_killed Killed status of the contract 534 | @return bool success 535 | """ 536 | assert msg.sender == self.address_provider.admin() # dev: admin-only function 537 | self.is_killed = _is_killed 538 | 539 | return True 540 | -------------------------------------------------------------------------------- /interfaces/AToken.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"contract ILendingPool","name":"pool","type":"address"},{"internalType":"address","name":"underlyingAssetAddress","type":"address"},{"internalType":"address","name":"reserveTreasuryAddress","type":"address"},{"internalType":"string","name":"tokenName","type":"string"},{"internalType":"string","name":"tokenSymbol","type":"string"},{"internalType":"address","name":"incentivesController","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"BalanceTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"ATOKEN_REVISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EIP712_REVISION","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"POOL","outputs":[{"internalType":"contract ILendingPool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESERVE_TREASURY_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UINT_MAX_VALUE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNDERLYING_ASSET_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"_nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"receiverOfUnderlying","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getScaledUserBalanceAndSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"underlyingAssetDecimals","type":"uint8"},{"internalType":"string","name":"tokenName","type":"string"},{"internalType":"string","name":"tokenSymbol","type":"string"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"mintToTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"scaledBalanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"scaledTotalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferOnLiquidation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferUnderlyingTo","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/AaveLendingPool.json: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reserve","type":"address"},{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"onBehalfOf","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"borrowRateMode","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"borrowRate","type":"uint256"},{"indexed":true,"internalType":"uint16","name":"referral","type":"uint16"}],"name":"Borrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reserve","type":"address"},{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"onBehalfOf","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":true,"internalType":"uint16","name":"referral","type":"uint16"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":true,"internalType":"address","name":"initiator","type":"address"},{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"premium","type":"uint256"},{"indexed":false,"internalType":"uint16","name":"referralCode","type":"uint16"}],"name":"FlashLoan","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collateralAsset","type":"address"},{"indexed":true,"internalType":"address","name":"debtAsset","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"debtToCover","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidatedCollateralAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"liquidator","type":"address"},{"indexed":false,"internalType":"bool","name":"receiveAToken","type":"bool"}],"name":"LiquidationCall","type":"event"},{"anonymous":false,"inputs":[],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reserve","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"}],"name":"RebalanceStableBorrowRate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reserve","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"repayer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Repay","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reserve","type":"address"},{"indexed":false,"internalType":"uint256","name":"liquidityRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stableBorrowRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"variableBorrowRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidityIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"variableBorrowIndex","type":"uint256"}],"name":"ReserveDataUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reserve","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"}],"name":"ReserveUsedAsCollateralDisabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reserve","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"}],"name":"ReserveUsedAsCollateralEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reserve","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"rateMode","type":"uint256"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reserve","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"FLASHLOAN_PREMIUM_TOTAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LENDINGPOOL_REVISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_NUMBER_RESERVES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STABLE_RATE_BORROW_SIZE_PERCENT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"interestRateMode","type":"uint256"},{"internalType":"uint16","name":"referralCode","type":"uint16"},{"internalType":"address","name":"onBehalfOf","type":"address"}],"name":"borrow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"onBehalfOf","type":"address"},{"internalType":"uint16","name":"referralCode","type":"uint16"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"balanceFromBefore","type":"uint256"},{"internalType":"uint256","name":"balanceToBefore","type":"uint256"}],"name":"finalizeTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiverAddress","type":"address"},{"internalType":"address[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256[]","name":"modes","type":"uint256[]"},{"internalType":"address","name":"onBehalfOf","type":"address"},{"internalType":"bytes","name":"params","type":"bytes"},{"internalType":"uint16","name":"referralCode","type":"uint16"}],"name":"flashLoan","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getAddressesProvider","outputs":[{"internalType":"contract ILendingPoolAddressesProvider","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getConfiguration","outputs":[{"components":[{"internalType":"uint256","name":"data","type":"uint256"}],"internalType":"struct DataTypes.ReserveConfigurationMap","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getReserveData","outputs":[{"components":[{"components":[{"internalType":"uint256","name":"data","type":"uint256"}],"internalType":"struct DataTypes.ReserveConfigurationMap","name":"configuration","type":"tuple"},{"internalType":"uint128","name":"liquidityIndex","type":"uint128"},{"internalType":"uint128","name":"variableBorrowIndex","type":"uint128"},{"internalType":"uint128","name":"currentLiquidityRate","type":"uint128"},{"internalType":"uint128","name":"currentVariableBorrowRate","type":"uint128"},{"internalType":"uint128","name":"currentStableBorrowRate","type":"uint128"},{"internalType":"uint40","name":"lastUpdateTimestamp","type":"uint40"},{"internalType":"address","name":"aTokenAddress","type":"address"},{"internalType":"address","name":"stableDebtTokenAddress","type":"address"},{"internalType":"address","name":"variableDebtTokenAddress","type":"address"},{"internalType":"address","name":"interestRateStrategyAddress","type":"address"},{"internalType":"uint8","name":"id","type":"uint8"}],"internalType":"struct DataTypes.ReserveData","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getReserveNormalizedIncome","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getReserveNormalizedVariableDebt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReservesList","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserAccountData","outputs":[{"internalType":"uint256","name":"totalCollateralETH","type":"uint256"},{"internalType":"uint256","name":"totalDebtETH","type":"uint256"},{"internalType":"uint256","name":"availableBorrowsETH","type":"uint256"},{"internalType":"uint256","name":"currentLiquidationThreshold","type":"uint256"},{"internalType":"uint256","name":"ltv","type":"uint256"},{"internalType":"uint256","name":"healthFactor","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserConfiguration","outputs":[{"components":[{"internalType":"uint256","name":"data","type":"uint256"}],"internalType":"struct DataTypes.UserConfigurationMap","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"aTokenAddress","type":"address"},{"internalType":"address","name":"stableDebtAddress","type":"address"},{"internalType":"address","name":"variableDebtAddress","type":"address"},{"internalType":"address","name":"interestRateStrategyAddress","type":"address"}],"name":"initReserve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ILendingPoolAddressesProvider","name":"provider","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collateralAsset","type":"address"},{"internalType":"address","name":"debtAsset","type":"address"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"debtToCover","type":"uint256"},{"internalType":"bool","name":"receiveAToken","type":"bool"}],"name":"liquidationCall","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"rebalanceStableBorrowRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"rateMode","type":"uint256"},{"internalType":"address","name":"onBehalfOf","type":"address"}],"name":"repay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"configuration","type":"uint256"}],"name":"setConfiguration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"val","type":"bool"}],"name":"setPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"rateStrategyAddress","type":"address"}],"name":"setReserveInterestRateStrategyAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"bool","name":"useAsCollateral","type":"bool"}],"name":"setUserUseReserveAsCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"rateMode","type":"uint256"}],"name":"swapBorrowRateMode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/BridgeToken.json: -------------------------------------------------------------------------------- 1 | [{"type":"event","name":"Approval","inputs":[{"type":"address","name":"owner","internalType":"address","indexed":true},{"type":"address","name":"spender","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"AuthorizationCanceled","inputs":[{"type":"address","name":"authorizer","internalType":"address","indexed":true},{"type":"bytes32","name":"nonce","internalType":"bytes32","indexed":true}],"anonymous":false},{"type":"event","name":"AuthorizationUsed","inputs":[{"type":"address","name":"authorizer","internalType":"address","indexed":true},{"type":"bytes32","name":"nonce","internalType":"bytes32","indexed":true}],"anonymous":false},{"type":"event","name":"Blacklisted","inputs":[{"type":"address","name":"account","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"MetaTransactionExecuted","inputs":[{"type":"address","name":"userAddress","internalType":"address","indexed":false},{"type":"address","name":"relayerAddress","internalType":"address payable","indexed":false},{"type":"bytes","name":"functionSignature","internalType":"bytes","indexed":false}],"anonymous":false},{"type":"event","name":"Pause","inputs":[],"anonymous":false},{"type":"event","name":"RescuerChanged","inputs":[{"type":"address","name":"newRescuer","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"RoleAdminChanged","inputs":[{"type":"bytes32","name":"role","internalType":"bytes32","indexed":true},{"type":"bytes32","name":"previousAdminRole","internalType":"bytes32","indexed":true},{"type":"bytes32","name":"newAdminRole","internalType":"bytes32","indexed":true}],"anonymous":false},{"type":"event","name":"RoleGranted","inputs":[{"type":"bytes32","name":"role","internalType":"bytes32","indexed":true},{"type":"address","name":"account","internalType":"address","indexed":true},{"type":"address","name":"sender","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"RoleRevoked","inputs":[{"type":"bytes32","name":"role","internalType":"bytes32","indexed":true},{"type":"address","name":"account","internalType":"address","indexed":true},{"type":"address","name":"sender","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","internalType":"address","indexed":true},{"type":"address","name":"to","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"UnBlacklisted","inputs":[{"type":"address","name":"account","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"Unpause","inputs":[],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"APPROVE_WITH_AUTHORIZATION_TYPEHASH","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"BLACKLISTER_ROLE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"CANCEL_AUTHORIZATION_TYPEHASH","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"DECREASE_ALLOWANCE_WITH_AUTHORIZATION_TYPEHASH","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"DEFAULT_ADMIN_ROLE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"DEPOSITOR_ROLE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"DOMAIN_SEPARATOR","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"EIP712_VERSION","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"INCREASE_ALLOWANCE_WITH_AUTHORIZATION_TYPEHASH","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"META_TRANSACTION_TYPEHASH","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"PAUSER_ROLE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"PERMIT_TYPEHASH","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"RESCUER_ROLE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"TRANSFER_WITH_AUTHORIZATION_TYPEHASH","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"WITHDRAW_WITH_AUTHORIZATION_TYPEHASH","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"allowance","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"approve","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"approveWithAuthorization","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"},{"type":"uint256","name":"validAfter","internalType":"uint256"},{"type":"uint256","name":"validBefore","internalType":"uint256"},{"type":"bytes32","name":"nonce","internalType":"bytes32"},{"type":"uint8","name":"v","internalType":"uint8"},{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"enum GasAbstraction.AuthorizationState"}],"name":"authorizationState","inputs":[{"type":"address","name":"authorizer","internalType":"address"},{"type":"bytes32","name":"nonce","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"balanceOf","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"blacklist","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address[]","name":"","internalType":"address[]"}],"name":"blacklisters","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"cancelAuthorization","inputs":[{"type":"address","name":"authorizer","internalType":"address"},{"type":"bytes32","name":"nonce","internalType":"bytes32"},{"type":"uint8","name":"v","internalType":"uint8"},{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"uint8"}],"name":"decimals","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"decreaseAllowance","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"subtractedValue","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"decreaseAllowanceWithAuthorization","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"decrement","internalType":"uint256"},{"type":"uint256","name":"validAfter","internalType":"uint256"},{"type":"uint256","name":"validBefore","internalType":"uint256"},{"type":"bytes32","name":"nonce","internalType":"bytes32"},{"type":"uint8","name":"v","internalType":"uint8"},{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"deposit","inputs":[{"type":"address","name":"user","internalType":"address"},{"type":"bytes","name":"depositData","internalType":"bytes"}]},{"type":"function","stateMutability":"payable","outputs":[{"type":"bytes","name":"","internalType":"bytes"}],"name":"executeMetaTransaction","inputs":[{"type":"address","name":"userAddress","internalType":"address"},{"type":"bytes","name":"functionSignature","internalType":"bytes"},{"type":"bytes32","name":"sigR","internalType":"bytes32"},{"type":"bytes32","name":"sigS","internalType":"bytes32"},{"type":"uint8","name":"sigV","internalType":"uint8"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"getRoleAdmin","inputs":[{"type":"bytes32","name":"role","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"getRoleMember","inputs":[{"type":"bytes32","name":"role","internalType":"bytes32"},{"type":"uint256","name":"index","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getRoleMemberCount","inputs":[{"type":"bytes32","name":"role","internalType":"bytes32"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"grantRole","inputs":[{"type":"bytes32","name":"role","internalType":"bytes32"},{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"hasRole","inputs":[{"type":"bytes32","name":"role","internalType":"bytes32"},{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"increaseAllowance","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"addedValue","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"increaseAllowanceWithAuthorization","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"increment","internalType":"uint256"},{"type":"uint256","name":"validAfter","internalType":"uint256"},{"type":"uint256","name":"validBefore","internalType":"uint256"},{"type":"bytes32","name":"nonce","internalType":"bytes32"},{"type":"uint8","name":"v","internalType":"uint8"},{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"initialize","inputs":[{"type":"string","name":"newName","internalType":"string"},{"type":"string","name":"newSymbol","internalType":"string"},{"type":"uint8","name":"newDecimals","internalType":"uint8"},{"type":"address","name":"childChainManager","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"initialized","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"isBlacklisted","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"name","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"nonces","inputs":[{"type":"address","name":"owner","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"pause","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"paused","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address[]","name":"","internalType":"address[]"}],"name":"pausers","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"permit","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"},{"type":"uint256","name":"deadline","internalType":"uint256"},{"type":"uint8","name":"v","internalType":"uint8"},{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"renounceRole","inputs":[{"type":"bytes32","name":"role","internalType":"bytes32"},{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"rescueERC20","inputs":[{"type":"address","name":"tokenContract","internalType":"contract IERC20"},{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address[]","name":"","internalType":"address[]"}],"name":"rescuers","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"revokeRole","inputs":[{"type":"bytes32","name":"role","internalType":"bytes32"},{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"symbol","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalSupply","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transfer","inputs":[{"type":"address","name":"recipient","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transferFrom","inputs":[{"type":"address","name":"sender","internalType":"address"},{"type":"address","name":"recipient","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferWithAuthorization","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"},{"type":"uint256","name":"validAfter","internalType":"uint256"},{"type":"uint256","name":"validBefore","internalType":"uint256"},{"type":"bytes32","name":"nonce","internalType":"bytes32"},{"type":"uint8","name":"v","internalType":"uint8"},{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"unBlacklist","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"unpause","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateMetadata","inputs":[{"type":"string","name":"newName","internalType":"string"},{"type":"string","name":"newSymbol","internalType":"string"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"withdraw","inputs":[{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"withdrawWithAuthorization","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"},{"type":"uint256","name":"validAfter","internalType":"uint256"},{"type":"uint256","name":"validBefore","internalType":"uint256"},{"type":"bytes32","name":"nonce","internalType":"bytes32"},{"type":"uint8","name":"v","internalType":"uint8"},{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"}]}] 2 | -------------------------------------------------------------------------------- /interfaces/ChildERC20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "uint256", 6 | "name": "amount", 7 | "type": "uint256" 8 | } 9 | ], 10 | "name": "withdraw", 11 | "outputs": [], 12 | "stateMutability": "nonpayable", 13 | "type": "function" 14 | } 15 | ] -------------------------------------------------------------------------------- /interfaces/ProxyAdmin.json: -------------------------------------------------------------------------------- 1 | [{"anonymous": false, "inputs": [{"indexed": true, "name": "admin", "type": "address"}, {"indexed": true, "name": "target", "type": "address"}, {"indexed": false, "name": "calldata", "type": "bytes"}, {"indexed": false, "name": "value", "type": "uint256"}], "name": "TransactionExecuted", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "name": "current_admin", "type": "address"}, {"indexed": false, "name": "future_admin", "type": "address"}], "name": "RequestAdminChange", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "name": "current_admin", "type": "address"}, {"indexed": false, "name": "future_admin", "type": "address"}, {"indexed": false, "name": "calling_admin", "type": "address"}], "name": "RevokeAdminChange", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "name": "current_admin", "type": "address"}, {"indexed": false, "name": "future_admin", "type": "address"}, {"indexed": false, "name": "calling_admin", "type": "address"}], "name": "ApproveAdminChange", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "name": "previous_admin", "type": "address"}, {"indexed": false, "name": "current_admin", "type": "address"}], "name": "AcceptAdminChange", "type": "event"}, {"inputs": [{"name": "_authorized", "type": "address[2]"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"gas": 1168658, "inputs": [{"name": "_target", "type": "address"}, {"name": "_calldata", "type": "bytes"}], "name": "execute", "outputs": [], "stateMutability": "payable", "type": "function"}, {"gas": 4202, "inputs": [], "name": "get_admin_change_status", "outputs": [{"name": "", "type": "address"}, {"name": "", "type": "address"}, {"name": "", "type": "bool"}], "stateMutability": "view", "type": "function"}, {"gas": 148342, "inputs": [{"name": "_new_admin", "type": "address"}], "name": "request_admin_change", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 41716, "inputs": [], "name": "approve_admin_change", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 67885, "inputs": [], "name": "revoke_admin_change", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 101134, "inputs": [], "name": "accept_admin_change", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 1377, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "admins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] 2 | -------------------------------------------------------------------------------- /interfaces/RootChain.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "currentHeaderBlock", 6 | "outputs": [ 7 | { 8 | "internalType": "uint256", 9 | "name": "", 10 | "type": "uint256" 11 | } 12 | ], 13 | "payable": false, 14 | "stateMutability": "view", 15 | "type": "function" 16 | }, 17 | { 18 | "constant": true, 19 | "inputs": [], 20 | "name": "getLastChildBlock", 21 | "outputs": [ 22 | { 23 | "internalType": "uint256", 24 | "name": "", 25 | "type": "uint256" 26 | } 27 | ], 28 | "payable": false, 29 | "stateMutability": "view", 30 | "type": "function" 31 | }, 32 | { 33 | "constant": true, 34 | "inputs": [ 35 | { 36 | "internalType": "uint256", 37 | "name": "_arg0", 38 | "type": "uint256" 39 | } 40 | ], 41 | "name": "headerBlocks", 42 | "outputs": [ 43 | { 44 | "components": [ 45 | { 46 | "internalType": "bytes32", 47 | "name": "root", 48 | "type": "bytes32" 49 | }, 50 | { 51 | "internalType": "uint256", 52 | "name": "start", 53 | "type": "uint256" 54 | }, 55 | { 56 | "internalType": "uint256", 57 | "name": "end", 58 | "type": "uint256" 59 | }, 60 | { 61 | "internalType": "uint256", 62 | "name": "createdAt", 63 | "type": "uint256" 64 | }, 65 | { 66 | "internalType": "address", 67 | "name": "proposer", 68 | "type": "address" 69 | } 70 | ], 71 | "internalType": "struct IRootChain.Tuple1", 72 | "name": "", 73 | "type": "tuple" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "view", 78 | "type": "function" 79 | } 80 | ] -------------------------------------------------------------------------------- /interfaces/RootChainManager.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "bytes", 6 | "name": "inputData", 7 | "type": "bytes" 8 | } 9 | ], 10 | "name": "exit", 11 | "outputs": [], 12 | "stateMutability": "nonpayable", 13 | "type": "function" 14 | } 15 | ] -------------------------------------------------------------------------------- /interfaces/WMatic.json: -------------------------------------------------------------------------------- 1 | [{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"string","name":""}],"name":"name","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":""}],"name":"approve","inputs":[{"type":"address","name":"guy"},{"type":"uint256","name":"wad"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"totalSupply","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":""}],"name":"transferFrom","inputs":[{"type":"address","name":"src"},{"type":"address","name":"dst"},{"type":"uint256","name":"wad"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"withdraw","inputs":[{"type":"uint256","name":"wad"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint8","name":""}],"name":"decimals","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"balanceOf","inputs":[{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"string","name":""}],"name":"symbol","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"bool","name":""}],"name":"transfer","inputs":[{"type":"address","name":"dst"},{"type":"uint256","name":"wad"}],"constant":false},{"type":"function","stateMutability":"payable","payable":true,"outputs":[],"name":"deposit","inputs":[],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"allowance","inputs":[{"type":"address","name":""},{"type":"address","name":""}],"constant":true},{"type":"fallback","stateMutability":"payable","payable":true},{"type":"event","name":"Approval","inputs":[{"type":"address","name":"src","indexed":true},{"type":"address","name":"guy","indexed":true},{"type":"uint256","name":"wad","indexed":false}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"src","indexed":true},{"type":"address","name":"dst","indexed":true},{"type":"uint256","name":"wad","indexed":false}],"anonymous":false},{"type":"event","name":"Deposit","inputs":[{"type":"address","name":"dst","indexed":true},{"type":"uint256","name":"wad","indexed":false}],"anonymous":false},{"type":"event","name":"Withdrawal","inputs":[{"type":"address","name":"src","indexed":true},{"type":"uint256","name":"wad","indexed":false}],"anonymous":false}] 2 | -------------------------------------------------------------------------------- /interfaces/renERC20.json: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_rate","type":"uint256"}],"name":"LogRateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_rateScale","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"balanceOfUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"blacklistRecoverableToken","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"claimOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"exchangeRateCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"_amountUnderlying","type":"uint256"}],"name":"fromUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_chainId","type":"uint256"},{"internalType":"address","name":"_nextOwner","type":"address"},{"internalType":"uint256","name":"_initialRate","type":"uint256"},{"internalType":"string","name":"_version","type":"string"},{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_chainId","type":"uint256"},{"internalType":"string","name":"_version","type":"string"},{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_nextOwner","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_nextOwner","type":"address"},{"internalType":"uint256","name":"_initialRate","type":"uint256"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"bool","name":"allowed","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"recoverTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_nextRate","type":"uint256"}],"name":"setExchangeRate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"toUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /network-config.yaml: -------------------------------------------------------------------------------- 1 | development: 2 | - name: Ganache-CLI (Polygon-Mainnet Fork) 3 | id: polygon-main-fork 4 | cmd: ganache-cli 5 | host: http://127.0.0.1 6 | timeout: 120 7 | cmd_settings: 8 | port: 8545 9 | gas_limit: 20000000 10 | accounts: 10 11 | evm_version: istanbul 12 | mnemonic: brownie 13 | fork: polygon-main 14 | 15 | live: 16 | - name: Polygon 17 | networks: 18 | - name: Mainnet 19 | chainid: 137 20 | id: polygon-main 21 | host: https://polygon-mainnet.infura.io/v3/21317ddb5ded42ce8d40c7d78f90474f 22 | explorer: https://api.polygonscan.com/api 23 | - name: Mumbai Testnet 24 | chainid: 80001 25 | id: polygon-testnet 26 | host: https://polygon-mumbai.infura.io/v3/21317ddb5ded42ce8d40c7d78f90474f 27 | explorer: https://explorer-mumbai.maticvigil.com/ 28 | -------------------------------------------------------------------------------- /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==20.8b1 2 | brownie-token-tester<=1.0.0 3 | eth-brownie>=1.14.5,<2.0.0 4 | flake8==3.8.4 5 | isort==5.7.0 6 | pip-tools<7.0.0 7 | pre-commit<3.0.0 8 | rlp==1.2.0 9 | trie==1.4.0 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile requirements.in 6 | # 7 | apipkg==1.5 8 | # via 9 | # eth-brownie 10 | # execnet 11 | appdirs==1.4.4 12 | # via 13 | # black 14 | # eth-brownie 15 | # virtualenv 16 | asttokens==2.0.4 17 | # via 18 | # eth-brownie 19 | # vyper 20 | attrs==20.3.0 21 | # via 22 | # eth-brownie 23 | # hypothesis 24 | # jsonschema 25 | # pytest 26 | base58==2.1.0 27 | # via 28 | # eth-brownie 29 | # multiaddr 30 | bitarray==1.2.2 31 | # via 32 | # eth-account 33 | # eth-brownie 34 | black==20.8b1 35 | # via 36 | # -r requirements.in 37 | # eth-brownie 38 | brownie-token-tester==0.2.0 39 | # via -r requirements.in 40 | certifi==2020.12.5 41 | # via 42 | # eth-brownie 43 | # requests 44 | cfgv==3.2.0 45 | # via pre-commit 46 | chardet==4.0.0 47 | # via 48 | # eth-brownie 49 | # requests 50 | click==7.1.2 51 | # via 52 | # black 53 | # eth-brownie 54 | # pip-tools 55 | cytoolz==0.11.0 56 | # via 57 | # eth-brownie 58 | # eth-keyfile 59 | # eth-utils 60 | distlib==0.3.1 61 | # via virtualenv 62 | eth-abi==2.1.1 63 | # via 64 | # eth-account 65 | # eth-brownie 66 | # eth-event 67 | # web3 68 | eth-account==0.5.4 69 | # via 70 | # eth-brownie 71 | # web3 72 | eth-brownie==1.14.6 73 | # via 74 | # -r requirements.in 75 | # brownie-token-tester 76 | eth-event==1.2.3 77 | # via eth-brownie 78 | eth-hash[pycryptodome]==0.3.1 79 | # via 80 | # eth-brownie 81 | # eth-event 82 | # eth-utils 83 | # trie 84 | # web3 85 | eth-keyfile==0.5.1 86 | # via 87 | # eth-account 88 | # eth-brownie 89 | eth-keys==0.3.3 90 | # via 91 | # eth-account 92 | # eth-brownie 93 | # eth-keyfile 94 | eth-rlp==0.2.1 95 | # via 96 | # eth-account 97 | # eth-brownie 98 | eth-typing==2.2.2 99 | # via 100 | # eth-abi 101 | # eth-brownie 102 | # eth-keys 103 | # eth-utils 104 | # web3 105 | eth-utils==1.10.0 106 | # via 107 | # eth-abi 108 | # eth-account 109 | # eth-brownie 110 | # eth-event 111 | # eth-hash 112 | # eth-keyfile 113 | # eth-keys 114 | # eth-rlp 115 | # rlp 116 | # trie 117 | # web3 118 | execnet==1.8.0 119 | # via 120 | # eth-brownie 121 | # pytest-xdist 122 | filelock==3.0.12 123 | # via virtualenv 124 | flake8==3.8.4 125 | # via -r requirements.in 126 | hexbytes==0.2.1 127 | # via 128 | # eth-account 129 | # eth-brownie 130 | # eth-event 131 | # eth-rlp 132 | # web3 133 | hypothesis==6.10.0 134 | # via eth-brownie 135 | identify==2.2.4 136 | # via pre-commit 137 | idna==2.10 138 | # via 139 | # eth-brownie 140 | # requests 141 | inflection==0.5.0 142 | # via 143 | # eth-brownie 144 | # mythx-models 145 | # pythx 146 | iniconfig==1.1.1 147 | # via 148 | # eth-brownie 149 | # pytest 150 | ipfshttpclient==0.7.0a1 151 | # via 152 | # eth-brownie 153 | # web3 154 | isort==5.7.0 155 | # via -r requirements.in 156 | jsonschema==3.2.0 157 | # via 158 | # eth-brownie 159 | # mythx-models 160 | # web3 161 | lru-dict==1.1.7 162 | # via 163 | # eth-brownie 164 | # web3 165 | mccabe==0.6.1 166 | # via flake8 167 | multiaddr==0.0.9 168 | # via 169 | # eth-brownie 170 | # ipfshttpclient 171 | mypy-extensions==0.4.3 172 | # via 173 | # black 174 | # eth-brownie 175 | mythx-models==1.9.1 176 | # via 177 | # eth-brownie 178 | # pythx 179 | netaddr==0.8.0 180 | # via 181 | # eth-brownie 182 | # multiaddr 183 | nodeenv==1.6.0 184 | # via pre-commit 185 | packaging==20.9 186 | # via 187 | # eth-brownie 188 | # pytest 189 | parsimonious==0.8.1 190 | # via 191 | # eth-abi 192 | # eth-brownie 193 | pathspec==0.8.1 194 | # via 195 | # black 196 | # eth-brownie 197 | pep517==0.10.0 198 | # via pip-tools 199 | pip-tools==6.1.0 200 | # via -r requirements.in 201 | pluggy==0.13.1 202 | # via 203 | # eth-brownie 204 | # pytest 205 | pre-commit==2.12.1 206 | # via -r requirements.in 207 | prompt-toolkit==3.0.18 208 | # via eth-brownie 209 | protobuf==3.15.8 210 | # via 211 | # eth-brownie 212 | # web3 213 | psutil==5.8.0 214 | # via eth-brownie 215 | py-solc-ast==1.2.8 216 | # via eth-brownie 217 | py-solc-x==1.1.0 218 | # via eth-brownie 219 | py==1.10.0 220 | # via 221 | # eth-brownie 222 | # pytest 223 | # pytest-forked 224 | pycodestyle==2.6.0 225 | # via flake8 226 | pycryptodome==3.10.1 227 | # via 228 | # eth-brownie 229 | # eth-hash 230 | # eth-keyfile 231 | # vyper 232 | pyflakes==2.2.0 233 | # via flake8 234 | pygments-lexer-solidity==0.7.0 235 | # via eth-brownie 236 | pygments==2.8.1 237 | # via 238 | # eth-brownie 239 | # pygments-lexer-solidity 240 | pyjwt==1.7.1 241 | # via 242 | # eth-brownie 243 | # pythx 244 | pyparsing==2.4.7 245 | # via 246 | # eth-brownie 247 | # packaging 248 | pyrsistent==0.17.3 249 | # via 250 | # eth-brownie 251 | # jsonschema 252 | pytest-forked==1.3.0 253 | # via 254 | # eth-brownie 255 | # pytest-xdist 256 | pytest-xdist==1.34.0 257 | # via eth-brownie 258 | pytest==6.2.3 259 | # via 260 | # eth-brownie 261 | # pytest-forked 262 | # pytest-xdist 263 | python-dateutil==2.8.1 264 | # via 265 | # eth-brownie 266 | # mythx-models 267 | # pythx 268 | python-dotenv==0.16.0 269 | # via eth-brownie 270 | pythx==1.6.1 271 | # via eth-brownie 272 | pyyaml==5.4.1 273 | # via 274 | # eth-brownie 275 | # pre-commit 276 | regex==2021.4.4 277 | # via 278 | # black 279 | # eth-brownie 280 | requests==2.25.1 281 | # via 282 | # eth-brownie 283 | # ipfshttpclient 284 | # py-solc-x 285 | # pythx 286 | # vvm 287 | # web3 288 | rlp==1.2.0 289 | # via 290 | # -r requirements.in 291 | # eth-account 292 | # eth-brownie 293 | # eth-rlp 294 | # trie 295 | semantic-version==2.8.5 296 | # via 297 | # eth-brownie 298 | # py-solc-x 299 | # vvm 300 | # vyper 301 | six==1.15.0 302 | # via 303 | # asttokens 304 | # eth-brownie 305 | # jsonschema 306 | # multiaddr 307 | # parsimonious 308 | # protobuf 309 | # pytest-xdist 310 | # python-dateutil 311 | # virtualenv 312 | sortedcontainers==2.3.0 313 | # via 314 | # eth-brownie 315 | # hypothesis 316 | toml==0.10.2 317 | # via 318 | # black 319 | # eth-brownie 320 | # pep517 321 | # pre-commit 322 | # pytest 323 | toolz==0.11.1 324 | # via 325 | # cytoolz 326 | # eth-brownie 327 | tqdm==4.60.0 328 | # via eth-brownie 329 | trie==1.4.0 330 | # via -r requirements.in 331 | typed-ast==1.4.3 332 | # via 333 | # black 334 | # eth-brownie 335 | typing-extensions==3.7.4.3 336 | # via 337 | # black 338 | # eth-brownie 339 | urllib3==1.26.4 340 | # via 341 | # eth-brownie 342 | # requests 343 | varint==1.0.2 344 | # via 345 | # eth-brownie 346 | # multiaddr 347 | virtualenv==20.4.4 348 | # via pre-commit 349 | vvm==0.1.0 350 | # via eth-brownie 351 | vyper==0.2.12 352 | # via eth-brownie 353 | wcwidth==0.2.5 354 | # via 355 | # eth-brownie 356 | # prompt-toolkit 357 | web3==5.18.0 358 | # via eth-brownie 359 | websockets==8.1 360 | # via 361 | # eth-brownie 362 | # web3 363 | 364 | # The following packages are considered to be unsafe in a requirements file: 365 | # pip 366 | # setuptools 367 | -------------------------------------------------------------------------------- /scripts/burn_fees.py: -------------------------------------------------------------------------------- 1 | from brownie import Contract, accounts, history 2 | 3 | 4 | def main(): 5 | deploy = accounts.load("curve-deploy") 6 | admin = Contract("0x7EAfd3cd937628b9671d5f8B9c823f4AAc914808") 7 | 8 | # withdraw admin fees to the burners 9 | swap = Contract("0x445FE580eF8d70FF569aB36e80c647af338db351") 10 | swap_btc = Contract("0xC2d95EEF97Ec6C17551d45e77B590dc1F9117C67") 11 | admin.execute( 12 | swap, 13 | swap.withdraw_admin_fees.encode_input(), 14 | {"from": deploy, "required_confs": 0}, 15 | ) 16 | admin.execute( 17 | swap_btc, 18 | swap_btc.withdraw_admin_fees.encode_input(), 19 | {"from": deploy, "required_confs": 0}, 20 | ) 21 | history.wait() 22 | 23 | # burn am3CRV fees 24 | burner = Contract("0xA237034249290De2B07988Ac64b96f22c0E76fE0") 25 | for i in range(3): 26 | try: 27 | burner.burn( 28 | swap.coins(i), 29 | {"from": deploy, "gas_limit": 2000000, "required_confs": 0}, 30 | ) 31 | except Exception: 32 | pass 33 | 34 | # burn renBTC fees 35 | burner = Contract("0x5109Abc063164d49C148A7AE970F631FEBbDa4FA") 36 | for i in range(2): 37 | try: 38 | burner.burn( 39 | swap_btc.coins(i), 40 | {"from": deploy, "gas_limit": 2000000, "required_confs": 0}, 41 | ) 42 | except Exception: 43 | pass 44 | 45 | # burn tricrypto3 46 | burner = Contract("0x43450Feccf936FbA3143e03F35D3Cc608D5fE1d2") 47 | burner.burn( 48 | "0xdAD97F7713Ae9437fa9249920eC8507e5FbB23d3", 49 | {'from': deploy, 'gas_limit': 2000000, 'required_confs': 0} 50 | ) 51 | 52 | history.wait() 53 | 54 | # send USDC over the bridge 55 | usdc = Contract("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174") 56 | bridge = Contract("0x4473243A61b5193670D1324872368d015081822f") 57 | amount = usdc.balanceOf(bridge) 58 | tx = admin.execute(bridge, bridge.withdraw.encode_input(usdc), {"from": deploy}) 59 | 60 | print(f"Burning phase 1 complete!\nAmount: {amount/1e6:,.2f} USDC\nBridge txid: {tx.txid}") 61 | print("\nUse `brownie run exit --network mainnet` to claim on ETH once the checkpoint is added") 62 | -------------------------------------------------------------------------------- /scripts/exit.py: -------------------------------------------------------------------------------- 1 | """Gracefully exit from Matic ... now in Python. 2 | 3 | Script for withdrawing an ERC20 asset from Matic Mainnet back to Ethereum. 4 | This is a two part script, the first portion requires you to burn an ERC20 asset 5 | on the Matic Network, and then after the burn is checkpointed on Ethereum mainnet, 6 | collect the ERC20 asset. 7 | 8 | Steps for successful withdrawl: 9 | 10 | 1. Add Matic ERC20 variable data below 11 | - MATIC_ERC20_ASSET_ADDR 12 | - BURN_AMOUNT 13 | 2. Call script as follows: `brownie run exit burn_asset_on_matic` 14 | - Do this in fork mode first ofcourse 15 | 3. Add the Burn TX ID below 16 | - MATIC_BURN_TX_ID 17 | 4. Wait a while up to 30 mins 18 | 5. Call check inclusion function to see if the burn tx has been checkpointed. 19 | `brownie run exit is_burn_checkpointed` 20 | 6. Once the burn tx has been checkpointed call the exit function 21 | `brownie run exit exit` 22 | - Do this in fork mode first ofcourse 23 | 24 | To test run: brownie run exit tester --network mainnet 25 | """ 26 | import math 27 | from datetime import datetime 28 | from functools import wraps 29 | from typing import List, Tuple 30 | 31 | import rlp 32 | from brownie import Contract, RootForwarder, accounts, chain, network, web3 33 | from brownie.project import get_loaded_projects 34 | from eth_utils import keccak 35 | from hexbytes import HexBytes 36 | from tqdm import tqdm, trange 37 | from trie import HexaryTrie 38 | from web3.types import BlockData, TxReceipt 39 | 40 | from scripts.fetch_deployment_data import PROXY_DEPLOYMENT_ADDRS as ADDRS 41 | 42 | PreparedLogs = List[Tuple[bytes, List[bytes], bytes]] 43 | PreparedReceipt = Tuple[bytes, int, bytes, PreparedLogs] 44 | 45 | ADDRS["mainnet-fork"] = ADDRS["mainnet"] 46 | 47 | # MUST SET VARIABLES BEFORE BURNING ON MATIC 48 | MSG_SENDER = accounts.add() 49 | MATIC_ERC20_ASSET_ADDR = "" 50 | BURN_AMOUNT = 0 51 | 52 | # BURN TX HASH 53 | MATIC_BURN_TX_ID = "" 54 | 55 | 56 | def keccak256(value): 57 | """Thin wrapper around keccak function.""" 58 | return HexBytes(keccak(value)) 59 | 60 | 61 | def burn_asset_on_matic(asset=MATIC_ERC20_ASSET_ADDR, amount=BURN_AMOUNT, sender=MSG_SENDER): 62 | """Burn an ERC20 asset on Matic Network""" 63 | abi = get_loaded_projects()[0].interface.ChildERC20.abi 64 | asset = Contract.from_abi("ChildERC20", asset, abi) 65 | 66 | tx = asset.withdraw(amount, {"from": sender}) 67 | print("Burn transaction has been sent.") 68 | print(f"Visit https://explorer-mainnet.maticvigil.com/tx/{tx.txid} for confirmation") 69 | 70 | 71 | def hot_swap_network(environment_name): 72 | """Decorator for hot swapping the network connection. 73 | 74 | There is probably a nicer way to do this but I'm lazy and this works. 75 | This basically hot swaps to the network we want and then back to the 76 | original network, useful for helper functions. 77 | 78 | Since we are swapping between Ethereum and Matic in this script 79 | it's neccessary to terminate the RPC and reconnect since Matic 80 | uses POA ... that sounds about right I really don't know but it works :) 81 | 82 | Args: 83 | environment_name: A valid environment name found in `brownie networks list` 84 | e.g. 'ethereum', 'polygon' 85 | """ 86 | 87 | def inner(func): 88 | @wraps(func) 89 | def wrapper(*args, **kwargs): 90 | initial_network_id = network.show_active() 91 | is_mainnet = "main" in initial_network_id 92 | 93 | if environment_name == "ethereum": 94 | swap_network_id = "mainnet" if is_mainnet else "goerli" 95 | elif environment_name == "polygon": 96 | swap_network_id = "polygon-main" if is_mainnet else "polygon-testnet" 97 | 98 | if initial_network_id == swap_network_id: 99 | return func(*args, **kwargs) 100 | 101 | network.disconnect() 102 | network.connect(swap_network_id) 103 | 104 | result = func(*args, **kwargs) 105 | 106 | network.disconnect() 107 | network.connect(initial_network_id) 108 | 109 | return result 110 | 111 | return wrapper 112 | 113 | return inner 114 | 115 | 116 | class MerkleTree: 117 | """This is my poor but successful lvl 2 copy paste of a merkle tree.""" 118 | 119 | def __init__(self, leaves: bytes): 120 | """Initialize and recursively build the Merkle tree. 121 | 122 | This is only used for building the block proof. 123 | 124 | Args: 125 | leaves: Serialized blocks 126 | """ 127 | assert len(leaves) >= 1, "Atleast 1 leaf is needed" 128 | tree_depth = math.ceil(math.log(len(leaves), 2)) 129 | assert tree_depth <= 20, "Depth must be 20 layers or less" 130 | 131 | self.leaves = leaves + [HexBytes(0) * 32] * (2 ** tree_depth - len(leaves)) 132 | self.layers = [self.leaves] 133 | self.create_hashes(self.leaves) 134 | 135 | @property 136 | def root(self) -> bytes: 137 | """Get the tree root.""" 138 | return self.layers[-1][0] 139 | 140 | def create_hashes(self, nodes: List[bytes]) -> None: 141 | """Recursively build the layers of the tree.""" 142 | if len(nodes) == 1: 143 | return 144 | 145 | tree_level = [] 146 | for i in range(0, len(nodes), 2): 147 | left, right = nodes[i : i + 2] 148 | new_node = keccak256(left + right) 149 | tree_level.append(new_node) 150 | 151 | if len(nodes) % 2 == 1: 152 | tree_level.append(nodes[-1]) 153 | 154 | self.layers.append(tree_level) 155 | self.create_hashes(tree_level) 156 | 157 | def get_proof(self, leaf: bytes) -> List[bytes]: 158 | """Generate a proof for a leaf.""" 159 | index = self.leaves.index(leaf) 160 | 161 | proof = [] 162 | sibling_index = None 163 | for i in trange(len(self.layers) - 1, desc="Building merkle proof"): 164 | if index % 2 == 0: 165 | sibling_index = index + 1 166 | else: 167 | sibling_index = index - 1 168 | index = index // 2 169 | sibling_node = self.layers[i][sibling_index] 170 | proof.append(sibling_node) 171 | 172 | return proof 173 | 174 | 175 | @hot_swap_network("polygon") 176 | def fetch_burn_tx_data(burn_tx_id: str = MATIC_BURN_TX_ID): 177 | """Fetch burn tx data.""" 178 | tx = web3.eth.get_transaction(burn_tx_id) 179 | tx_receipt = web3.eth.get_transaction_receipt(burn_tx_id) 180 | tx_block = chain[tx["blockNumber"]] 181 | 182 | return tx, tx_receipt, tx_block 183 | 184 | 185 | @hot_swap_network("ethereum") 186 | def is_burn_checkpointed(burn_tx_id: str = MATIC_BURN_TX_ID, silent: bool = False) -> bool: 187 | """Check a burn tx has been checkpointed on Ethereum mainnet.""" 188 | _, _, burn_tx_block = fetch_burn_tx_data(burn_tx_id) 189 | root_chain_proxy_addr = ADDRS[network.show_active()]["RootChainProxy"] 190 | abi = get_loaded_projects()[0].interface.RootChain.abi 191 | root_chain = Contract.from_abi("RootChain", root_chain_proxy_addr, abi) 192 | 193 | is_checkpointed = root_chain.getLastChildBlock() >= burn_tx_block["number"] 194 | if not silent: 195 | print(f"Has Burn TX been Checkpointed? {is_checkpointed}") 196 | return is_checkpointed 197 | 198 | 199 | @hot_swap_network("ethereum") 200 | def fetch_block_inclusion_data(child_block_number: int) -> dict: 201 | """Fetch burn tx checkpoint block inclusion data. 202 | 203 | Args: 204 | child_block_number: The block number of the burn tx was included in on 205 | the matic network 206 | """ 207 | CHECKPOINT_ID_INTERVAL = 10000 208 | 209 | root_chain_proxy_addr = ADDRS[network.show_active()]["RootChainProxy"] 210 | abi = get_loaded_projects()[0].interface.RootChain.abi 211 | root_chain = Contract.from_abi("RootChain", root_chain_proxy_addr, abi) 212 | 213 | start = 1 214 | end = root_chain.currentHeaderBlock() // CHECKPOINT_ID_INTERVAL 215 | 216 | header_block_number = None 217 | while start <= end: 218 | if start == end: 219 | header_block_number = start 220 | 221 | middle = (start + end) // 2 222 | header_block = root_chain.headerBlocks(middle * CHECKPOINT_ID_INTERVAL) 223 | header_start = header_block["start"] 224 | header_end = header_block["end"] 225 | 226 | if header_start <= child_block_number <= header_end: 227 | header_block_number = middle 228 | break 229 | elif header_start > child_block_number: 230 | # child block was checkpointed after 231 | end = middle - 1 232 | elif header_end < child_block_number: 233 | # child block was checkpointed before 234 | start = middle + 1 235 | 236 | return header_start, header_end, header_block_number * CHECKPOINT_ID_INTERVAL 237 | 238 | 239 | def prepare_receipt(receipt: TxReceipt) -> PreparedReceipt: 240 | """Prepare a transaction receipt for serialization""" 241 | receipt_root = HexBytes(receipt.get("root", b"")) 242 | receipt_status = receipt.get("status", 1) 243 | 244 | receipt_root_or_status = receipt_root if len(receipt_root) > 0 else receipt_status 245 | receipt_cumulative_gas = receipt["cumulativeGasUsed"] 246 | receipt_logs_bloom = HexBytes(receipt["logsBloom"]) 247 | receipt_logs = [ 248 | ( 249 | HexBytes(log["address"]), 250 | list(map(HexBytes, log["topics"])), 251 | HexBytes(log["data"]), 252 | ) 253 | for log in receipt["logs"] 254 | ] 255 | 256 | return ( 257 | receipt_root_or_status, 258 | receipt_cumulative_gas, 259 | receipt_logs_bloom, 260 | receipt_logs, 261 | ) 262 | 263 | 264 | def serialize_receipt(receipt: TxReceipt) -> bytes: 265 | """Serialize a receipt. 266 | 267 | This also handles EIP-2718 typed transactions. 268 | """ 269 | prepared_receipt = prepare_receipt(receipt) 270 | encoded_receipt = rlp.encode(prepared_receipt) 271 | 272 | receipt_type = HexBytes(receipt.get("type", 0)) 273 | if receipt_type == HexBytes(0): 274 | return encoded_receipt 275 | 276 | buffer = HexBytes(receipt_type) + encoded_receipt 277 | return rlp.encode(buffer) 278 | 279 | 280 | def serialize_block(block: dict) -> bytes: 281 | """Serialize a block.""" 282 | block_number = block["number"].to_bytes(32, "big") 283 | timestamp = block["timestamp"].to_bytes(32, "big") 284 | txs_root = HexBytes(block["transactionsRoot"]) 285 | receipts_root = HexBytes(block["receiptsRoot"]) 286 | return keccak256(block_number + timestamp + txs_root + receipts_root) 287 | 288 | 289 | @hot_swap_network("polygon") 290 | def build_block_proof(block_start: int, block_end: int, burn_tx_block_number: int) -> List[bytes]: 291 | """Build a merkle proof for the burn tx block.""" 292 | 293 | checkpoint_blocks = ( 294 | chain[block_number] 295 | for block_number in trange( 296 | block_start, block_end + 1, desc="Serializing blocks", unit="block" 297 | ) 298 | ) 299 | serialized_blocks = list(map(serialize_block, checkpoint_blocks)) 300 | 301 | burn_tx_serialized_block = serialized_blocks[burn_tx_block_number - block_start] 302 | merkle_tree = MerkleTree(serialized_blocks) 303 | 304 | return merkle_tree.get_proof(burn_tx_serialized_block) 305 | 306 | 307 | @hot_swap_network("polygon") 308 | def build_receipt_proof(burn_tx_receipt: TxReceipt, burn_tx_block: BlockData) -> List[bytes]: 309 | """Build the burn_tx_receipt proof.""" 310 | state_sync_tx_hash = keccak256( 311 | b"matic-bor-receipt-" + burn_tx_block["number"].to_bytes(8, "big") + burn_tx_block["hash"] 312 | ) 313 | receipts_trie = HexaryTrie({}) 314 | receipts = ( 315 | web3.eth.get_transaction_receipt(tx) 316 | for tx in tqdm(burn_tx_block["transactions"], desc="Building receipts trie", unit="receipt") 317 | if tx != state_sync_tx_hash 318 | ) 319 | for tx_receipt in receipts: 320 | path = rlp.encode(tx_receipt["transactionIndex"]) 321 | receipts_trie[path] = serialize_receipt(tx_receipt) 322 | 323 | key = rlp.encode(burn_tx_receipt["transactionIndex"]) 324 | print("Building merkle proof") 325 | proof = receipts_trie.get_proof(key) 326 | 327 | assert ( 328 | receipts_trie.root_hash == burn_tx_block["receiptsRoot"] 329 | ), "Receipts trie root is incorrect" 330 | 331 | return key, proof 332 | 333 | 334 | def find_log_index(burn_tx_receipt: TxReceipt) -> int: 335 | """Retrieve the index of the burn event log.""" 336 | ERC20_TRANSFER_EVENT_SIG = keccak256(b"Transfer(address,address,uint256)") 337 | for idx, log in enumerate(burn_tx_receipt["logs"]): 338 | topics = log["topics"] 339 | if topics[0] == ERC20_TRANSFER_EVENT_SIG and topics[2] == HexBytes(0) * 32: 340 | return idx 341 | 342 | # this should not be reached 343 | raise Exception("Transfer Log Event Not Found in Burn Tx Receipt") 344 | 345 | 346 | def encode_payload( 347 | header_block_number: int, 348 | block_proof: List[bytes], 349 | block_number: int, 350 | timestamp: int, 351 | transactions_root: bytes, 352 | receipts_root: bytes, 353 | burn_tx_receipt: TxReceipt, 354 | receipt_proof: List[bytes], 355 | path: bytes, 356 | log_index: int, 357 | ) -> bytes: 358 | """RLP encode the data required to form the calldata for exiting.""" 359 | payload = [ 360 | header_block_number, 361 | b"".join(block_proof), 362 | block_number, 363 | timestamp, 364 | transactions_root, 365 | receipts_root, 366 | serialize_receipt(burn_tx_receipt), 367 | rlp.encode(receipt_proof), 368 | HexBytes(0) + path, 369 | log_index, 370 | ] 371 | return rlp.encode(payload) 372 | 373 | 374 | def build_calldata(burn_tx_id: str = MATIC_BURN_TX_ID) -> bytes: 375 | """Generate the calldata required for withdrawing ERC20 asset on Ethereum.""" 376 | assert is_burn_checkpointed(burn_tx_id) 377 | 378 | burn_tx, burn_tx_receipt, burn_tx_block = fetch_burn_tx_data(burn_tx_id) 379 | log_index = find_log_index(burn_tx_receipt) 380 | start, end, header_block_number = fetch_block_inclusion_data(burn_tx_block["number"]) 381 | block_proof = build_block_proof(start, end, burn_tx_block["number"]) 382 | path, receipt_proof = build_receipt_proof(burn_tx_receipt, burn_tx_block) 383 | 384 | calldata = encode_payload( 385 | header_block_number, 386 | block_proof, 387 | burn_tx_block["number"], 388 | burn_tx_block["timestamp"], 389 | burn_tx_block["transactionsRoot"], 390 | burn_tx_block["receiptsRoot"], 391 | burn_tx_receipt, 392 | receipt_proof, 393 | path, 394 | log_index, 395 | ) 396 | return calldata 397 | 398 | 399 | def withdraw_asset_on_ethereum(burn_tx_id: str = MATIC_BURN_TX_ID, sender=MSG_SENDER): 400 | print("Building Calldata") 401 | calldata = build_calldata(burn_tx_id) 402 | fp = f"withdraw-calldata-{datetime.now().isoformat()}.txt" 403 | with open(fp, "w") as f: 404 | f.write(calldata.hex()) 405 | 406 | root_chain_mgr_proxy_addr = ADDRS[network.show_active()]["RootChainManagerProxy"] 407 | abi = get_loaded_projects()[0].interface.RootChainManager.abi 408 | root_chain_mgr = Contract.from_abi("RootChainManager", root_chain_mgr_proxy_addr, abi) 409 | 410 | print("Calling Exit Function on Root Chain Manager") 411 | root_chain_mgr.exit(calldata, {"from": sender, "priority_fee": "2 gwei"}) 412 | 413 | # transfer USDC out of the root receiver 414 | usdc = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" 415 | root_receiver = RootForwarder.at("0x4473243A61b5193670D1324872368d015081822f") 416 | root_receiver.transfer(usdc, {"from": sender, "priority_fee": "2 gwei"}) 417 | 418 | 419 | def main(): 420 | 421 | route = input( 422 | """ 423 | Choose an option: 424 | (1) Burn an asset on Matic 425 | (2) Withdraw an asset on Ethereum 426 | (3) Check burn tx checkpoint 427 | Choice: """ 428 | ) 429 | try: 430 | route = int(route) 431 | except ValueError: 432 | exit() 433 | 434 | if route == 1: 435 | asset = input("Input token to burn on matic: ") 436 | amount = int(input("Input amount of token to burn: ")) 437 | sender = ( 438 | accounts.load(input("Account name: ")) 439 | if input("Do you want to load an account? [y/N] ") == "y" 440 | else MSG_SENDER 441 | ) 442 | burn_asset_on_matic(asset, amount, sender) 443 | elif route == 2: 444 | burn_tx_hash = input("Input matic burn tx hash: ") 445 | sender = ( 446 | accounts.load(input("Account name: ")) 447 | if input("Do you want to load an account? [y/N] ") == "y" 448 | else MSG_SENDER 449 | ) 450 | withdraw_asset_on_ethereum(burn_tx_hash, sender) 451 | elif route == 3: 452 | burn_tx_hash = input("Enter burn tx hash: ") 453 | bar_fmt = "Blocks Mined: {n} blocks - Time Elapsed: {elapsed}" 454 | for block in tqdm(chain.new_blocks(1), bar_format=bar_fmt): 455 | if is_burn_checkpointed(burn_tx_hash, True): 456 | print(f"Tx {burn_tx_hash} has been checkpointed in block {block['number']}") 457 | break 458 | 459 | 460 | def test_calldata(burn_tx: str, exit_tx: str): 461 | print(f"Testing Burn TX: {burn_tx}") 462 | 463 | root_chain_mgr_proxy_addr = ADDRS[network.show_active()]["RootChainManagerProxy"] 464 | abi = get_loaded_projects()[0].interface.RootChainManager.abi 465 | root_chain_mgr = Contract.from_abi("RootChainManager", root_chain_mgr_proxy_addr, abi) 466 | 467 | calldata = HexBytes(root_chain_mgr.exit.encode_input(build_calldata(burn_tx))) 468 | input_data = HexBytes(web3.eth.get_transaction(exit_tx)["input"]) 469 | 470 | assert calldata == input_data 471 | print("Test passed") 472 | 473 | 474 | def tester(): 475 | # (burn_tx, exit_tx) 476 | test_txs = [ 477 | ( 478 | "0x4486e398e0f2ca4d00bec85edbb9aff94e7085fa2b5ef18319989d9d8e37152f", 479 | "0x6fe5d2638e7bdbf598c215c6d20b6bf2cad58479460091c0f2330506c14762bf", 480 | ), 481 | ( 482 | "0xbcaafea9bed5c31dc2472a015afca6463a5de14730a3a6ab4501475c0594cfc4", 483 | "0x1afcfe324fcfa0fbf54182524e74fc57ff8ddff58367529af519adbaccc13f7a", 484 | ), 485 | ( 486 | "0x7d17b4cfbab16739bf00cead6ffec306f7420ec5c91de4ac1d485b7de9efaf49", 487 | "0xfed6fc9558d45b0672fe9ff23d341d028d99f71a318feabf925f0d1b67eea503", 488 | ), 489 | ] 490 | for burn_tx, exit_tx in test_txs: 491 | test_calldata(burn_tx, exit_tx) 492 | print("All works as expected.") 493 | -------------------------------------------------------------------------------- /scripts/fetch_deployment_data.py: -------------------------------------------------------------------------------- 1 | """Fetch Matic Network contract deployment data.""" 2 | import json 3 | from pathlib import Path 4 | 5 | import requests 6 | 7 | # Endpoint for retrieving matic data 8 | DATA_URI = "https://static.matic.network/network/{network}/{version}/index.json" 9 | 10 | 11 | # Hard coded values for permanent proxy addresses 12 | PROXY_DEPLOYMENT_ADDRS = { 13 | "mainnet": { 14 | "RootChainProxy": "0x86E4Dc95c7FBdBf52e33D563BbDB00823894C287", 15 | "RootChainManagerProxy": "0xA0c68C638235ee32657e8f720a23ceC1bFc77C77", 16 | }, 17 | "goerli": { 18 | "RootChainProxy": "0x2890bA17EfE978480615e330ecB65333b880928e", 19 | "RootChainManagerProxy": "0xBbD7cBFA79faee899Eaf900F13C9065bF03B1A74", 20 | }, 21 | } 22 | 23 | 24 | def fetch_deployment_data(network: str, version: str, force_fetch: bool = True) -> dict: 25 | """Fetch matic deployment data with the side effect of writing to disk. 26 | 27 | Args: 28 | network: The network name of interest, e.g. 'mainnet' or 'testnet' 29 | version: The network version of interest e.g. 'v1' or 'mumbai' 30 | """ 31 | path = Path(__file__).parent.parent.joinpath(f"{network}-{version}.json") 32 | 33 | if not force_fetch and path.exists(): 34 | try: 35 | with path.open() as fp: 36 | return json.load(fp) 37 | except (json.JSONDecodeError, FileNotFoundError): 38 | pass 39 | 40 | print("Fetching matic deployment data...") 41 | 42 | URL = DATA_URI.format(network=network, version=version) 43 | data = requests.get(URL).json() 44 | 45 | with path.open("w") as fp: 46 | json.dump(data, fp, sort_keys=True, indent=2) 47 | 48 | print(f"Matic deployment data saved at {path.as_posix()}") 49 | return data 50 | 51 | 52 | def main(): 53 | return fetch_deployment_data("mainnet", "v1") 54 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 100 3 | ignore = E203,W503 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/conftest.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | from brownie.project.main import get_loaded_projects 5 | 6 | # functions in wrapped methods are renamed to simplify common tests 7 | 8 | pytest_plugins = [ 9 | "fixtures.accounts", 10 | "fixtures.coins", 11 | "fixtures.deployments", 12 | "fixtures.functions", 13 | "fixtures.pooldata", 14 | "fixtures.setup", 15 | ] 16 | 17 | _pooldata = {} 18 | 19 | 20 | def pytest_addoption(parser): 21 | parser.addoption("--pool", help="comma-separated list of pools to target") 22 | 23 | 24 | def pytest_sessionstart(): 25 | # load `pooldata.json` for each pool 26 | project = get_loaded_projects()[0] 27 | for path in [i for i in project._path.glob("contracts/pools/*") if i.is_dir()]: 28 | with path.joinpath("pooldata.json").open() as fp: 29 | _pooldata[path.name] = json.load(fp) 30 | _pooldata[path.name].update( 31 | name=path.name, swap_contract=next(i.stem for i in path.glob("StableSwap*")) 32 | ) 33 | 34 | 35 | def pytest_generate_tests(metafunc): 36 | if "pool_data" in metafunc.fixturenames: 37 | # parametrize `pool_data` 38 | if metafunc.config.getoption("pool"): 39 | params = metafunc.config.getoption("pool").split(",") 40 | else: 41 | params = list(_pooldata) 42 | metafunc.parametrize("pool_data", params, indirect=True, scope="session") 43 | 44 | 45 | # isolation setup 46 | 47 | 48 | @pytest.fixture(autouse=True) 49 | def isolation_setup(fn_isolation): 50 | pass 51 | 52 | 53 | # main parametrized fixture, used to pass data about each pool into the other fixtures 54 | 55 | 56 | @pytest.fixture(scope="module") 57 | def pool_data(request): 58 | return _pooldata[request.param] 59 | 60 | 61 | @pytest.fixture(scope="session") 62 | def project(): 63 | yield get_loaded_projects()[0] 64 | -------------------------------------------------------------------------------- /tests/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-contract-polygon/2b227765023001f534ef5359ceb7cc470b43d5e4/tests/fixtures/__init__.py -------------------------------------------------------------------------------- /tests/fixtures/accounts.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="session") 5 | def alice(accounts): 6 | yield accounts[0] 7 | 8 | 9 | @pytest.fixture(scope="session") 10 | def bob(accounts): 11 | yield accounts[1] 12 | 13 | 14 | @pytest.fixture(scope="session") 15 | def charlie(accounts): 16 | yield accounts[2] 17 | -------------------------------------------------------------------------------- /tests/fixtures/coins.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import ETH_ADDRESS, Contract, interface 3 | from brownie.convert import to_bytes 4 | 5 | # public fixtures - these can be used when testing 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def pool_token(project, alice, pool_data): 10 | name = pool_data["name"] 11 | deployer = getattr(project, pool_data["lp_contract"]) 12 | args = [f"Curve {name} LP Token", f"{name}CRV", 18, 0][: len(deployer.deploy.abi["inputs"])] 13 | return deployer.deploy(*args, {"from": alice}) 14 | 15 | 16 | @pytest.fixture(scope="module") 17 | def wrapped_coins(project, alice, pool_data, underlying_coins): 18 | coins = [] 19 | 20 | for i, data in enumerate(pool_data["coins"]): 21 | if not data.get("wrapped_decimals"): 22 | coins.append(underlying_coins[i]) 23 | else: 24 | coins.append(_MintableTestToken(data["wrapped_address"], data["wrapped_interface"])) 25 | return coins 26 | 27 | 28 | @pytest.fixture(scope="module") 29 | def underlying_coins(alice, project, pool_data): 30 | coins = [] 31 | 32 | for data in pool_data["coins"]: 33 | if data.get("underlying_address") == ETH_ADDRESS: 34 | coins.append(ETH_ADDRESS) 35 | else: 36 | coins.append( 37 | _MintableTestToken( 38 | data.get("underlying_address", data.get("wrapped_address")), 39 | data.get("underlying_interface", data.get("wrapped_interface")), 40 | ) 41 | ) 42 | 43 | return coins 44 | 45 | 46 | class _MintableTestToken(Contract): 47 | def __init__(self, address, interface_name): 48 | abi = getattr(interface, interface_name).abi 49 | self.from_abi("PolygonToken", address, abi) 50 | 51 | super().__init__(address) 52 | 53 | def _mint_for_testing(self, target, amount, kwargs=None): 54 | if hasattr(self, "getRoleMember"): 55 | role = "0x8f4f2da22e8ac8f11e15f9fc141cddbb5deea8800186560abb6e68c5496619a9" 56 | minter = self.getRoleMember(role, 0) 57 | amount = to_bytes(amount, "bytes32") 58 | self.deposit(target, amount, {"from": minter}) 59 | elif hasattr(self, "POOL"): 60 | token = _MintableTestToken(self.UNDERLYING_ASSET_ADDRESS(), "BridgeToken") 61 | lending_pool = interface.AaveLendingPool(self.POOL()) 62 | token._mint_for_testing(target, amount) 63 | token.approve(lending_pool, amount, {"from": target}) 64 | lending_pool.deposit(token, amount, target, 0, {"from": target}) 65 | elif hasattr(self, "mint"): 66 | self.mint(target, amount, {"from": self.owner()}) 67 | else: 68 | raise ValueError("Unsupported Token") 69 | -------------------------------------------------------------------------------- /tests/fixtures/deployments.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="module") 5 | def swap( 6 | project, 7 | alice, 8 | underlying_coins, 9 | wrapped_coins, 10 | pool_token, 11 | pool_data, 12 | ): 13 | deployer = getattr(project, pool_data["swap_contract"]) 14 | 15 | abi = next(i["inputs"] for i in deployer.abi if i["type"] == "constructor") 16 | 17 | args = { 18 | "_coins": wrapped_coins, 19 | "_underlying_coins": underlying_coins, 20 | "_pool_token": pool_token, 21 | "_A": 360 * 2, 22 | "_fee": 0, 23 | "_admin_fee": 0, 24 | "_offpeg_fee_multiplier": 0, 25 | "_owner": alice, 26 | } 27 | deployment_args = [args[i["name"]] for i in abi] + [({"from": alice})] 28 | 29 | contract = deployer.deploy(*deployment_args) 30 | pool_token.set_minter(contract, {"from": alice}) 31 | 32 | return contract 33 | -------------------------------------------------------------------------------- /tests/fixtures/functions.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | # helper functions for contract interactions where the functionality differs 4 | # depending on the pool 5 | 6 | 7 | def _set_fees(chain, swap, fee, admin_fee, offpeg_multiplier=None): 8 | owner = swap.owner() 9 | if hasattr(swap, "commit_new_fee"): 10 | if hasattr(swap, "offpeg_fee_multiplier"): 11 | swap.commit_new_fee(fee, admin_fee, offpeg_multiplier or 0, {"from": owner}) 12 | elif offpeg_multiplier is not None: 13 | raise ValueError("Pool does not support `offpeg_fee_multiplier`") 14 | else: 15 | swap.commit_new_fee(fee, admin_fee, {"from": owner}) 16 | chain.sleep(86400 * 3) 17 | swap.apply_new_fee({"from": owner}) 18 | else: 19 | swap.commit_new_parameters(360 * 2, fee, admin_fee, {"from": owner}) 20 | chain.sleep(86400 * 3) 21 | swap.apply_new_parameters({"from": owner}) 22 | 23 | 24 | @pytest.fixture(scope="module") 25 | def set_fees(chain, swap): 26 | def _set_fee_fixture_fn(fee, admin_fee, include_meta=False): 27 | _set_fees(chain, swap, fee, admin_fee) 28 | 29 | yield _set_fee_fixture_fn 30 | 31 | 32 | @pytest.fixture(scope="session") 33 | def approx(): 34 | def _approx(a, b, precision=1e-10): 35 | return 2 * abs(a - b) / (a + b) <= precision 36 | 37 | yield _approx 38 | -------------------------------------------------------------------------------- /tests/fixtures/pooldata.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | # pools 4 | 5 | 6 | @pytest.fixture(scope="module") 7 | def underlying_decimals(pool_data): 8 | # number of decimal places for each underlying coin in the active pool 9 | return [i.get("decimals", i.get("wrapped_decimals")) for i in pool_data["coins"]] 10 | 11 | 12 | @pytest.fixture(scope="module") 13 | def wrapped_decimals(pool_data): 14 | # number of decimal places for each wrapped coin in the active pool 15 | yield [i.get("wrapped_decimals", i.get("decimals")) for i in pool_data["coins"]] 16 | 17 | 18 | @pytest.fixture(scope="module") 19 | def base_amount(pool_data): 20 | try: 21 | amount = pool_data["testing"]["initial_amount"] 22 | except KeyError: 23 | amount = 1000000 24 | 25 | yield amount 26 | 27 | 28 | @pytest.fixture(scope="module") 29 | def initial_amounts(wrapped_decimals, base_amount): 30 | # 1e6 of each coin - used to make an even initial deposit in many test setups 31 | yield [10 ** i * base_amount for i in wrapped_decimals] 32 | 33 | 34 | @pytest.fixture(scope="module") 35 | def initial_amounts_underlying(underlying_decimals, base_amount, n_coins): 36 | return [10 ** i * base_amount for i in underlying_decimals] 37 | 38 | 39 | @pytest.fixture(scope="module") 40 | def n_coins(pool_data): 41 | yield len(pool_data["coins"]) 42 | -------------------------------------------------------------------------------- /tests/fixtures/setup.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | # shared logic for pool and base_pool setup fixtures 4 | 5 | 6 | def _mint(acct, wrapped_coins, wrapped_amounts, underlying_coins, underlying_amounts): 7 | for coin, amount in zip(wrapped_coins, wrapped_amounts): 8 | coin._mint_for_testing(acct, amount, {"from": acct}) 9 | 10 | for coin, amount in zip(underlying_coins, underlying_amounts): 11 | if coin in wrapped_coins: 12 | continue 13 | coin._mint_for_testing(acct, amount, {"from": acct}) 14 | 15 | 16 | def _approve(owner, spender, *coins): 17 | for coin in set(x for i in coins for x in i): 18 | coin.approve(spender, 2 ** 256 - 1, {"from": owner}) 19 | 20 | 21 | # pool setup fixtures 22 | 23 | 24 | @pytest.fixture(scope="module") 25 | def add_initial_liquidity( 26 | alice, mint_alice, approve_alice, underlying_coins, swap, initial_amounts 27 | ): 28 | # mint (10**7 * precision) of each coin in the pool 29 | swap.add_liquidity(initial_amounts, 0, {"from": alice}) 30 | 31 | 32 | @pytest.fixture(scope="module") 33 | def mint_bob(bob, underlying_coins, wrapped_coins, initial_amounts, initial_amounts_underlying): 34 | _mint(bob, wrapped_coins, initial_amounts, underlying_coins, initial_amounts_underlying) 35 | 36 | 37 | @pytest.fixture(scope="module") 38 | def approve_bob(bob, swap, underlying_coins, wrapped_coins): 39 | _approve(bob, swap, underlying_coins, wrapped_coins) 40 | 41 | 42 | @pytest.fixture(scope="module") 43 | def mint_alice(alice, underlying_coins, wrapped_coins, initial_amounts, initial_amounts_underlying): 44 | _mint( 45 | alice, 46 | wrapped_coins, 47 | initial_amounts, 48 | underlying_coins, 49 | initial_amounts_underlying, 50 | ) 51 | 52 | 53 | @pytest.fixture(scope="module") 54 | def approve_alice(alice, swap, underlying_coins, wrapped_coins): 55 | _approve(alice, swap, underlying_coins, wrapped_coins) 56 | 57 | 58 | @pytest.fixture(scope="module") 59 | def approve_zap(alice, bob, zap, pool_token, underlying_coins): 60 | for underlying in underlying_coins: 61 | underlying.approve(zap, 2 ** 256 - 1, {"from": alice}) 62 | underlying.approve(zap, 2 ** 256 - 1, {"from": bob}) 63 | 64 | pool_token.approve(zap, 2 ** 256 - 1, {"from": alice}) 65 | pool_token.approve(zap, 2 ** 256 - 1, {"from": bob}) 66 | -------------------------------------------------------------------------------- /tests/forked/test_reverts.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | import brownie 4 | import pytest 5 | 6 | 7 | @pytest.fixture(scope="module", autouse=True) 8 | def setup(mint_alice, approve_alice, mint_bob, approve_bob, set_fees): 9 | set_fees(4000000, 5000000000, include_meta=True) 10 | 11 | 12 | def test_insufficient_balances( 13 | chain, 14 | alice, 15 | bob, 16 | charlie, 17 | swap, 18 | n_coins, 19 | wrapped_decimals, 20 | underlying_decimals, 21 | wrapped_coins, 22 | underlying_coins, 23 | initial_amounts, 24 | ): 25 | # attempt to deposit more funds than user has 26 | for idx in range(n_coins): 27 | amounts = [i // 2 for i in initial_amounts] 28 | amounts[idx] = int(wrapped_coins[idx].balanceOf(alice) * 1.01) 29 | with brownie.reverts(): 30 | swap.add_liquidity(amounts, 0, {"from": alice}) 31 | 32 | # add liquidity balanced 33 | amounts = [i // 2 for i in initial_amounts] 34 | swap.add_liquidity(amounts, 0, {"from": alice}) 35 | 36 | # attempt to perform swaps between coins with insufficient funds 37 | for send, recv in itertools.permutations(range(n_coins), 2): 38 | amount = initial_amounts[send] // 4 39 | with brownie.reverts(): 40 | swap.exchange(send, recv, amount, 0, {"from": charlie}) 41 | 42 | # attempt to perform swaps between coins with insufficient funds 43 | if hasattr(swap, "exchange_underlying"): 44 | for send, recv in itertools.permutations(range(n_coins), 2): 45 | assert underlying_coins[send].balanceOf(charlie) == 0 46 | amount = initial_amounts[send] // 4 47 | with brownie.reverts(): 48 | swap.exchange_underlying(send, recv, amount, 0, {"from": charlie}) 49 | 50 | # remove liquidity balanced 51 | with brownie.reverts(): 52 | swap.remove_liquidity(10 ** 18, [0] * n_coins, {"from": charlie}) 53 | 54 | # remove liquidity imbalanced 55 | for idx in range(n_coins): 56 | amounts = [10 ** wrapped_decimals[i] for i in range(n_coins)] 57 | amounts[idx] = wrapped_coins[idx].balanceOf(swap) + 1 58 | with brownie.reverts(): 59 | swap.remove_liquidity_imbalance(amounts, 2 ** 256 - 1, {"from": charlie}) 60 | 61 | for idx in range(n_coins): 62 | with brownie.reverts(): 63 | swap.remove_liquidity_one_coin(10 ** wrapped_decimals[idx], idx, 0, {"from": charlie}) 64 | -------------------------------------------------------------------------------- /tests/forked/test_success.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def setup(mint_alice, approve_alice, mint_bob, approve_bob, set_fees): 8 | set_fees(4000000, 5000000000, include_meta=True) 9 | 10 | 11 | def test_swaps( 12 | chain, 13 | alice, 14 | bob, 15 | swap, 16 | n_coins, 17 | wrapped_decimals, 18 | underlying_decimals, 19 | wrapped_coins, 20 | underlying_coins, 21 | initial_amounts, 22 | ): 23 | 24 | # add liquidity balanced 25 | amounts = [i // 2 for i in initial_amounts] 26 | swap.add_liquidity(amounts, 0, {"from": alice}) 27 | chain.sleep(3600) 28 | 29 | # add liquidity imbalanced 30 | for idx in range(n_coins): 31 | amounts = [i // 10 for i in initial_amounts] 32 | amounts[idx] = 0 33 | swap.add_liquidity(amounts, 0, {"from": alice}) 34 | chain.sleep(3600) 35 | 36 | # perform swaps between each coin 37 | for send, recv in itertools.permutations(range(n_coins), 2): 38 | amount = 10 ** wrapped_decimals[send] 39 | 40 | # retain a balance of the sent coin and start with 0 balance of receiving coin 41 | # this is the least gas-efficient method :) 42 | wrapped_coins[send]._mint_for_testing(bob, amount + 1, {"from": bob}) 43 | recv_balance = wrapped_coins[recv].balanceOf(bob) 44 | if recv_balance > 0: 45 | wrapped_coins[recv].transfer(alice, recv_balance, {"from": bob}) 46 | 47 | swap.exchange(send, recv, amount, 0, {"from": bob}) 48 | chain.sleep(3600) 49 | 50 | # perform swaps between each underlying coin 51 | if hasattr(swap, "exchange_underlying"): 52 | for send, recv in itertools.permutations(range(n_coins), 2): 53 | amount = 10 ** underlying_decimals[send] 54 | 55 | underlying_coins[send]._mint_for_testing(bob, amount + 1, {"from": bob}) 56 | recv_balance = underlying_coins[recv].balanceOf(bob) 57 | if recv_balance > 0: 58 | underlying_coins[recv].transfer(alice, recv_balance, {"from": bob}) 59 | 60 | swap.exchange_underlying(send, recv, amount, 0, {"from": bob}) 61 | chain.sleep(3600) 62 | 63 | # remove liquidity balanced 64 | swap.remove_liquidity(10 ** 18, [0] * n_coins, {"from": alice}) 65 | chain.sleep(3600) 66 | 67 | amounts = [10 ** wrapped_decimals[i] for i in range(n_coins)] 68 | swap.remove_liquidity_imbalance(amounts, 2 ** 256 - 1, {"from": alice}) 69 | chain.sleep(3600) 70 | 71 | # remove liquidity imbalanced 72 | for idx in range(n_coins): 73 | amounts = [10 ** wrapped_decimals[i] for i in range(n_coins)] 74 | amounts[idx] = 0 75 | swap.remove_liquidity_imbalance(amounts, 2 ** 256 - 1, {"from": alice}) 76 | chain.sleep(3600) 77 | 78 | for idx in range(n_coins): 79 | swap.remove_liquidity_one_coin(10 ** wrapped_decimals[idx], idx, 0, {"from": alice}) 80 | chain.sleep(3600) 81 | --------------------------------------------------------------------------------