├── .gitignore ├── ape-config.yaml ├── README.md ├── contracts ├── testing │ ├── ERC20Mock.vy │ └── ERC4626Mock.vy ├── CurveTokenV5.vy └── CurveCryptoSwap4626.vy ├── tests ├── test_exchange.py └── conftest.py ├── scripts └── setup.py ├── requirements.txt └── notebook.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | Pipfile 2 | myenv 3 | -------------------------------------------------------------------------------- /ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: curve-4626-pool 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ERC-4626 Curve CryptoSwap Adaptation 2 | 3 | ## Overview 4 | 5 | Forked CurveCryptoSwap2 pool from curvefi/curve-crypto-contract and adapted to work with ERC-4626. 6 | 7 | Supported features: 8 | - Exchange shares (ERC-4626) in pool 9 | - Exchange underlying assets in pool 10 | - Provide/Remove liquidity in shares (ERC-4626) 11 | - Provide/Remove liquidity in underlying assets 12 | 13 | ## The Fun Stuff 14 | 15 | Thanks to `vyperlang/titanoboa`, we created a [Jupiter Notebook](notebook.ipynb) that allows users to visualize changes in the pool and interact with it directly. No need to spin up a chain or deal with accounts, full on interpretation in python 😎 16 | 17 | Screen Shot 2022-07-25 at 3 06 11 PM 18 | 19 | ## Other forked code 20 | 21 | - `ERC4626Mock.vy` forked from fubuloubu/ERC4626 22 | - `ERC20.vy` forked from `vyperlang/vyper` 23 | -------------------------------------------------------------------------------- /contracts/testing/ERC20Mock.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.4 2 | """ 3 | @notice Mock ERC20 for testing 4 | """ 5 | 6 | event Transfer: 7 | _from: indexed(address) 8 | _to: indexed(address) 9 | _value: uint256 10 | 11 | event Approval: 12 | _owner: indexed(address) 13 | _spender: indexed(address) 14 | _value: uint256 15 | 16 | name: public(String[64]) 17 | symbol: public(String[32]) 18 | decimals: public(uint256) 19 | balanceOf: public(HashMap[address, uint256]) 20 | allowances: HashMap[address, HashMap[address, uint256]] 21 | total_supply: uint256 22 | 23 | 24 | @external 25 | def __init__(_name: String[64], _symbol: String[32], _decimals: uint256): 26 | self.name = _name 27 | self.symbol = _symbol 28 | self.decimals = _decimals 29 | 30 | 31 | @external 32 | @view 33 | def totalSupply() -> uint256: 34 | return self.total_supply 35 | 36 | 37 | @external 38 | @view 39 | def allowance(_owner : address, _spender : address) -> uint256: 40 | return self.allowances[_owner][_spender] 41 | 42 | 43 | @external 44 | def transfer(_to : address, _value : uint256) -> bool: 45 | self.balanceOf[msg.sender] -= _value 46 | self.balanceOf[_to] += _value 47 | log Transfer(msg.sender, _to, _value) 48 | return True 49 | 50 | 51 | @external 52 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 53 | self.balanceOf[_from] -= _value 54 | self.balanceOf[_to] += _value 55 | self.allowances[_from][msg.sender] -= _value 56 | log Transfer(_from, _to, _value) 57 | return True 58 | 59 | 60 | @external 61 | def approve(_spender : address, _value : uint256) -> bool: 62 | self.allowances[msg.sender][_spender] = _value 63 | log Approval(msg.sender, _spender, _value) 64 | return True 65 | 66 | 67 | @external 68 | def _mint_for_testing(_target: address, _value: uint256) -> bool: 69 | self.total_supply += _value 70 | self.balanceOf[_target] += _value 71 | log Transfer(ZERO_ADDRESS, _target, _value) 72 | 73 | return True 74 | -------------------------------------------------------------------------------- /tests/test_exchange.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import ape 3 | import random 4 | 5 | SAMPLES = 20 6 | 7 | i_min = 0 8 | i_max = 2 9 | 10 | j_min = 0 11 | j_max = 2 12 | 13 | amount_min = 10**6 14 | amount_max = 2 * 10**6 * 10**18 15 | 16 | 17 | def test_exchange(initial_prices, crypto_swap_with_deposit, token, coins, coins_underlying, accounts): 18 | for sample in range(SAMPLES): 19 | amount = random.randint(amount_min, amount_max) 20 | 21 | for i in range(i_min, i_max + 1): 22 | for j in range(j_min, j_max + 1): 23 | 24 | user = accounts[1] 25 | 26 | if i == j or i > 1 or j > 1: 27 | with ape.reverts(): 28 | crypto_swap_with_deposit.get_dy(i, j, 10**6) 29 | with ape.reverts(): 30 | crypto_swap_with_deposit.exchange(i, j, 10**6, 0, sender=user) 31 | 32 | else: 33 | prices = [10**18] + initial_prices 34 | amount = amount * 10**18 // prices[i] 35 | coins_underlying[i]._mint_for_testing(user, amount, sender=accounts[0]) 36 | coins_underlying[i].approve(coins[i], 2**256 - 1, sender=user) 37 | coins[i].deposit(amount, sender=user) 38 | 39 | calculated = crypto_swap_with_deposit.get_dy(i, j, amount) 40 | measured_i = coins[i].balanceOf(user) 41 | measured_j = coins[j].balanceOf(user) 42 | d_balance_i = crypto_swap_with_deposit.balances(i) 43 | d_balance_j = crypto_swap_with_deposit.balances(j) 44 | 45 | crypto_swap_with_deposit.exchange( 46 | i, j, amount, int(0.999 * calculated), sender=user 47 | ) 48 | 49 | measured_i -= coins[i].balanceOf(user) 50 | measured_j = coins[j].balanceOf(user) - measured_j 51 | d_balance_i = crypto_swap_with_deposit.balances(i) - d_balance_i 52 | d_balance_j = crypto_swap_with_deposit.balances(j) - d_balance_j 53 | 54 | assert amount == measured_i 55 | assert calculated == measured_j 56 | 57 | assert d_balance_i == amount 58 | assert -d_balance_j == measured_j 59 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | INITIAL_PRICES = [int(0.8 * 10**18)] # 1/eur 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def initial_prices(): 8 | yield INITIAL_PRICES 9 | 10 | 11 | @pytest.fixture(scope="module", autouse=True) 12 | def coins_underlying(project, accounts): 13 | yield [ 14 | project.ERC20Mock.deploy(name, name, 18, sender=accounts[0]) 15 | for name in ["USD", "EUR"] 16 | ] 17 | 18 | 19 | @pytest.fixture(scope="module", autouse=True) 20 | def coins(project, accounts, coins_underlying): 21 | yield [ 22 | project.ERC4626Mock.deploy(asset, name, name, 18, sender=accounts[0]) 23 | for asset, name in zip(coins_underlying, ["USD, EUR"]) 24 | ] 25 | 26 | 27 | @pytest.fixture(scope="module", autouse=True) 28 | def token(project, accounts): 29 | yield project.CurveTokenV5.deploy("Curve EUR-USD", "crvEURUSD", sender=accounts[0]) 30 | 31 | 32 | @pytest.fixture(scope="module", autouse=True) 33 | def crypto_swap(project, token, coins, accounts): 34 | swap = project.CurveCryptoSwap4626.deploy( 35 | accounts[0], 36 | accounts[0], 37 | 90 * 2**2 * 10000, # A 38 | int(2.8e-4 * 1e18), # gamma 39 | int(5e-4 * 1e10), # mid_fee 40 | int(4e-3 * 1e10), # out_fee 41 | 10**10, # allowed_extra_profit 42 | int(0.012 * 1e18), # fee_gamma 43 | int(0.55e-5 * 1e18), # adjustment_step 44 | 0, # admin_fee 45 | 600, # ma_half_time 46 | INITIAL_PRICES[0], 47 | token, 48 | coins, 49 | sender=accounts[0], 50 | ) 51 | token.set_minter(swap, sender=accounts[0]) 52 | 53 | return swap 54 | 55 | 56 | def _crypto_swap_with_deposit(crypto_swap, coins_underlying, coins, accounts): 57 | user = accounts[1] 58 | quantities = [10**6 * 10**36 // p for p in [10**18] + INITIAL_PRICES] 59 | for coin_underlying, coin, q in zip(coins_underlying, coins, quantities): 60 | coin_underlying._mint_for_testing(user, q, sender=accounts[0]) 61 | coin_underlying.approve(coin, 2**256 - 1, sender=user) 62 | coin.deposit(q, sender=user) 63 | coin.approve(crypto_swap, 2**256 - 1, sender=user) 64 | 65 | # Very first deposit 66 | crypto_swap.add_liquidity(quantities, 0, sender=user) 67 | 68 | return crypto_swap 69 | 70 | 71 | @pytest.fixture(scope="module") 72 | def crypto_swap_with_deposit(crypto_swap, coins_underlying, coins, accounts): 73 | return _crypto_swap_with_deposit(crypto_swap, coins_underlying, coins, accounts) 74 | 75 | 76 | # @pytest.fixture(autouse=True) 77 | # def isolation(fn_isolation): 78 | # pass 79 | -------------------------------------------------------------------------------- /scripts/setup.py: -------------------------------------------------------------------------------- 1 | import boa 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class Info: 7 | deployer: str 8 | user: str 9 | tokens: [str] 10 | erc20_list: [object] 11 | erc4626_list: [object] 12 | pool: object 13 | lp_token: object 14 | 15 | 16 | def setup(): 17 | deployer = "0x0000000000000000000000000000000000001234" 18 | user = "0x0000000000000000000000000000000000001235" 19 | 20 | tokens = ["USDC", "WETH"] 21 | prepends = ["y", "a"] 22 | mint_quantity = 10 * 10**6 * 10**18 # 5 million 23 | 24 | initial_prices = [int(1500 * 10**18)] 25 | 26 | erc20_list = [None] * len(tokens) 27 | erc4626_list = [None] * len(tokens) 28 | 29 | with boa.env.prank(deployer): 30 | for i in range(len(tokens)): 31 | erc20_list[i] = boa.load( 32 | "contracts/testing/ERC20Mock.vy", tokens[i], tokens[i], 18 33 | ) 34 | vault_token = prepends[i] + tokens[i] 35 | erc4626_list[i] = boa.load( 36 | "contracts/testing/ERC4626Mock.vy", 37 | erc20_list[i].address, 38 | vault_token, 39 | vault_token, 40 | 18, 41 | ) 42 | 43 | for erc20 in erc20_list: 44 | erc20._mint_for_testing(user, mint_quantity) 45 | 46 | with boa.env.prank(user): 47 | for i in range(len(erc20_list)): 48 | erc20_list[i].approve(erc4626_list[i], 2**256 - 1) 49 | erc4626_list[i].deposit(int(mint_quantity * (3 / 4))) 50 | 51 | with boa.env.prank(deployer): 52 | lp_token = boa.load("contracts/CurveTokenV5.vy", "Curve EUR-USD", "crvEURUSD") 53 | 54 | pool = boa.load( 55 | "contracts/CurveCryptoSwap4626.vy", 56 | deployer, 57 | deployer, 58 | 90 * 2**2 * 10000, # A 59 | int(2.8e-4 * 1e18), # gamma 60 | int(5e-4 * 1e10), # mid_fee 61 | int(4e-3 * 1e10), # out_fee 62 | 10**10, # allowed_extra_profit 63 | int(0.012 * 1e18), # fee_gamma 64 | int(0.55e-5 * 1e18), # adjustment_step 65 | 0, # admin_fee 66 | 600, # ma_half_time 67 | initial_prices[0], 68 | lp_token.address, 69 | [erc4626.address for erc4626 in erc4626_list], 70 | ) 71 | 72 | lp_token.set_minter(pool.address) 73 | 74 | with boa.env.prank(user): 75 | for i in range(len(erc20_list)): 76 | erc4626_list[i].approve(pool, 2**256 - 1) 77 | erc20_list[i].approve(pool, 2**256 - 1) 78 | quantities = [mint_quantity // 2, mint_quantity // 2 // 5] 79 | pool.add_liquidity(quantities, 0) 80 | 81 | return Info(deployer, user, tokens, erc20_list, erc4626_list, pool, lp_token) 82 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.8.1 2 | aiosignal==1.2.0 3 | ape-vyper==0.3.0 4 | appnope==0.1.3 5 | argon2-cffi==21.3.0 6 | argon2-cffi-bindings==21.2.0 7 | asttokens==2.0.5 8 | async-timeout==4.0.2 9 | attrs==21.4.0 10 | backcall==0.2.0 11 | base58==1.0.3 12 | beautifulsoup4==4.11.1 13 | bitarray==1.2.2 14 | bleach==5.0.1 15 | cached-property==1.5.2 16 | certifi==2022.6.15 17 | cffi==1.15.1 18 | charset-normalizer==2.1.0 19 | click==8.1.3 20 | colorama==0.4.5 21 | commonmark==0.9.1 22 | cytoolz==0.12.0 23 | debugpy==1.6.2 24 | decorator==5.1.1 25 | defusedxml==0.7.1 26 | Deprecated==1.2.13 27 | entrypoints==0.4 28 | eth-abi==2.2.0 29 | eth-account==0.5.7 30 | eth-ape==0.3.5 31 | eth-bloom==1.0.4 32 | eth-hash==0.3.3 33 | eth-keyfile==0.5.1 34 | eth-keys==0.3.4 35 | eth-rlp==0.2.1 36 | eth-tester==0.6.0b6 37 | eth-typing==2.3.0 38 | eth-utils==1.10.0 39 | ethpm-types==0.3.2 40 | evm-trace==0.1.0a6 41 | executing==0.8.3 42 | fastjsonschema==2.16.1 43 | frozenlist==1.3.0 44 | hexbytes==0.2.2 45 | idna==3.3 46 | importlib-metadata==4.12.0 47 | iniconfig==1.1.1 48 | ipfshttpclient==0.8.0a2 49 | ipykernel==6.15.1 50 | ipython==8.4.0 51 | ipython-genutils==0.2.0 52 | jedi==0.18.1 53 | Jinja2==3.1.2 54 | jsonschema==4.7.2 55 | jupyter-client==7.3.4 56 | jupyter-core==4.11.1 57 | jupyterlab-pygments==0.2.2 58 | lru-dict==1.1.8 59 | MarkupSafe==2.1.1 60 | matplotlib-inline==0.1.3 61 | mistune==0.8.4 62 | morphys==1.0 63 | multiaddr==0.0.9 64 | multidict==6.0.2 65 | mypy-extensions==0.4.3 66 | nbclient==0.6.6 67 | nbconvert==6.5.0 68 | nbformat==5.4.0 69 | nest-asyncio==1.5.5 70 | netaddr==0.8.0 71 | notebook==6.4.12 72 | numpy==1.23.1 73 | packaging==20.9 74 | pandas==1.4.3 75 | pandocfilters==1.5.0 76 | parsimonious==0.8.1 77 | parso==0.8.3 78 | pexpect==4.8.0 79 | pickleshare==0.7.5 80 | pluggy==0.13.1 81 | prometheus-client==0.14.1 82 | prompt-toolkit==3.0.30 83 | protobuf==3.20.1 84 | psutil==5.9.1 85 | ptyprocess==0.7.0 86 | pure-eval==0.2.2 87 | py==1.11.0 88 | py-cid==0.3.0 89 | py-ecc==5.2.0 90 | py-evm==0.5.0a3 91 | py-geth==3.8.0 92 | py-multibase==1.0.3 93 | py-multicodec==0.2.1 94 | py-multihash==0.2.3 95 | pycparser==2.21 96 | pycryptodome==3.15.0 97 | pydantic==1.9.1 98 | pyethash==0.1.27 99 | pygit2==1.9.2 100 | PyGithub==1.55 101 | Pygments==2.12.0 102 | PyJWT==2.4.0 103 | PyNaCl==1.5.0 104 | pyparsing==3.0.9 105 | pyrsistent==0.18.1 106 | pysha3==1.0.2 107 | pytest==7.1.2 108 | python-baseconv==1.2.2 109 | python-dateutil==2.8.2 110 | pytz==2022.1 111 | PyYAML==6.0 112 | pyzmq==23.2.0 113 | requests==2.28.1 114 | rich==10.16.2 115 | rlp==2.0.1 116 | semantic-version==2.8.5 117 | Send2Trash==1.8.0 118 | six==1.16.0 119 | sortedcontainers==2.4.0 120 | soupsieve==2.3.2.post1 121 | stack-data==0.3.0 122 | terminado==0.15.0 123 | tinycss2==1.1.1 124 | titanoboa @ git+https://github.com/vyperlang/titanoboa@ec390b9ecc3f39474a9c73404d566dafe85fe032 125 | tomli==2.0.1 126 | toolz==0.12.0 127 | tornado==6.2 128 | tqdm==4.64.0 129 | traitlets==5.3.0 130 | trie==2.0.0a5 131 | typing-extensions==3.10.0.2 132 | urllib3==1.26.10 133 | varint==1.0.2 134 | vvm==0.1.0 135 | vyper @ git+https://github.com/vyperlang/vyper@f6a2dcb3e3d5c8340d3f71a96aa521dba0ac6311 136 | wcwidth==0.2.5 137 | web3==5.30.0 138 | webencodings==0.5.1 139 | websockets==9.1 140 | wrapt==1.14.1 141 | yarl==1.7.2 142 | zipp==3.8.1 143 | -------------------------------------------------------------------------------- /contracts/testing/ERC4626Mock.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.4 2 | from vyper.interfaces import ERC20 3 | from vyper.interfaces import ERC4626 4 | 5 | implements: ERC20 6 | implements: ERC4626 7 | 8 | ##### ERC20 ##### 9 | 10 | totalSupply: public(uint256) 11 | balanceOf: public(HashMap[address, uint256]) 12 | allowance: public(HashMap[address, HashMap[address, uint256]]) 13 | 14 | name: public(String[64]) 15 | symbol: public(String[32]) 16 | decimals: public(uint8) 17 | 18 | event Transfer: 19 | sender: indexed(address) 20 | receiver: indexed(address) 21 | amount: uint256 22 | 23 | event Approval: 24 | owner: indexed(address) 25 | spender: indexed(address) 26 | allowance: uint256 27 | 28 | ##### ERC4626 ##### 29 | 30 | asset: public(ERC20) 31 | 32 | event Deposit: 33 | depositor: indexed(address) 34 | receiver: indexed(address) 35 | assets: uint256 36 | shares: uint256 37 | 38 | event Withdraw: 39 | withdrawer: indexed(address) 40 | receiver: indexed(address) 41 | owner: indexed(address) 42 | assets: uint256 43 | shares: uint256 44 | 45 | 46 | @external 47 | def __init__(_asset: ERC20, _name: String[64], _symbol: String[32], _decimals: uint8): 48 | self.asset = _asset 49 | self.name = _name 50 | self.symbol = _symbol 51 | self.decimals = _decimals 52 | 53 | 54 | @external 55 | def transfer(receiver: address, amount: uint256) -> bool: 56 | self.balanceOf[msg.sender] -= amount 57 | self.balanceOf[receiver] += amount 58 | log Transfer(msg.sender, receiver, amount) 59 | return True 60 | 61 | 62 | @external 63 | def approve(spender: address, amount: uint256) -> bool: 64 | self.allowance[msg.sender][spender] = amount 65 | log Approval(msg.sender, spender, amount) 66 | return True 67 | 68 | 69 | @external 70 | def transferFrom(sender: address, receiver: address, amount: uint256) -> bool: 71 | self.allowance[sender][msg.sender] -= amount 72 | self.balanceOf[sender] -= amount 73 | self.balanceOf[receiver] += amount 74 | log Transfer(sender, receiver, amount) 75 | return True 76 | 77 | 78 | @view 79 | @external 80 | def totalAssets() -> uint256: 81 | return self.asset.balanceOf(self) 82 | 83 | 84 | @view 85 | @internal 86 | def _convertToAssets(shareAmount: uint256) -> uint256: 87 | totalSupply: uint256 = self.totalSupply 88 | if totalSupply == 0: 89 | return 0 90 | 91 | # NOTE: `shareAmount = 0` is extremely rare case, not optimizing for it 92 | # NOTE: `totalAssets = 0` is extremely rare case, not optimizing for it 93 | return shareAmount * self.asset.balanceOf(self) / totalSupply 94 | 95 | 96 | @view 97 | @external 98 | def convertToAssets(shareAmount: uint256) -> uint256: 99 | return self._convertToAssets(shareAmount) 100 | 101 | 102 | @view 103 | @internal 104 | def _convertToShares(assetAmount: uint256) -> uint256: 105 | totalSupply: uint256 = self.totalSupply 106 | totalAssets: uint256 = self.asset.balanceOf(self) 107 | if totalAssets == 0 or totalSupply == 0: 108 | return assetAmount # 1:1 price 109 | 110 | # NOTE: `assetAmount = 0` is extremely rare case, not optimizing for it 111 | return assetAmount * totalSupply / totalAssets 112 | 113 | 114 | @view 115 | @external 116 | def convertToShares(assetAmount: uint256) -> uint256: 117 | return self._convertToShares(assetAmount) 118 | 119 | 120 | @view 121 | @external 122 | def maxDeposit(owner: address) -> uint256: 123 | return MAX_UINT256 124 | 125 | 126 | @view 127 | @external 128 | def previewDeposit(assets: uint256) -> uint256: 129 | return self._convertToShares(assets) 130 | 131 | 132 | @external 133 | def deposit(assets: uint256, receiver: address=msg.sender) -> uint256: 134 | shares: uint256 = self._convertToShares(assets) 135 | self.asset.transferFrom(msg.sender, self, assets) 136 | 137 | self.totalSupply += shares 138 | self.balanceOf[receiver] += shares 139 | log Deposit(msg.sender, receiver, assets, shares) 140 | return shares 141 | 142 | 143 | @view 144 | @external 145 | def maxMint(owner: address) -> uint256: 146 | return MAX_UINT256 147 | 148 | 149 | @view 150 | @external 151 | def previewMint(shares: uint256) -> uint256: 152 | assets: uint256 = self._convertToAssets(shares) 153 | 154 | # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time 155 | if assets == 0 and self.asset.balanceOf(self) == 0: 156 | return shares # NOTE: Assume 1:1 price if nothing deposited yet 157 | 158 | return assets 159 | 160 | 161 | @external 162 | def mint(shares: uint256, receiver: address=msg.sender) -> uint256: 163 | assets: uint256 = self._convertToAssets(shares) 164 | 165 | if assets == 0 and self.asset.balanceOf(self) == 0: 166 | assets = shares # NOTE: Assume 1:1 price if nothing deposited yet 167 | 168 | self.asset.transferFrom(msg.sender, self, assets) 169 | 170 | self.totalSupply += shares 171 | self.balanceOf[receiver] += shares 172 | log Deposit(msg.sender, receiver, assets, shares) 173 | return assets 174 | 175 | 176 | @view 177 | @external 178 | def maxWithdraw(owner: address) -> uint256: 179 | return MAX_UINT256 # real max is `self.asset.balanceOf(self)` 180 | 181 | 182 | @view 183 | @external 184 | def previewWithdraw(assets: uint256) -> uint256: 185 | shares: uint256 = self._convertToShares(assets) 186 | 187 | # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time 188 | if shares == assets and self.totalSupply == 0: 189 | return 0 # NOTE: Nothing to redeem 190 | 191 | return shares 192 | 193 | 194 | @external 195 | def withdraw(assets: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256: 196 | shares: uint256 = self._convertToShares(assets) 197 | 198 | # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time 199 | if shares == assets and self.totalSupply == 0: 200 | raise # Nothing to redeem 201 | 202 | if owner != msg.sender: 203 | self.allowance[owner][msg.sender] -= shares 204 | 205 | self.totalSupply -= shares 206 | self.balanceOf[owner] -= shares 207 | 208 | self.asset.transfer(receiver, assets) 209 | log Withdraw(msg.sender, receiver, owner, assets, shares) 210 | return shares 211 | 212 | 213 | @view 214 | @external 215 | def maxRedeem(owner: address) -> uint256: 216 | return MAX_UINT256 # real max is `self.totalSupply` 217 | 218 | 219 | @view 220 | @external 221 | def previewRedeem(shares: uint256) -> uint256: 222 | return self._convertToAssets(shares) 223 | 224 | 225 | @external 226 | def redeem(shares: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256: 227 | if owner != msg.sender: 228 | self.allowance[owner][msg.sender] -= shares 229 | 230 | assets: uint256 = self._convertToAssets(shares) 231 | self.totalSupply -= shares 232 | self.balanceOf[owner] -= shares 233 | 234 | self.asset.transfer(receiver, assets) 235 | log Withdraw(msg.sender, receiver, owner, assets, shares) 236 | return assets 237 | 238 | 239 | @external 240 | def DEBUG_steal_tokens(amount: uint256): 241 | # NOTE: This is the primary method of mocking share price changes 242 | self.asset.transfer(msg.sender, amount) 243 | -------------------------------------------------------------------------------- /contracts/CurveTokenV5.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.4 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 | from vyper.interfaces import ERC20 11 | 12 | implements: ERC20 13 | 14 | interface Curve: 15 | def owner() -> address: view 16 | 17 | interface ERC1271: 18 | def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view 19 | 20 | 21 | event Approval: 22 | _owner: indexed(address) 23 | _spender: indexed(address) 24 | _value: uint256 25 | 26 | event Transfer: 27 | _from: indexed(address) 28 | _to: indexed(address) 29 | _value: uint256 30 | 31 | event SetName: 32 | old_name: String[64] 33 | old_symbol: String[32] 34 | name: String[64] 35 | symbol: String[32] 36 | owner: address 37 | time: uint256 38 | 39 | event SetMinter: 40 | _old_minter: address 41 | _new_minter: address 42 | 43 | 44 | EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") 45 | PERMIT_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") 46 | 47 | # keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 48 | ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 49 | VERSION: constant(String[8]) = "v5.0.0" 50 | 51 | 52 | name: public(String[64]) 53 | symbol: public(String[32]) 54 | DOMAIN_SEPARATOR: public(bytes32) 55 | 56 | balanceOf: public(HashMap[address, uint256]) 57 | allowance: public(HashMap[address, HashMap[address, uint256]]) 58 | totalSupply: public(uint256) 59 | 60 | minter: public(address) 61 | nonces: public(HashMap[address, uint256]) 62 | 63 | 64 | @external 65 | def __init__(_name: String[64], _symbol: String[32]): 66 | self.name = _name 67 | self.symbol = _symbol 68 | 69 | # set as storage variable in the event that `name` ever changes 70 | self.DOMAIN_SEPARATOR = keccak256( 71 | _abi_encode(EIP712_TYPEHASH, keccak256(_name), keccak256(VERSION), chain.id, self) 72 | ) 73 | 74 | self.minter = msg.sender 75 | log SetMinter(ZERO_ADDRESS, msg.sender) 76 | 77 | # fire a transfer event so block explorers identify the contract as an ERC20 78 | log Transfer(ZERO_ADDRESS, msg.sender, 0) 79 | 80 | 81 | @external 82 | def transfer(_to: address, _value: uint256) -> bool: 83 | """ 84 | @dev Transfer token for a specified address 85 | @param _to The address to transfer to. 86 | @param _value The amount to be transferred. 87 | """ 88 | # NOTE: vyper does not allow underflows 89 | # so the following subtraction would revert on insufficient balance 90 | self.balanceOf[msg.sender] -= _value 91 | self.balanceOf[_to] += _value 92 | 93 | log Transfer(msg.sender, _to, _value) 94 | return True 95 | 96 | 97 | @external 98 | def transferFrom(_from: address, _to: address, _value: uint256) -> bool: 99 | """ 100 | @dev Transfer tokens from one address to another. 101 | @param _from address The address which you want to send tokens from 102 | @param _to address The address which you want to transfer to 103 | @param _value uint256 the amount of tokens to be transferred 104 | """ 105 | self.balanceOf[_from] -= _value 106 | self.balanceOf[_to] += _value 107 | 108 | _allowance: uint256 = self.allowance[_from][msg.sender] 109 | if _allowance != MAX_UINT256: 110 | self.allowance[_from][msg.sender] = _allowance - _value 111 | 112 | log Transfer(_from, _to, _value) 113 | return True 114 | 115 | 116 | @external 117 | def approve(_spender: address, _value: uint256) -> bool: 118 | """ 119 | @notice Approve the passed address to transfer the specified amount of 120 | tokens on behalf of msg.sender 121 | @dev Beware that changing an allowance via this method brings the risk 122 | that someone may use both the old and new allowance by unfortunate 123 | transaction ordering. This may be mitigated with the use of 124 | {increaseAllowance} and {decreaseAllowance}. 125 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 126 | @param _spender The address which will transfer the funds 127 | @param _value The amount of tokens that may be transferred 128 | @return bool success 129 | """ 130 | self.allowance[msg.sender][_spender] = _value 131 | 132 | log Approval(msg.sender, _spender, _value) 133 | return True 134 | 135 | 136 | @external 137 | def permit( 138 | _owner: address, 139 | _spender: address, 140 | _value: uint256, 141 | _deadline: uint256, 142 | _v: uint8, 143 | _r: bytes32, 144 | _s: bytes32 145 | ) -> bool: 146 | """ 147 | @notice Approves spender by owner's signature to expend owner's tokens. 148 | See https://eips.ethereum.org/EIPS/eip-2612. 149 | @dev Inspired by https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L753-L793 150 | @dev Supports smart contract wallets which implement ERC1271 151 | https://eips.ethereum.org/EIPS/eip-1271 152 | @param _owner The address which is a source of funds and has signed the Permit. 153 | @param _spender The address which is allowed to spend the funds. 154 | @param _value The amount of tokens to be spent. 155 | @param _deadline The timestamp after which the Permit is no longer valid. 156 | @param _v The bytes[64] of the valid secp256k1 signature of permit by owner 157 | @param _r The bytes[0:32] of the valid secp256k1 signature of permit by owner 158 | @param _s The bytes[32:64] of the valid secp256k1 signature of permit by owner 159 | @return True, if transaction completes successfully 160 | """ 161 | assert _owner != ZERO_ADDRESS 162 | assert block.timestamp <= _deadline 163 | 164 | nonce: uint256 = self.nonces[_owner] 165 | digest: bytes32 = keccak256( 166 | concat( 167 | b"\x19\x01", 168 | self.DOMAIN_SEPARATOR, 169 | keccak256(_abi_encode(PERMIT_TYPEHASH, _owner, _spender, _value, nonce, _deadline)) 170 | ) 171 | ) 172 | 173 | if _owner.is_contract: 174 | sig: Bytes[65] = concat(_abi_encode(_r, _s), slice(convert(_v, bytes32), 31, 1)) 175 | # reentrancy not a concern since this is a staticcall 176 | assert ERC1271(_owner).isValidSignature(digest, sig) == ERC1271_MAGIC_VAL 177 | else: 178 | assert ecrecover(digest, convert(_v, uint256), convert(_r, uint256), convert(_s, uint256)) == _owner 179 | 180 | self.allowance[_owner][_spender] = _value 181 | self.nonces[_owner] = nonce + 1 182 | 183 | log Approval(_owner, _spender, _value) 184 | return True 185 | 186 | 187 | @external 188 | def increaseAllowance(_spender: address, _added_value: uint256) -> bool: 189 | """ 190 | @notice Increase the allowance granted to `_spender` by the caller 191 | @dev This is alternative to {approve} that can be used as a mitigation for 192 | the potential race condition 193 | @param _spender The address which will transfer the funds 194 | @param _added_value The amount of to increase the allowance 195 | @return bool success 196 | """ 197 | allowance: uint256 = self.allowance[msg.sender][_spender] + _added_value 198 | self.allowance[msg.sender][_spender] = allowance 199 | 200 | log Approval(msg.sender, _spender, allowance) 201 | return True 202 | 203 | 204 | @external 205 | def decreaseAllowance(_spender: address, _subtracted_value: uint256) -> bool: 206 | """ 207 | @notice Decrease the allowance granted to `_spender` by the caller 208 | @dev This is alternative to {approve} that can be used as a mitigation for 209 | the potential race condition 210 | @param _spender The address which will transfer the funds 211 | @param _subtracted_value The amount of to decrease the allowance 212 | @return bool success 213 | """ 214 | allowance: uint256 = self.allowance[msg.sender][_spender] - _subtracted_value 215 | self.allowance[msg.sender][_spender] = allowance 216 | 217 | log Approval(msg.sender, _spender, allowance) 218 | return True 219 | 220 | 221 | @external 222 | def mint(_to: address, _value: uint256) -> bool: 223 | """ 224 | @dev Mint an amount of the token and assigns it to an account. 225 | This encapsulates the modification of balances such that the 226 | proper events are emitted. 227 | @param _to The account that will receive the created tokens. 228 | @param _value The amount that will be created. 229 | """ 230 | assert msg.sender == self.minter 231 | 232 | self.totalSupply += _value 233 | self.balanceOf[_to] += _value 234 | 235 | log Transfer(ZERO_ADDRESS, _to, _value) 236 | return True 237 | 238 | 239 | @external 240 | def mint_relative(_to: address, frac: uint256) -> uint256: 241 | """ 242 | @dev Increases supply by factor of (1 + frac/1e18) and mints it for _to 243 | """ 244 | assert msg.sender == self.minter 245 | 246 | supply: uint256 = self.totalSupply 247 | d_supply: uint256 = supply * frac / 10**18 248 | if d_supply > 0: 249 | self.totalSupply = supply + d_supply 250 | self.balanceOf[_to] += d_supply 251 | log Transfer(ZERO_ADDRESS, _to, d_supply) 252 | 253 | return d_supply 254 | 255 | 256 | @external 257 | def burnFrom(_to: address, _value: uint256) -> bool: 258 | """ 259 | @dev Burn an amount of the token from a given account. 260 | @param _to The account whose tokens will be burned. 261 | @param _value The amount that will be burned. 262 | """ 263 | assert msg.sender == self.minter 264 | 265 | self.totalSupply -= _value 266 | self.balanceOf[_to] -= _value 267 | 268 | log Transfer(_to, ZERO_ADDRESS, _value) 269 | return True 270 | 271 | 272 | @external 273 | def set_minter(_minter: address): 274 | """ 275 | @notice Set the address allowed to mint tokens 276 | @dev Emits the `SetMinter` event 277 | @param _minter The address to set as the minter 278 | """ 279 | assert msg.sender == self.minter 280 | 281 | log SetMinter(msg.sender, _minter) 282 | self.minter = _minter 283 | 284 | 285 | @view 286 | @external 287 | def decimals() -> uint8: 288 | """ 289 | @notice Get the number of decimals for this token 290 | @dev Implemented as a view method to reduce gas costs 291 | @return uint8 decimal places 292 | """ 293 | return 18 294 | 295 | 296 | @view 297 | @external 298 | def version() -> String[8]: 299 | """ 300 | @notice Get the version of this token contract 301 | """ 302 | return VERSION 303 | 304 | 305 | @external 306 | def set_name(_name: String[64], _symbol: String[32]): 307 | """ 308 | @notice Set the token name and symbol 309 | @dev Only callable by the owner of the Minter contract 310 | """ 311 | assert Curve(self.minter).owner() == msg.sender 312 | 313 | # avoid writing to memory, save a few gas 314 | log SetName(self.name, self.symbol, _name, _symbol, msg.sender, block.timestamp) 315 | 316 | self.name = _name 317 | self.symbol = _symbol 318 | 319 | # update domain separator 320 | self.DOMAIN_SEPARATOR = keccak256( 321 | _abi_encode(EIP712_TYPEHASH, keccak256(_name), keccak256(VERSION), chain.id, self) 322 | ) 323 | 324 | -------------------------------------------------------------------------------- /contracts/CurveCryptoSwap4626.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.4 2 | # (c) Curve.Fi, 2022 3 | # Pool for two crypto assets 4 | 5 | # Expected coins: 6 | # eth/whatever 7 | 8 | 9 | interface CurveToken: 10 | def totalSupply() -> uint256: view 11 | def mint(_to: address, _value: uint256) -> bool: nonpayable 12 | def mint_relative(_to: address, frac: uint256) -> uint256: nonpayable 13 | def burnFrom(_to: address, _value: uint256) -> bool: nonpayable 14 | 15 | interface ERC20: 16 | def transfer(_to: address, _value: uint256) -> bool: nonpayable 17 | def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable 18 | def decimals() -> uint256: view 19 | def balanceOf(_user: address) -> uint256: view 20 | def approve(_spender : address, _value : uint256) -> bool: nonpayable 21 | 22 | interface ERC4626: 23 | def asset() -> address: view 24 | def convertToShares(assetAmount: uint256) -> uint256: view 25 | def convertToAssets(shareAmount: uint256) -> uint256: view 26 | def deposit(assets: uint256, receiver: address) -> uint256: nonpayable 27 | def withdraw(assets: uint256, receiver: address, owner: address) -> uint256: nonpayable 28 | 29 | # Events 30 | event TokenExchange: 31 | buyer: indexed(address) 32 | sold_id: uint256 33 | tokens_sold: uint256 34 | bought_id: uint256 35 | tokens_bought: uint256 36 | 37 | event AddLiquidity: 38 | provider: indexed(address) 39 | token_amounts: uint256[N_COINS] 40 | fee: uint256 41 | token_supply: uint256 42 | 43 | event RemoveLiquidity: 44 | provider: indexed(address) 45 | token_amounts: uint256[N_COINS] 46 | token_supply: uint256 47 | 48 | event RemoveLiquidityOne: 49 | provider: indexed(address) 50 | token_amount: uint256 51 | coin_index: uint256 52 | coin_amount: uint256 53 | 54 | event CommitNewAdmin: 55 | deadline: indexed(uint256) 56 | admin: indexed(address) 57 | 58 | event NewAdmin: 59 | admin: indexed(address) 60 | 61 | event CommitNewParameters: 62 | deadline: indexed(uint256) 63 | admin_fee: uint256 64 | mid_fee: uint256 65 | out_fee: uint256 66 | fee_gamma: uint256 67 | allowed_extra_profit: uint256 68 | adjustment_step: uint256 69 | ma_half_time: uint256 70 | 71 | event NewParameters: 72 | admin_fee: uint256 73 | mid_fee: uint256 74 | out_fee: uint256 75 | fee_gamma: uint256 76 | allowed_extra_profit: uint256 77 | adjustment_step: uint256 78 | ma_half_time: uint256 79 | 80 | event RampAgamma: 81 | initial_A: uint256 82 | future_A: uint256 83 | initial_gamma: uint256 84 | future_gamma: uint256 85 | initial_time: uint256 86 | future_time: uint256 87 | 88 | event StopRampA: 89 | current_A: uint256 90 | current_gamma: uint256 91 | time: uint256 92 | 93 | event ClaimAdminFee: 94 | admin: indexed(address) 95 | tokens: uint256 96 | 97 | 98 | N_COINS: constant(uint256) = 2 99 | PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to 100 | A_MULTIPLIER: constant(uint256) = 10000 101 | 102 | token: immutable(address) 103 | coins: immutable(address[N_COINS]) 104 | underlying_coins: public(address[N_COINS]) 105 | 106 | price_scale: public(uint256) # Internal price scale 107 | _price_oracle: uint256 # Price target given by MA 108 | 109 | last_prices: public(uint256) 110 | last_prices_timestamp: public(uint256) 111 | 112 | initial_A_gamma: public(uint256) 113 | future_A_gamma: public(uint256) 114 | initial_A_gamma_time: public(uint256) 115 | future_A_gamma_time: public(uint256) 116 | 117 | allowed_extra_profit: public(uint256) # 2 * 10**12 - recommended value 118 | future_allowed_extra_profit: public(uint256) 119 | 120 | fee_gamma: public(uint256) 121 | future_fee_gamma: public(uint256) 122 | 123 | adjustment_step: public(uint256) 124 | future_adjustment_step: public(uint256) 125 | 126 | ma_half_time: public(uint256) 127 | future_ma_half_time: public(uint256) 128 | 129 | mid_fee: public(uint256) 130 | out_fee: public(uint256) 131 | admin_fee: public(uint256) 132 | future_mid_fee: public(uint256) 133 | future_out_fee: public(uint256) 134 | future_admin_fee: public(uint256) 135 | 136 | balances: public(uint256[N_COINS]) 137 | D: public(uint256) 138 | 139 | owner: public(address) 140 | future_owner: public(address) 141 | 142 | xcp_profit: public(uint256) 143 | xcp_profit_a: public(uint256) # Full profit at last claim of admin fees 144 | virtual_price: public(uint256) # Cached (fast to read) virtual price also used internally 145 | not_adjusted: bool 146 | 147 | is_killed: public(bool) 148 | kill_deadline: public(uint256) 149 | transfer_ownership_deadline: public(uint256) 150 | admin_actions_deadline: public(uint256) 151 | 152 | admin_fee_receiver: public(address) 153 | 154 | KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 155 | ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 156 | MIN_RAMP_TIME: constant(uint256) = 86400 157 | 158 | MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 159 | MIN_FEE: constant(uint256) = 5 * 10 ** 5 # 0.5 bps 160 | MAX_FEE: constant(uint256) = 10 * 10 ** 9 161 | MAX_A_CHANGE: constant(uint256) = 10 162 | NOISE_FEE: constant(uint256) = 10**5 # 0.1 bps 163 | 164 | MIN_GAMMA: constant(uint256) = 10**10 165 | MAX_GAMMA: constant(uint256) = 2 * 10**16 166 | 167 | MIN_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER / 10 168 | MAX_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER * 100000 169 | 170 | 171 | # This must be changed for different N_COINS 172 | # For example: 173 | # N_COINS = 3 -> 1 (10**18 -> 10**18) 174 | # N_COINS = 4 -> 10**8 (10**18 -> 10**10) 175 | # PRICE_PRECISION_MUL: constant(uint256) = 1 176 | PRECISIONS: immutable(uint256[N_COINS]) 177 | 178 | EXP_PRECISION: constant(uint256) = 10**10 179 | 180 | 181 | @external 182 | def __init__( 183 | owner: address, 184 | admin_fee_receiver: address, 185 | A: uint256, 186 | gamma: uint256, 187 | mid_fee: uint256, 188 | out_fee: uint256, 189 | allowed_extra_profit: uint256, 190 | fee_gamma: uint256, 191 | adjustment_step: uint256, 192 | admin_fee: uint256, 193 | ma_half_time: uint256, 194 | initial_price: uint256, 195 | _token: address, 196 | _coins: address[N_COINS] 197 | ): 198 | self.owner = owner 199 | 200 | # Pack A and gamma: 201 | # shifted A + gamma 202 | A_gamma: uint256 = shift(A, 128) 203 | A_gamma = (A_gamma | gamma) 204 | self.initial_A_gamma = A_gamma 205 | self.future_A_gamma = A_gamma 206 | 207 | self.mid_fee = mid_fee 208 | self.out_fee = out_fee 209 | self.allowed_extra_profit = allowed_extra_profit 210 | self.fee_gamma = fee_gamma 211 | self.adjustment_step = adjustment_step 212 | self.admin_fee = admin_fee 213 | 214 | self.price_scale = initial_price 215 | self._price_oracle = initial_price 216 | self.last_prices = initial_price 217 | self.last_prices_timestamp = block.timestamp 218 | self.ma_half_time = ma_half_time 219 | 220 | self.xcp_profit_a = 10**18 221 | 222 | self.kill_deadline = block.timestamp + KILL_DEADLINE_DT 223 | 224 | self.admin_fee_receiver = admin_fee_receiver 225 | 226 | token = _token 227 | coins = _coins 228 | PRECISIONS = [10 ** (18 - ERC20(_coins[0]).decimals()), 229 | 10 ** (18 - ERC20(_coins[1]).decimals())] 230 | for i in range(N_COINS): 231 | coin: address = coins[i] 232 | underlying_coin: address = ERC4626(coin).asset() 233 | self.underlying_coins[i] = underlying_coin 234 | ERC20(underlying_coin).approve(coin, MAX_UINT256) 235 | 236 | ### Math functions 237 | @internal 238 | @pure 239 | def geometric_mean(unsorted_x: uint256[N_COINS], sort: bool) -> uint256: 240 | """ 241 | (x[0] * x[1] * ...) ** (1/N) 242 | """ 243 | x: uint256[N_COINS] = unsorted_x 244 | if sort and x[0] < x[1]: 245 | x = [unsorted_x[1], unsorted_x[0]] 246 | D: uint256 = x[0] 247 | diff: uint256 = 0 248 | for i in range(255): 249 | D_prev: uint256 = D 250 | # tmp: uint256 = 10**18 251 | # for _x in x: 252 | # tmp = tmp * _x / D 253 | # D = D * ((N_COINS - 1) * 10**18 + tmp) / (N_COINS * 10**18) 254 | # line below makes it for 2 coins 255 | D = unsafe_div(D + x[0] * x[1] / D, N_COINS) 256 | if D > D_prev: 257 | diff = unsafe_sub(D, D_prev) 258 | else: 259 | diff = unsafe_sub(D_prev, D) 260 | if diff <= 1 or diff * 10**18 < D: 261 | return D 262 | raise "Did not converge" 263 | 264 | 265 | @internal 266 | @view 267 | def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS]) -> uint256: 268 | """ 269 | Finding the invariant using Newton method. 270 | ANN is higher by the factor A_MULTIPLIER 271 | ANN is already A * N**N 272 | 273 | Currently uses 60k gas 274 | """ 275 | # Safety checks 276 | assert ANN > MIN_A - 1 and ANN < MAX_A + 1 # dev: unsafe values A 277 | assert gamma > MIN_GAMMA - 1 and gamma < MAX_GAMMA + 1 # dev: unsafe values gamma 278 | 279 | # Initial value of invariant D is that for constant-product invariant 280 | x: uint256[N_COINS] = x_unsorted 281 | if x[0] < x[1]: 282 | x = [x_unsorted[1], x_unsorted[0]] 283 | 284 | assert x[0] > 10**9 - 1 and x[0] < 10**15 * 10**18 + 1 # dev: unsafe values x[0] 285 | assert x[1] * 10**18 / x[0] > 10**14-1 # dev: unsafe values x[i] (input) 286 | 287 | D: uint256 = N_COINS * self.geometric_mean(x, False) 288 | S: uint256 = x[0] + x[1] 289 | __g1k0: uint256 = gamma + 10**18 290 | 291 | for i in range(255): 292 | D_prev: uint256 = D 293 | assert D > 0 294 | # Unsafe ivision by D is now safe 295 | 296 | # K0: uint256 = 10**18 297 | # for _x in x: 298 | # K0 = K0 * _x * N_COINS / D 299 | # collapsed for 2 coins 300 | K0: uint256 = unsafe_div(unsafe_div((10**18 * N_COINS**2) * x[0], D) * x[1], D) 301 | 302 | _g1k0: uint256 = __g1k0 303 | if _g1k0 > K0: 304 | _g1k0 = unsafe_sub(_g1k0, K0) + 1 # > 0 305 | else: 306 | _g1k0 = unsafe_sub(K0, _g1k0) + 1 # > 0 307 | 308 | # D / (A * N**N) * _g1k0**2 / gamma**2 309 | mul1: uint256 = unsafe_div(unsafe_div(unsafe_div(10**18 * D, gamma) * _g1k0, gamma) * _g1k0 * A_MULTIPLIER, ANN) 310 | 311 | # 2*N*K0 / _g1k0 312 | mul2: uint256 = unsafe_div(((2 * 10**18) * N_COINS) * K0, _g1k0) 313 | 314 | neg_fprime: uint256 = (S + unsafe_div(S * mul2, 10**18)) + mul1 * N_COINS / K0 - unsafe_div(mul2 * D, 10**18) 315 | 316 | # D -= f / fprime 317 | D_plus: uint256 = D * (neg_fprime + S) / neg_fprime 318 | D_minus: uint256 = D*D / neg_fprime 319 | if 10**18 > K0: 320 | D_minus += unsafe_div(D * (mul1 / neg_fprime), 10**18) * unsafe_sub(10**18, K0) / K0 321 | else: 322 | D_minus -= unsafe_div(D * (mul1 / neg_fprime), 10**18) * unsafe_sub(K0, 10**18) / K0 323 | 324 | if D_plus > D_minus: 325 | D = unsafe_sub(D_plus, D_minus) 326 | else: 327 | D = unsafe_div(unsafe_sub(D_minus, D_plus), 2) 328 | 329 | diff: uint256 = 0 330 | if D > D_prev: 331 | diff = unsafe_sub(D, D_prev) 332 | else: 333 | diff = unsafe_sub(D_prev, D) 334 | if diff * 10**14 < max(10**16, D): # Could reduce precision for gas efficiency here 335 | # Test that we are safe with the next newton_y 336 | for _x in x: 337 | frac: uint256 = _x * 10**18 / D 338 | assert (frac > 10**16 - 1) and (frac < 10**20 + 1) # dev: unsafe values x[i] 339 | return D 340 | 341 | raise "Did not converge" 342 | 343 | 344 | @internal 345 | @pure 346 | def newton_y(ANN: uint256, gamma: uint256, x: uint256[N_COINS], D: uint256, i: uint256) -> uint256: 347 | """ 348 | Calculating x[i] given other balances x[0..N_COINS-1] and invariant D 349 | ANN = A * N**N 350 | """ 351 | # Safety checks 352 | assert ANN > MIN_A - 1 and ANN < MAX_A + 1 # dev: unsafe values A 353 | assert gamma > MIN_GAMMA - 1 and gamma < MAX_GAMMA + 1 # dev: unsafe values gamma 354 | assert D > 10**17 - 1 and D < 10**15 * 10**18 + 1 # dev: unsafe values D 355 | 356 | x_j: uint256 = x[1 - i] 357 | y: uint256 = D**2 / (x_j * N_COINS**2) 358 | K0_i: uint256 = (10**18 * N_COINS) * x_j / D 359 | # S_i = x_j 360 | 361 | # frac = x_j * 1e18 / D => frac = K0_i / N_COINS 362 | assert (K0_i > 10**16*N_COINS - 1) and (K0_i < 10**20*N_COINS + 1) # dev: unsafe values x[i] 363 | 364 | # x_sorted: uint256[N_COINS] = x 365 | # x_sorted[i] = 0 366 | # x_sorted = self.sort(x_sorted) # From high to low 367 | # x[not i] instead of x_sorted since x_soted has only 1 element 368 | 369 | convergence_limit: uint256 = max(max(x_j / 10**14, D / 10**14), 100) 370 | 371 | __g1k0: uint256 = gamma + 10**18 372 | 373 | for j in range(255): 374 | y_prev: uint256 = y 375 | 376 | K0: uint256 = unsafe_div(K0_i * y * N_COINS, D) 377 | S: uint256 = x_j + y 378 | 379 | _g1k0: uint256 = __g1k0 380 | if _g1k0 > K0: 381 | _g1k0 = unsafe_sub(_g1k0, K0) + 1 382 | else: 383 | _g1k0 = unsafe_sub(K0, _g1k0) + 1 384 | 385 | # D / (A * N**N) * _g1k0**2 / gamma**2 386 | mul1: uint256 = unsafe_div(unsafe_div(unsafe_div(10**18 * D, gamma) * _g1k0, gamma) * _g1k0 * A_MULTIPLIER, ANN) 387 | 388 | # 2*K0 / _g1k0 389 | mul2: uint256 = unsafe_div(10**18 + (2 * 10**18) * K0, _g1k0) 390 | 391 | yfprime: uint256 = 10**18 * y + S * mul2 + mul1 392 | _dyfprime: uint256 = D * mul2 393 | if yfprime < _dyfprime: 394 | y = unsafe_div(y_prev, 2) 395 | continue 396 | else: 397 | yfprime = unsafe_sub(yfprime, _dyfprime) 398 | fprime: uint256 = yfprime / y 399 | 400 | # y -= f / f_prime; y = (y * fprime - f) / fprime 401 | # y = (yfprime + 10**18 * D - 10**18 * S) // fprime + mul1 // fprime * (10**18 - K0) // K0 402 | y_minus: uint256 = mul1 / fprime 403 | y_plus: uint256 = (yfprime + 10**18 * D) / fprime + y_minus * 10**18 / K0 404 | y_minus += 10**18 * S / fprime 405 | 406 | if y_plus < y_minus: 407 | y = unsafe_div(y_prev, 2) 408 | else: 409 | y = unsafe_sub(y_plus, y_minus) 410 | 411 | diff: uint256 = 0 412 | if y > y_prev: 413 | diff = unsafe_sub(y, y_prev) 414 | else: 415 | diff = unsafe_sub(y_prev, y) 416 | if diff < max(convergence_limit, unsafe_div(y, 10**14)): 417 | frac: uint256 = unsafe_div(y * 10**18, D) 418 | assert (frac > 10**16 - 1) and (frac < 10**20 + 1) # dev: unsafe value for y 419 | return y 420 | 421 | raise "Did not converge" 422 | 423 | 424 | @internal 425 | @pure 426 | def halfpow(power: uint256) -> uint256: 427 | """ 428 | 1e18 * 0.5 ** (power/1e18) 429 | 430 | Inspired by: https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol#L128 431 | """ 432 | intpow: uint256 = unsafe_div(power, 10**18) 433 | if intpow > 59: 434 | return 0 435 | otherpow: uint256 = unsafe_sub(power, unsafe_mul(intpow, 10**18)) # < 10**18 436 | # result: uint256 = unsafe_div(10**18, pow_mod256(2, intpow)) 437 | result: uint256 = pow_mod256(2, intpow) 438 | result = unsafe_div(10**18, result) 439 | if otherpow == 0: 440 | return result 441 | 442 | term: uint256 = 10**18 443 | S: uint256 = 10**18 444 | neg: bool = False 445 | 446 | for i in range(1, 256): 447 | K: uint256 = unsafe_mul(i, 10**18) # <= 255 * 10**18; >= 10**18 448 | c: uint256 = unsafe_sub(K, 10**18) # <= 254 * 10**18; < K 449 | if otherpow > c: # c < otherpow < 10**18 <= K -> c < K 450 | c = unsafe_sub(otherpow, c) 451 | neg = not neg 452 | else: 453 | c = unsafe_sub(c, otherpow) # c < K 454 | # c <= 254 * 10**18, < K -> (c/2) / K < 1 -> term * c/2 / K <= 10**18 455 | term = unsafe_div(unsafe_mul(term, unsafe_div(c, 2)), K) 456 | if neg: 457 | S -= term 458 | else: 459 | S += term 460 | if term < EXP_PRECISION: 461 | return unsafe_div(result * S, 10**18) 462 | 463 | raise "Did not converge" 464 | ### end of Math functions 465 | 466 | 467 | @external 468 | @view 469 | def token() -> address: 470 | return token 471 | 472 | 473 | @external 474 | @view 475 | def coins(i: uint256) -> address: 476 | _coins: address[N_COINS] = coins 477 | return _coins[i] 478 | 479 | @internal 480 | @view 481 | def xp() -> uint256[N_COINS]: 482 | return [self.balances[0] * PRECISIONS[0], 483 | unsafe_div(self.balances[1] * PRECISIONS[1] * self.price_scale, PRECISION)] 484 | 485 | 486 | @view 487 | @internal 488 | def _A_gamma() -> uint256[2]: 489 | t1: uint256 = self.future_A_gamma_time 490 | 491 | A_gamma_1: uint256 = self.future_A_gamma 492 | gamma1: uint256 = (A_gamma_1) & (2**128-1) 493 | A1: uint256 = shift(A_gamma_1, -128) 494 | 495 | if block.timestamp < t1: 496 | # handle ramping up and down of A 497 | A_gamma_0: uint256 = self.initial_A_gamma 498 | t0: uint256 = self.initial_A_gamma_time 499 | 500 | # Less readable but more compact way of writing and converting to uint256 501 | # gamma0: uint256 = bitwise_and(A_gamma_0, 2**128-1) 502 | # A0: uint256 = shift(A_gamma_0, -128) 503 | # A1 = A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) 504 | # gamma1 = gamma0 + (gamma1 - gamma0) * (block.timestamp - t0) / (t1 - t0) 505 | 506 | t1 -= t0 507 | t0 = block.timestamp - t0 508 | t2: uint256 = t1 - t0 509 | 510 | A1 = (shift(A_gamma_0, -128) * t2 + A1 * t0) / t1 511 | gamma1 = ((A_gamma_0) & (2**128-1) * t2 + gamma1 * t0) / t1 512 | 513 | return [A1, gamma1] 514 | 515 | 516 | @view 517 | @external 518 | def A() -> uint256: 519 | return self._A_gamma()[0] 520 | 521 | 522 | @view 523 | @external 524 | def gamma() -> uint256: 525 | return self._A_gamma()[1] 526 | 527 | 528 | @internal 529 | @view 530 | def _fee(xp: uint256[N_COINS]) -> uint256: 531 | """ 532 | f = fee_gamma / (fee_gamma + (1 - K)) 533 | where 534 | K = prod(x) / (sum(x) / N)**N 535 | (all normalized to 1e18) 536 | """ 537 | fee_gamma: uint256 = self.fee_gamma 538 | f: uint256 = xp[0] + xp[1] # sum 539 | f = unsafe_mul(fee_gamma, 10**18) / ( 540 | unsafe_add(fee_gamma, 10**18) - unsafe_div((10**18 * N_COINS**N_COINS) * xp[0] / f * xp[1], f) 541 | ) 542 | return unsafe_div(self.mid_fee * f + self.out_fee * (10**18 - f), 10**18) 543 | 544 | 545 | @external 546 | @view 547 | def fee() -> uint256: 548 | return self._fee(self.xp()) 549 | 550 | 551 | @internal 552 | @view 553 | def get_xcp(D: uint256) -> uint256: 554 | x: uint256[N_COINS] = [unsafe_div(D, N_COINS), D * PRECISION / (self.price_scale * N_COINS)] 555 | return self.geometric_mean(x, True) 556 | 557 | 558 | @external 559 | @view 560 | def get_virtual_price() -> uint256: 561 | return 10**18 * self.get_xcp(self.D) / CurveToken(token).totalSupply() 562 | 563 | 564 | @internal 565 | def _claim_admin_fees(): 566 | A_gamma: uint256[2] = self._A_gamma() 567 | 568 | xcp_profit: uint256 = self.xcp_profit 569 | xcp_profit_a: uint256 = self.xcp_profit_a 570 | 571 | # Gulp here 572 | _coins: address[N_COINS] = coins 573 | for i in range(N_COINS): 574 | self.balances[i] = ERC20(_coins[i]).balanceOf(self) 575 | 576 | vprice: uint256 = self.virtual_price 577 | 578 | if xcp_profit > xcp_profit_a: 579 | fees: uint256 = unsafe_div((xcp_profit - xcp_profit_a) * self.admin_fee, 2 * 10**10) 580 | if fees > 0: 581 | receiver: address = self.admin_fee_receiver 582 | if receiver != ZERO_ADDRESS: 583 | frac: uint256 = vprice * 10**18 / (vprice - fees) - 10**18 584 | claimed: uint256 = CurveToken(token).mint_relative(receiver, frac) 585 | xcp_profit -= unsafe_mul(fees, 2) 586 | self.xcp_profit = xcp_profit 587 | log ClaimAdminFee(receiver, claimed) 588 | 589 | total_supply: uint256 = CurveToken(token).totalSupply() 590 | 591 | # Recalculate D b/c we gulped 592 | D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], self.xp()) 593 | self.D = D 594 | 595 | self.virtual_price = 10**18 * self.get_xcp(D) / total_supply 596 | 597 | if xcp_profit > xcp_profit_a: 598 | self.xcp_profit_a = xcp_profit 599 | 600 | 601 | @internal 602 | @view 603 | def internal_price_oracle() -> uint256: 604 | price_oracle: uint256 = self._price_oracle 605 | last_prices_timestamp: uint256 = self.last_prices_timestamp 606 | 607 | if last_prices_timestamp < block.timestamp: 608 | ma_half_time: uint256 = self.ma_half_time 609 | last_prices: uint256 = self.last_prices 610 | alpha: uint256 = self.halfpow(unsafe_div(unsafe_mul(block.timestamp - last_prices_timestamp, 10**18), ma_half_time)) 611 | return unsafe_div(last_prices * (10**18 - alpha) + price_oracle * alpha, 10**18) 612 | 613 | else: 614 | return price_oracle 615 | 616 | 617 | @external 618 | @view 619 | def price_oracle() -> uint256: 620 | return self.internal_price_oracle() 621 | 622 | 623 | @internal 624 | def tweak_price(A_gamma: uint256[2],_xp: uint256[N_COINS], p_i: uint256, new_D: uint256): 625 | price_oracle: uint256 = self._price_oracle 626 | last_prices: uint256 = self.last_prices 627 | price_scale: uint256 = self.price_scale 628 | last_prices_timestamp: uint256 = self.last_prices_timestamp 629 | p_new: uint256 = 0 630 | 631 | if last_prices_timestamp < block.timestamp: 632 | # MA update required 633 | ma_half_time: uint256 = self.ma_half_time 634 | alpha: uint256 = self.halfpow(unsafe_div(unsafe_mul(block.timestamp - last_prices_timestamp, 10**18), ma_half_time)) 635 | price_oracle = unsafe_div(last_prices * (10**18 - alpha) + price_oracle * alpha, 10**18) 636 | self._price_oracle = price_oracle 637 | self.last_prices_timestamp = block.timestamp 638 | 639 | D_unadjusted: uint256 = new_D # Withdrawal methods know new D already 640 | if new_D == 0: 641 | # We will need this a few times (35k gas) 642 | D_unadjusted = self.newton_D(A_gamma[0], A_gamma[1], _xp) 643 | 644 | if p_i > 0: 645 | last_prices = p_i 646 | 647 | else: 648 | # calculate real prices 649 | __xp: uint256[N_COINS] = _xp 650 | dx_price: uint256 = unsafe_div(__xp[0], 10**6) 651 | __xp[0] += dx_price 652 | last_prices = price_scale * dx_price / (_xp[1] - self.newton_y(A_gamma[0], A_gamma[1], __xp, D_unadjusted, 1)) 653 | 654 | self.last_prices = last_prices 655 | 656 | total_supply: uint256 = CurveToken(token).totalSupply() 657 | old_xcp_profit: uint256 = self.xcp_profit 658 | old_virtual_price: uint256 = self.virtual_price 659 | 660 | # Update profit numbers without price adjustment first 661 | xp: uint256[N_COINS] = [unsafe_div(D_unadjusted, N_COINS), D_unadjusted * PRECISION / (N_COINS * price_scale)] 662 | xcp_profit: uint256 = 10**18 663 | virtual_price: uint256 = 10**18 664 | 665 | if old_virtual_price > 0: 666 | xcp: uint256 = self.geometric_mean(xp, True) 667 | virtual_price = 10**18 * xcp / total_supply 668 | xcp_profit = old_xcp_profit * virtual_price / old_virtual_price 669 | 670 | t: uint256 = self.future_A_gamma_time 671 | if virtual_price < old_virtual_price and t == 0: 672 | raise "Loss" 673 | if t == 1: 674 | self.future_A_gamma_time = 0 675 | 676 | self.xcp_profit = xcp_profit 677 | 678 | norm: uint256 = price_oracle * 10**18 / price_scale 679 | if norm > 10**18: 680 | norm = unsafe_sub(norm, 10**18) 681 | else: 682 | norm = unsafe_sub(10**18, norm) 683 | adjustment_step: uint256 = max(self.adjustment_step, unsafe_div(norm, 10)) 684 | 685 | needs_adjustment: bool = self.not_adjusted 686 | # if not needs_adjustment and (virtual_price-10**18 > (xcp_profit-10**18)/2 + self.allowed_extra_profit): 687 | # (re-arrange for gas efficiency) 688 | if not needs_adjustment and (virtual_price * 2 - 10**18 > xcp_profit + unsafe_mul(self.allowed_extra_profit, 2)) and (norm > adjustment_step) and (old_virtual_price > 0): 689 | needs_adjustment = True 690 | self.not_adjusted = True 691 | 692 | if needs_adjustment: 693 | if norm > adjustment_step and old_virtual_price > 0: 694 | p_new = unsafe_div(price_scale * (norm - adjustment_step) + adjustment_step * price_oracle, norm) 695 | 696 | # Calculate balances*prices 697 | xp = [_xp[0], _xp[1] * p_new / price_scale] 698 | 699 | # Calculate "extended constant product" invariant xCP and virtual price 700 | D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], xp) 701 | xp = [unsafe_div(D, N_COINS), D * PRECISION / (N_COINS * p_new)] 702 | # We reuse old_virtual_price here but it's not old anymore 703 | old_virtual_price = 10**18 * self.geometric_mean(xp, True) / total_supply 704 | 705 | # Proceed if we've got enough profit 706 | # if (old_virtual_price > 10**18) and (2 * (old_virtual_price - 10**18) > xcp_profit - 10**18): 707 | if (old_virtual_price > 10**18) and (2 * old_virtual_price - 10**18 > xcp_profit): 708 | self.price_scale = p_new 709 | self.D = D 710 | self.virtual_price = old_virtual_price 711 | 712 | return 713 | 714 | else: 715 | self.not_adjusted = False 716 | 717 | # Can instead do another flag variable if we want to save bytespace 718 | self.D = D_unadjusted 719 | self.virtual_price = virtual_price 720 | self._claim_admin_fees() 721 | 722 | return 723 | 724 | # If we are here, the price_scale adjustment did not happen 725 | # Still need to update the profit counter and D 726 | self.D = D_unadjusted 727 | self.virtual_price = virtual_price 728 | 729 | # norm appeared < adjustment_step after 730 | if needs_adjustment: 731 | self.not_adjusted = False 732 | self._claim_admin_fees() 733 | 734 | 735 | @internal 736 | def _exchange(sender: address, i: uint256, j: uint256, dx: uint256, min_dy: uint256, 737 | receiver: address, callbacker: address, callback_sig: Bytes[4], _use_underlying: bool) -> uint256: 738 | assert not self.is_killed # dev: the pool is killed 739 | assert i != j # dev: coin index out of range 740 | assert i < N_COINS # dev: coin index out of range 741 | assert j < N_COINS # dev: coin index out of range 742 | assert dx > 0 # dev: do not exchange 0 coins 743 | 744 | A_gamma: uint256[2] = self._A_gamma() 745 | xp: uint256[N_COINS] = self.balances 746 | p: uint256 = 0 747 | dy: uint256 = 0 748 | 749 | _coins: address[N_COINS] = coins 750 | _underlying_coins: address[N_COINS] = self.underlying_coins 751 | 752 | y: uint256 = xp[j] 753 | x0: uint256 = xp[i] 754 | xp[i] = x0 + dx 755 | self.balances[i] = xp[i] 756 | 757 | price_scale: uint256 = self.price_scale 758 | 759 | xp = [xp[0] * PRECISIONS[0], xp[1] * price_scale * PRECISIONS[1] / PRECISION] 760 | 761 | prec_i: uint256 = PRECISIONS[0] 762 | prec_j: uint256 = PRECISIONS[1] 763 | if i == 1: 764 | prec_i = PRECISIONS[1] 765 | prec_j = PRECISIONS[0] 766 | 767 | # In case ramp is happening 768 | t: uint256 = self.future_A_gamma_time 769 | if t > 0: 770 | x0 *= prec_i 771 | if i > 0: 772 | x0 = x0 * price_scale / PRECISION 773 | x1: uint256 = xp[i] # Back up old value in xp 774 | xp[i] = x0 775 | self.D = self.newton_D(A_gamma[0], A_gamma[1], xp) 776 | xp[i] = x1 # And restore 777 | if block.timestamp >= t: 778 | self.future_A_gamma_time = 1 779 | 780 | dy = xp[j] - self.newton_y(A_gamma[0], A_gamma[1], xp, self.D, j) 781 | # Not defining new "y" here to have less variables / make subsequent calls cheaper 782 | xp[j] -= dy 783 | dy -= 1 784 | 785 | if j > 0: 786 | dy = dy * PRECISION / price_scale 787 | dy /= prec_j 788 | 789 | dy -= self._fee(xp) * dy / 10**10 790 | assert dy >= min_dy, "Slippage" 791 | y -= dy 792 | 793 | self.balances[j] = y 794 | 795 | # Transfer input and output at the same time 796 | if callback_sig == b"\x00\x00\x00\x00": 797 | if _use_underlying: 798 | assert ERC20(self.underlying_coins[i]).transferFrom(sender, self, dx) 799 | ERC4626(_coins[i]).deposit(dx, self) 800 | else: 801 | assert ERC20(_coins[i]).transferFrom(sender, self, dx) 802 | else: 803 | c: address = _coins[i] 804 | b: uint256 = ERC20(c).balanceOf(self) 805 | raw_call(callbacker, 806 | concat( 807 | callback_sig, 808 | convert(sender, bytes32), 809 | convert(receiver, bytes32), 810 | convert(c, bytes32), 811 | convert(dx, bytes32), 812 | convert(dy, bytes32) 813 | ) 814 | ) 815 | assert ERC20(c).balanceOf(self) - b == dx # dev: callback didn't give us coins 816 | 817 | if _use_underlying: 818 | ERC4626(_coins[j]).withdraw(dx, receiver, self) 819 | else: 820 | assert ERC20(_coins[j]).transfer(receiver, dy) 821 | 822 | y *= prec_j 823 | if j > 0: 824 | y = y * price_scale / PRECISION 825 | xp[j] = y 826 | 827 | # Calculate price 828 | if dx > 10**5 and dy > 10**5: 829 | _dx: uint256 = dx * prec_i 830 | _dy: uint256 = dy * prec_j 831 | if i == 0: 832 | p = _dx * 10**18 / _dy 833 | else: # j == 0 834 | p = _dy * 10**18 / _dx 835 | 836 | self.tweak_price(A_gamma, xp, p, 0) 837 | 838 | log TokenExchange(sender, i, dx, j, dy) 839 | 840 | return dy 841 | 842 | 843 | @external 844 | @nonreentrant('lock') 845 | def exchange(i: uint256, j: uint256, dx: uint256, min_dy: uint256, 846 | receiver: address = msg.sender) -> uint256: 847 | """ 848 | Exchange using WETH by default 849 | """ 850 | return self._exchange(msg.sender, i, j, dx, min_dy, receiver, ZERO_ADDRESS, b'\x00\x00\x00\x00', False) 851 | 852 | @external 853 | @nonreentrant('lock') 854 | def exchange_underlying(i: uint256, j: uint256, dx: uint256, min_dy: uint256, 855 | receiver: address = msg.sender) -> uint256: 856 | shares_dx: uint256 = ERC4626(coins[i]).convertToShares(dx) 857 | return self._exchange(msg.sender, i, j, shares_dx, min_dy, receiver, ZERO_ADDRESS, b'\x00\x00\x00\x00', True) 858 | 859 | 860 | @external 861 | @nonreentrant('lock') 862 | def exchange_extended(i: uint256, j: uint256, dx: uint256, min_dy: uint256, 863 | sender: address, receiver: address, cb: Bytes[4]) -> uint256: 864 | assert cb != b'\x00\x00\x00\x00' # dev: No callback specified 865 | return self._exchange(sender, i, j, dx, min_dy, receiver, msg.sender, cb, False) 866 | 867 | 868 | @view 869 | @internal 870 | def _get_dy(i: uint256, j: uint256, dx: uint256) -> uint256: 871 | assert i != j # dev: same input and output coin 872 | assert i < N_COINS # dev: coin index out of range 873 | assert j < N_COINS # dev: coin index out of range 874 | 875 | price_scale: uint256 = self.price_scale * PRECISIONS[1] 876 | xp: uint256[N_COINS] = self.balances 877 | 878 | A_gamma: uint256[2] = self._A_gamma() 879 | D: uint256 = self.D 880 | if self.future_A_gamma_time > 0: 881 | D = self.newton_D(A_gamma[0], A_gamma[1], self.xp()) 882 | 883 | xp[i] += dx 884 | xp = [xp[0] * PRECISIONS[0], xp[1] * price_scale / PRECISION] 885 | 886 | y: uint256 = self.newton_y(A_gamma[0], A_gamma[1], xp, D, j) 887 | dy: uint256 = xp[j] - y - 1 888 | xp[j] = y 889 | if j > 0: 890 | dy = dy * PRECISION / price_scale 891 | else: 892 | dy /= PRECISIONS[0] 893 | dy -= self._fee(xp) * dy / 10**10 894 | 895 | return dy 896 | 897 | 898 | @external 899 | @view 900 | def get_dy(i: uint256, j: uint256, dx: uint256) -> uint256: 901 | return self._get_dy(i, j, dx) 902 | 903 | @external 904 | @view 905 | def get_dy_underlying(i: uint256, j: uint256, dx: uint256) -> uint256: 906 | shares_dx: uint256 = ERC4626(self.underlying_coins[i]).convertToShares(dx) 907 | shares_dy: uint256 = self._get_dy(i, j, shares_dx) 908 | return ERC4626(self.underlying_coins[i]).convertToAssets(shares_dy) 909 | 910 | @view 911 | @internal 912 | def _calc_token_fee(amounts: uint256[N_COINS], xp: uint256[N_COINS]) -> uint256: 913 | # fee = sum(amounts_i - avg(amounts)) * fee' / sum(amounts) 914 | fee: uint256 = self._fee(xp) * N_COINS / (4 * (N_COINS-1)) 915 | S: uint256 = 0 916 | for _x in amounts: 917 | S += _x 918 | avg: uint256 = S / N_COINS 919 | Sdiff: uint256 = 0 920 | for _x in amounts: 921 | if _x > avg: 922 | Sdiff += _x - avg 923 | else: 924 | Sdiff += avg - _x 925 | return fee * Sdiff / S + NOISE_FEE 926 | 927 | 928 | @external 929 | @nonreentrant('lock') 930 | def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256, receiver: address = msg.sender, use_underlying: bool = False) -> uint256: 931 | assert not self.is_killed # dev: the pool is killed 932 | assert amounts[0] > 0 or amounts[1] > 0 # dev: no coins to add 933 | 934 | A_gamma: uint256[2] = self._A_gamma() 935 | 936 | _coins: address[N_COINS] = coins 937 | _underlying_coins: address[N_COINS] = self.underlying_coins 938 | 939 | xp: uint256[N_COINS] = self.balances 940 | amountsp: uint256[N_COINS] = empty(uint256[N_COINS]) 941 | xx: uint256[N_COINS] = empty(uint256[N_COINS]) 942 | d_token: uint256 = 0 943 | d_token_fee: uint256 = 0 944 | old_D: uint256 = 0 945 | 946 | xp_old: uint256[N_COINS] = xp 947 | 948 | for i in range(N_COINS): 949 | bal: uint256 = xp[i] + amounts[i] 950 | xp[i] = bal 951 | self.balances[i] = bal 952 | xx = xp 953 | 954 | price_scale: uint256 = self.price_scale * PRECISIONS[1] 955 | xp = [xp[0] * PRECISIONS[0], xp[1] * price_scale / PRECISION] 956 | xp_old = [xp_old[0] * PRECISIONS[0], xp_old[1] * price_scale / PRECISION] 957 | 958 | for i in range(N_COINS): 959 | if amounts[i] > 0: 960 | if use_underlying: 961 | assert ERC20(_underlying_coins[i]).transferFrom(msg.sender, self, amounts[i]) 962 | ERC4626(_coins[i]).deposit(amounts[i], self) 963 | amountsp[i] = xp[i] - xp_old[i] 964 | else: 965 | assert ERC20(coins[i]).transferFrom(msg.sender, self, amounts[i]) 966 | 967 | assert amounts[0] > 0 or amounts[1] > 0 # dev: no coins to add 968 | 969 | t: uint256 = self.future_A_gamma_time 970 | if t > 0: 971 | old_D = self.newton_D(A_gamma[0], A_gamma[1], xp_old) 972 | if block.timestamp >= t: 973 | self.future_A_gamma_time = 1 974 | else: 975 | old_D = self.D 976 | 977 | D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], xp) 978 | 979 | token_supply: uint256 = CurveToken(token).totalSupply() 980 | if old_D > 0: 981 | d_token = token_supply * D / old_D - token_supply 982 | else: 983 | d_token = self.get_xcp(D) # making initial virtual price equal to 1 984 | assert d_token > 0 # dev: nothing minted 985 | 986 | if old_D > 0: 987 | d_token_fee = self._calc_token_fee(amountsp, xp) * d_token / 10**10 + 1 988 | d_token -= d_token_fee 989 | token_supply += d_token 990 | CurveToken(token).mint(receiver, d_token) 991 | 992 | # Calculate price 993 | # p_i * (dx_i - dtoken / token_supply * xx_i) = sum{k!=i}(p_k * (dtoken / token_supply * xx_k - dx_k)) 994 | # Simplified for 2 coins 995 | p: uint256 = 0 996 | if d_token > 10**5: 997 | if amounts[0] == 0 or amounts[1] == 0: 998 | S: uint256 = 0 999 | precision: uint256 = 0 1000 | ix: uint256 = 0 1001 | if amounts[0] == 0: 1002 | S = xx[0] * PRECISIONS[0] 1003 | precision = PRECISIONS[1] 1004 | ix = 1 1005 | else: 1006 | S = xx[1] * PRECISIONS[1] 1007 | precision = PRECISIONS[0] 1008 | S = S * d_token / token_supply 1009 | p = S * PRECISION / (amounts[ix] * precision - d_token * xx[ix] * precision / token_supply) 1010 | if ix == 0: 1011 | p = (10**18)**2 / p 1012 | 1013 | self.tweak_price(A_gamma, xp, p, D) 1014 | 1015 | else: 1016 | self.D = D 1017 | self.virtual_price = 10**18 1018 | self.xcp_profit = 10**18 1019 | CurveToken(token).mint(receiver, d_token) 1020 | 1021 | assert d_token >= min_mint_amount, "Slippage" 1022 | 1023 | log AddLiquidity(receiver, amounts, d_token_fee, token_supply) 1024 | 1025 | return d_token 1026 | 1027 | 1028 | @external 1029 | @nonreentrant('lock') 1030 | def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS], receiver: address = msg.sender): 1031 | """ 1032 | This withdrawal method is very safe, does no complex math 1033 | """ 1034 | _coins: address[N_COINS] = coins 1035 | total_supply: uint256 = CurveToken(token).totalSupply() 1036 | CurveToken(token).burnFrom(msg.sender, _amount) 1037 | balances: uint256[N_COINS] = self.balances 1038 | amount: uint256 = _amount - 1 # Make rounding errors favoring other LPs a tiny bit 1039 | 1040 | for i in range(N_COINS): 1041 | d_balance: uint256 = balances[i] * amount / total_supply 1042 | assert d_balance >= min_amounts[i] 1043 | self.balances[i] = balances[i] - d_balance 1044 | balances[i] = d_balance # now it's the amounts going out 1045 | assert ERC20(_coins[i]).transfer(receiver, d_balance) 1046 | 1047 | D: uint256 = self.D 1048 | self.D = D - D * amount / total_supply 1049 | 1050 | log RemoveLiquidity(msg.sender, balances, total_supply - _amount) 1051 | 1052 | 1053 | @view 1054 | @external 1055 | def calc_token_amount(amounts: uint256[N_COINS]) -> uint256: 1056 | token_supply: uint256 = CurveToken(token).totalSupply() 1057 | price_scale: uint256 = self.price_scale * PRECISIONS[1] 1058 | A_gamma: uint256[2] = self._A_gamma() 1059 | xp: uint256[N_COINS] = self.xp() 1060 | amountsp: uint256[N_COINS] = [ 1061 | amounts[0] * PRECISIONS[0], 1062 | amounts[1] * price_scale / PRECISION] 1063 | D0: uint256 = self.D 1064 | if self.future_A_gamma_time > 0: 1065 | D0 = self.newton_D(A_gamma[0], A_gamma[1], xp) 1066 | xp[0] += amountsp[0] 1067 | xp[1] += amountsp[1] 1068 | D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], xp) 1069 | d_token: uint256 = token_supply * D / D0 - token_supply 1070 | d_token -= self._calc_token_fee(amountsp, xp) * d_token / 10**10 + 1 1071 | return d_token 1072 | 1073 | 1074 | @internal 1075 | @view 1076 | def _calc_withdraw_one_coin(A_gamma: uint256[2], token_amount: uint256, i: uint256, update_D: bool, 1077 | calc_price: bool) -> (uint256, uint256, uint256, uint256[N_COINS]): 1078 | token_supply: uint256 = CurveToken(token).totalSupply() 1079 | assert token_amount <= token_supply # dev: token amount more than supply 1080 | assert i < N_COINS # dev: coin out of range 1081 | 1082 | xx: uint256[N_COINS] = self.balances 1083 | D0: uint256 = 0 1084 | 1085 | price_scale_i: uint256 = self.price_scale * PRECISIONS[1] 1086 | xp: uint256[N_COINS] = [xx[0] * PRECISIONS[0], xx[1] * price_scale_i / PRECISION] 1087 | if i == 0: 1088 | price_scale_i = PRECISION * PRECISIONS[0] 1089 | 1090 | if update_D: 1091 | D0 = self.newton_D(A_gamma[0], A_gamma[1], xp) 1092 | else: 1093 | D0 = self.D 1094 | 1095 | D: uint256 = D0 1096 | 1097 | # Charge the fee on D, not on y, e.g. reducing invariant LESS than charging the user 1098 | fee: uint256 = self._fee(xp) 1099 | dD: uint256 = token_amount * D / token_supply 1100 | D -= (dD - (fee * dD / (2 * 10**10) + 1)) 1101 | y: uint256 = self.newton_y(A_gamma[0], A_gamma[1], xp, D, i) 1102 | dy: uint256 = (xp[i] - y) * PRECISION / price_scale_i 1103 | xp[i] = y 1104 | 1105 | # Price calc 1106 | p: uint256 = 0 1107 | if calc_price and dy > 10**5 and token_amount > 10**5: 1108 | # p_i = dD / D0 * sum'(p_k * x_k) / (dy - dD / D0 * y0) 1109 | S: uint256 = 0 1110 | precision: uint256 = PRECISIONS[0] 1111 | if i == 1: 1112 | S = xx[0] * PRECISIONS[0] 1113 | precision = PRECISIONS[1] 1114 | else: 1115 | S = xx[1] * PRECISIONS[1] 1116 | S = S * dD / D0 1117 | p = S * PRECISION / (dy * precision - dD * xx[i] * precision / D0) 1118 | if i == 0: 1119 | p = (10**18)**2 / p 1120 | 1121 | return dy, p, D, xp 1122 | 1123 | 1124 | @view 1125 | @external 1126 | def calc_withdraw_one_coin(token_amount: uint256, i: uint256) -> uint256: 1127 | return self._calc_withdraw_one_coin(self._A_gamma(), token_amount, i, True, False)[0] 1128 | 1129 | 1130 | @external 1131 | @nonreentrant('lock') 1132 | def remove_liquidity_one_coin(token_amount: uint256, i: uint256, min_amount: uint256, receiver: address = msg.sender) -> uint256: 1133 | assert not self.is_killed # dev: the pool is killed 1134 | 1135 | A_gamma: uint256[2] = self._A_gamma() 1136 | 1137 | dy: uint256 = 0 1138 | D: uint256 = 0 1139 | p: uint256 = 0 1140 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 1141 | future_A_gamma_time: uint256 = self.future_A_gamma_time 1142 | dy, p, D, xp = self._calc_withdraw_one_coin(A_gamma, token_amount, i, (future_A_gamma_time > 0), True) 1143 | assert dy >= min_amount, "Slippage" 1144 | 1145 | if block.timestamp >= future_A_gamma_time: 1146 | self.future_A_gamma_time = 1 1147 | 1148 | self.balances[i] -= dy 1149 | CurveToken(token).burnFrom(msg.sender, token_amount) 1150 | 1151 | _coins: address[N_COINS] = coins 1152 | assert ERC20(_coins[i]).transfer(receiver, dy) 1153 | 1154 | self.tweak_price(A_gamma, xp, p, D) 1155 | 1156 | log RemoveLiquidityOne(msg.sender, token_amount, i, dy) 1157 | 1158 | return dy 1159 | 1160 | 1161 | @external 1162 | @nonreentrant('lock') 1163 | def claim_admin_fees(): 1164 | self._claim_admin_fees() 1165 | 1166 | 1167 | # Admin parameters 1168 | @external 1169 | def ramp_A_gamma(future_A: uint256, future_gamma: uint256, future_time: uint256): 1170 | assert msg.sender == self.owner # dev: only owner 1171 | assert block.timestamp > self.initial_A_gamma_time + (MIN_RAMP_TIME-1) 1172 | assert future_time > block.timestamp + (MIN_RAMP_TIME-1) # dev: insufficient time 1173 | 1174 | A_gamma: uint256[2] = self._A_gamma() 1175 | initial_A_gamma: uint256 = shift(A_gamma[0], 128) 1176 | initial_A_gamma = (initial_A_gamma) | (A_gamma[1]) 1177 | 1178 | assert future_A > MIN_A-1 1179 | assert future_A < MAX_A+1 1180 | assert future_gamma > MIN_GAMMA-1 1181 | assert future_gamma < MAX_GAMMA+1 1182 | 1183 | ratio: uint256 = 10**18 * future_A / A_gamma[0] 1184 | assert ratio < 10**18 * MAX_A_CHANGE + 1 1185 | assert ratio > 10**18 / MAX_A_CHANGE - 1 1186 | 1187 | ratio = 10**18 * future_gamma / A_gamma[1] 1188 | assert ratio < 10**18 * MAX_A_CHANGE + 1 1189 | assert ratio > 10**18 / MAX_A_CHANGE - 1 1190 | 1191 | self.initial_A_gamma = initial_A_gamma 1192 | self.initial_A_gamma_time = block.timestamp 1193 | 1194 | future_A_gamma: uint256 = shift(future_A, 128) 1195 | future_A_gamma = (future_A_gamma | future_gamma) 1196 | self.future_A_gamma_time = future_time 1197 | self.future_A_gamma = future_A_gamma 1198 | 1199 | log RampAgamma(A_gamma[0], future_A, A_gamma[1], future_gamma, block.timestamp, future_time) 1200 | 1201 | 1202 | @external 1203 | def stop_ramp_A_gamma(): 1204 | assert msg.sender == self.owner # dev: only owner 1205 | 1206 | A_gamma: uint256[2] = self._A_gamma() 1207 | current_A_gamma: uint256 = shift(A_gamma[0], 128) 1208 | current_A_gamma = (current_A_gamma | A_gamma[1]) 1209 | self.initial_A_gamma = current_A_gamma 1210 | self.future_A_gamma = current_A_gamma 1211 | self.initial_A_gamma_time = block.timestamp 1212 | self.future_A_gamma_time = block.timestamp 1213 | # now (block.timestamp < t1) is always False, so we return saved A 1214 | 1215 | log StopRampA(A_gamma[0], A_gamma[1], block.timestamp) 1216 | 1217 | 1218 | @external 1219 | def commit_new_parameters( 1220 | _new_mid_fee: uint256, 1221 | _new_out_fee: uint256, 1222 | _new_admin_fee: uint256, 1223 | _new_fee_gamma: uint256, 1224 | _new_allowed_extra_profit: uint256, 1225 | _new_adjustment_step: uint256, 1226 | _new_ma_half_time: uint256, 1227 | ): 1228 | assert msg.sender == self.owner # dev: only owner 1229 | assert self.admin_actions_deadline == 0 # dev: active action 1230 | 1231 | new_mid_fee: uint256 = _new_mid_fee 1232 | new_out_fee: uint256 = _new_out_fee 1233 | new_admin_fee: uint256 = _new_admin_fee 1234 | new_fee_gamma: uint256 = _new_fee_gamma 1235 | new_allowed_extra_profit: uint256 = _new_allowed_extra_profit 1236 | new_adjustment_step: uint256 = _new_adjustment_step 1237 | new_ma_half_time: uint256 = _new_ma_half_time 1238 | 1239 | # Fees 1240 | if new_out_fee < MAX_FEE+1: 1241 | assert new_out_fee > MIN_FEE-1 # dev: fee is out of range 1242 | else: 1243 | new_out_fee = self.out_fee 1244 | if new_mid_fee > MAX_FEE: 1245 | new_mid_fee = self.mid_fee 1246 | assert new_mid_fee <= new_out_fee # dev: mid-fee is too high 1247 | if new_admin_fee > MAX_ADMIN_FEE: 1248 | new_admin_fee = self.admin_fee 1249 | 1250 | # AMM parameters 1251 | if new_fee_gamma < 10**18: 1252 | assert new_fee_gamma > 0 # dev: fee_gamma out of range [1 .. 10**18] 1253 | else: 1254 | new_fee_gamma = self.fee_gamma 1255 | if new_allowed_extra_profit > 10**18: 1256 | new_allowed_extra_profit = self.allowed_extra_profit 1257 | if new_adjustment_step > 10**18: 1258 | new_adjustment_step = self.adjustment_step 1259 | 1260 | # MA 1261 | if new_ma_half_time < 7*86400: 1262 | assert new_ma_half_time > 0 # dev: MA time should be longer than 1 second 1263 | else: 1264 | new_ma_half_time = self.ma_half_time 1265 | 1266 | _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY 1267 | self.admin_actions_deadline = _deadline 1268 | 1269 | self.future_admin_fee = new_admin_fee 1270 | self.future_mid_fee = new_mid_fee 1271 | self.future_out_fee = new_out_fee 1272 | self.future_fee_gamma = new_fee_gamma 1273 | self.future_allowed_extra_profit = new_allowed_extra_profit 1274 | self.future_adjustment_step = new_adjustment_step 1275 | self.future_ma_half_time = new_ma_half_time 1276 | 1277 | log CommitNewParameters(_deadline, new_admin_fee, new_mid_fee, new_out_fee, 1278 | new_fee_gamma, 1279 | new_allowed_extra_profit, new_adjustment_step, 1280 | new_ma_half_time) 1281 | 1282 | 1283 | @external 1284 | @nonreentrant('lock') 1285 | def apply_new_parameters(): 1286 | assert msg.sender == self.owner # dev: only owner 1287 | assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time 1288 | assert self.admin_actions_deadline != 0 # dev: no active action 1289 | 1290 | self.admin_actions_deadline = 0 1291 | 1292 | admin_fee: uint256 = self.future_admin_fee 1293 | if self.admin_fee != admin_fee: 1294 | self._claim_admin_fees() 1295 | self.admin_fee = admin_fee 1296 | 1297 | mid_fee: uint256 = self.future_mid_fee 1298 | self.mid_fee = mid_fee 1299 | out_fee: uint256 = self.future_out_fee 1300 | self.out_fee = out_fee 1301 | fee_gamma: uint256 = self.future_fee_gamma 1302 | self.fee_gamma = fee_gamma 1303 | allowed_extra_profit: uint256 = self.future_allowed_extra_profit 1304 | self.allowed_extra_profit = allowed_extra_profit 1305 | adjustment_step: uint256 = self.future_adjustment_step 1306 | self.adjustment_step = adjustment_step 1307 | ma_half_time: uint256 = self.future_ma_half_time 1308 | self.ma_half_time = ma_half_time 1309 | 1310 | log NewParameters(admin_fee, mid_fee, out_fee, 1311 | fee_gamma, 1312 | allowed_extra_profit, adjustment_step, 1313 | ma_half_time) 1314 | 1315 | 1316 | @external 1317 | def revert_new_parameters(): 1318 | assert msg.sender == self.owner # dev: only owner 1319 | 1320 | self.admin_actions_deadline = 0 1321 | 1322 | 1323 | @external 1324 | def commit_transfer_ownership(_owner: address): 1325 | assert msg.sender == self.owner # dev: only owner 1326 | assert self.transfer_ownership_deadline == 0 # dev: active transfer 1327 | 1328 | _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY 1329 | self.transfer_ownership_deadline = _deadline 1330 | self.future_owner = _owner 1331 | 1332 | log CommitNewAdmin(_deadline, _owner) 1333 | 1334 | 1335 | @external 1336 | def apply_transfer_ownership(): 1337 | assert msg.sender == self.owner # dev: only owner 1338 | assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time 1339 | assert self.transfer_ownership_deadline != 0 # dev: no active transfer 1340 | 1341 | self.transfer_ownership_deadline = 0 1342 | _owner: address = self.future_owner 1343 | self.owner = _owner 1344 | 1345 | log NewAdmin(_owner) 1346 | 1347 | 1348 | @external 1349 | def revert_transfer_ownership(): 1350 | assert msg.sender == self.owner # dev: only owner 1351 | 1352 | self.transfer_ownership_deadline = 0 1353 | 1354 | 1355 | @external 1356 | def kill_me(): 1357 | assert msg.sender == self.owner # dev: only owner 1358 | assert self.kill_deadline > block.timestamp # dev: deadline has passed 1359 | self.is_killed = True 1360 | 1361 | 1362 | @external 1363 | def unkill_me(): 1364 | assert msg.sender == self.owner # dev: only owner 1365 | self.is_killed = False 1366 | 1367 | 1368 | @external 1369 | def set_admin_fee_receiver(_admin_fee_receiver: address): 1370 | assert msg.sender == self.owner # dev: only owner 1371 | self.admin_fee_receiver = _admin_fee_receiver 1372 | 1373 | 1374 | @internal 1375 | @pure 1376 | def sqrt_int(x: uint256) -> uint256: 1377 | """ 1378 | Originating from: https://github.com/vyperlang/vyper/issues/1266 1379 | """ 1380 | 1381 | if x == 0: 1382 | return 0 1383 | 1384 | z: uint256 = (x + 10**18) / 2 1385 | y: uint256 = x 1386 | 1387 | for i in range(256): 1388 | if z == y: 1389 | return y 1390 | y = z 1391 | z = (x * 10**18 / z + z) / 2 1392 | 1393 | raise "Did not converge" 1394 | 1395 | 1396 | @external 1397 | @view 1398 | def lp_price() -> uint256: 1399 | """ 1400 | Approximate LP token price 1401 | """ 1402 | return 2 * self.virtual_price * self.sqrt_int(self.internal_price_oracle()) / 10**18 1403 | -------------------------------------------------------------------------------- /notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "8dff0bdf", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import boa\n", 11 | "import seaborn as sns\n", 12 | "import pandas as pd\n", 13 | "from scripts.setup import setup" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "id": "d19ff788", 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "deployment = setup()\n", 24 | "(deployer, user, tokens, vault_tokens, pool) = (deployment.deployer, deployment.user, deployment.erc20_list, deployment.erc4626_list, deployment.pool)" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 3, 30 | "id": "89cdea43", 31 | "metadata": {}, 32 | "outputs": [ 33 | { 34 | "data": { 35 | "image/png": "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\n", 36 | "text/plain": [ 37 | "
" 38 | ] 39 | }, 40 | "metadata": { 41 | "needs_background": "light" 42 | }, 43 | "output_type": "display_data" 44 | } 45 | ], 46 | "source": [ 47 | "def display_pool_chart(pool):\n", 48 | " data = []\n", 49 | " balances = (pool.balances(0), pool.balances(1))\n", 50 | " for i in range(len(balances)):\n", 51 | " underlying_balance = (vault_tokens[i].convertToAssets(balances[i]))\n", 52 | " data.append([vault_tokens[i].symbol(), \"Underlying balance\", underlying_balance / 10 ** 18])\n", 53 | " data.append([vault_tokens[i].symbol(), \"Shares\", balances[i] / 10 ** 18])\n", 54 | " sns.set_theme(style=\"whitegrid\")\n", 55 | " data = pd.DataFrame(data,columns=[\"asset\",\"type\",\"amount\"])\n", 56 | " g = sns.catplot(\n", 57 | " data=data, kind=\"bar\",\n", 58 | " x=\"asset\", y=\"amount\", hue=\"type\",\n", 59 | " ci=\"sd\", palette=\"dark\", alpha=.6, height=4\n", 60 | " )\n", 61 | " g.despine(left=True)\n", 62 | " g.set_axis_labels(\"Assets\", \"Token supply\")\n", 63 | " g.legend.set_title(\"CryptoSwap\")\n", 64 | " \n", 65 | "# Pool with initial liquidity\n", 66 | "display_pool_chart(pool)" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 4, 72 | "id": "a8cb0f98", 73 | "metadata": {}, 74 | "outputs": [ 75 | { 76 | "data": { 77 | "image/png": "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5eTJk7i4uFCtWrUir1FERESKnt2HlgMHDpCVlXXbibjVqlWjRo0arF+/3qp97dq1hIWF4erqWhxlioiISBGzqzH+ESNG0KBBAwIDAylTpgxHjx4lOjqawMBAHnroIQDefPNNVq5cyW+//Wbp99JLL/Hqq69y//3307x5c9auXcvBgwdZvHhxSR2KiIiIFDK7Ci3BwcGsXbuW2bNnYzabqVq1Kk899RSDBw+2jJjk5OTkmhz7yCOPkJaWxpw5c5g9ezY1a9Zk2rRpNGrUqCQOQ0RERIqAwaxLJQrd2fPXmRuzg/iE1JIupVBV969Av64BnN60nMxk2y8lt0euHt7U6d4Xz8pVS7oUERG5Dbuf0yIiIiICCi0iIiLiIBRaRERExCEotIiIiIhDUGgRERERh6DQIiIiIg5BoUVEREQcgkKLiIiIOASFFhEREXEICi0iIiLiEBRaRERExCEotIiIiIhDUGgRERERh6DQIiIiIg7BptDy3HPPsXr1atLT0wu7HhEREZE8OdvSKTY2ltdeew13d3c6depEREQEYWFhGAyGwq5PREREBLAxtGzYsIGDBw+yatUq1q9fz6pVq6hYsSKPPPIIjz76KHXr1i3sOkVEROQvzqbQAhAcHExwcDBvvvkmO3bsYNWqVSxdupT58+cTEBBAREQEPXr04L777ivMekVEROQv6p4n4jo5OdGmTRs+++wztm7dSpcuXTh+/DiTJ08mPDycyMhItm7dWgilioiIyF+ZzSMtf7R7925WrVrFhg0bSEhI4MEHH6Rnz544OzuzYsUKXnjhBYYNG8aoUaMKY3ciIiLyF2RzaDl+/DirVq1izZo1XLx4kQoVKvDYY48RERFhNadl4MCBvPPOOyxZskShRURERGxmU2iJiIjgf//7H66urnTs2JH33nuPNm3a4OSU99mm5s2bs3z58nsqVERERP7abAotXl5efPDBB3Tr1g0PD4+7rt+xY0c2bdpky65EREREABtDy6JFiwq0ftmyZalataotuxIREREBCmkibmFZt24dq1at4vDhwyQmJlK9enX69+/PE088cccb14WHh3P+/Plc7QcPHsTNza0oSxYREZFikq/QUqdOnQLf7dZgMPDbb78VqM/8+fOpWrUqY8eOpXz58uzcuZN33nmHS5cuMWLEiDv27dKlC4MGDbJqc3V1LdD+RURExH7l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Nm7caNW+YcMGq9dhYWHAzb/wg4KCcv3cbXTkdp5++mmOHDnChAkTqFGjBk2aNLFpO3/UoEEDADZt2mRpy8nJYcuWLfnexp/fj++//5769etjNBrJzMwkJyfHKrwlJyezefPmO24zPT0dg8FgNRq2bt06y4BDQbRo0QKDwcCKFStuu05YWBjHjh2jXr16uX5ftWrVKvA+b7FppOWXX35h+PDh+Pr6cuPGjVzLq1SpYjn3JSIid+btWcYh9te6dWuaNGnCiBEjGD58OA8++CCXL1/myy+/xMnJyeov//wwGo08//zzjB8/ngoVKtCqVSt27NiR6064NWvWpG/fvvz9739n8ODBNGzYkKysLE6fPs2uXbv45z//adPx1KlTh6CgIH755RdeeeUVm7bxZw8++CCdOnXio48+Ii0tjSpVqrBs2TJLaMiPf//735QpU4Z69eqxdu1afvnlF2bPng3cPN0WFBTEnDlz8PX1xdnZmdmzZ+Ph4XHH0Zxbp9beeOMNevfuzbFjx5g3b55NN8+rWbMmvXv3ZsqUKSQkJBAWFkZ6ejpbt27lpZdeonLlyowcOZInn3ySwYMH8/TTT1OxYkWuXr3Kzz//TJMmTW57Z+W7sfkpz2XK3P5Df/36dZsvZxIR+Svx8rh5d9qS2G9BOTk5MWvWLKZOncq8efO4fPkyHh4etGjRgqioKCpVqlTgbfbv35/ExESWLFlCTEwMYWFhfPTRRzz33HNW67399tvUrFmTpUuXMn36dMqVK0fNmjXp2rVrgff5R506deK3336zXOlTGD7++GM++OADPv30U1xdXXnsscd48MEH87xnTF4mT57M559/zvTp06lQoQIffvih1eTiyZMn8+677zJ27Fh8fHzo378/qampzJ0797bbDAwMZMKECUybNo2hQ4dSt25dpkyZwujRo206xnfffRd/f3+WL1/OggUL8PHxoWnTppZTP9WrV2f58uX84x//YNy4caSmpuLn50fTpk3zfb+avBjMfz63kw99+/alXLlyzJ49mxs3bhAWFsa8efMICwsjOzubxx57jPvuu485c+bYXJgjO3v+OnNjdpS6O1xW969Av64BnN60nMzkgp8HtWeuHt7U6d4Xz8pVS7oUESlGffv2xdPTk5kzZxb5fpycnFi0aFGR7qe0s2mk5fnnn2fYsGG89957luvFr127xs6d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f/89Xbt2pWvXrrmWV6pUiTVr1rB582bCwsIoX768ZZmTkxOBgYEAllMzN27csFqnXLly3H///Vy8eBGAChUq0KxZM5YuXUq/fv2oVKmS1f6uX7+Or68vAGXLlgUgKSnJap2EhAS8vLwwGAyWtrp161rVISLyV6fQIqXO5s2bSUlJITw8PM/lISEh+Pr6smrVKr777jsSEhJo0aIFlStX5sKFCyxevJi6desSEBAAQPfu3WnWrBn169fHx8eHX3/9lQ0bNtCvXz/LNt977z369OlDjx49ePrpp6lWrRpXr15l//79XLp0iVWrVgE3g4jRaGTOnDkkJSXh6upKixYtWL16NTExMTz00EPcf//9pKSksGzZMjw8PGjbtm3Rv2kiIg5Ap4ek1Bk2bBg7d+5k165dlpGNP3vjjTdYvXo1kydPZtmyZRw5coTExET8/Pxo06YNL730En5+fgDMmDGDzZs3c/r0aTIzM6lSpQoREREMHjwYFxcXyzZjY2OZNm0aO3bsID4+Hl9fX+rVq8djjz1Gly5dLOstX76cWbNmceHCBUwmEwsXLsTT05Po6Gj27t3L1atX8fT0JDg4mBEjRtCgQYOifcNERByEQouIiIg4BF3yLCIiIg5BoUVEREQcgkKLiIiIOASFFhEREXEICi0iIiLiEBRaRERExCEotIiIiIhDUGgRERERh6DQIiIiIg5BoUVEREQcgkKLiIiIOASFFhEREXEI/w9vBEv6bxXQ4wAAAABJRU5ErkJggg==\n", 78 | "text/plain": [ 79 | "
" 80 | ] 81 | }, 82 | "metadata": {}, 83 | "output_type": "display_data" 84 | } 85 | ], 86 | "source": [ 87 | "# Exchange aETH for yUSDC\n", 88 | "with boa.env.prank(user):\n", 89 | " pool.exchange(1, 0, int(3 * 10**5 * 10**18), 0)\n", 90 | " \n", 91 | "display_pool_chart(pool);" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 5, 97 | "id": "f07ab9e3", 98 | "metadata": {}, 99 | "outputs": [ 100 | { 101 | "data": { 102 | "image/png": "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QoU8PRt/5OHhQYUKFewuKiUlhWHDhuHn50d0dPRN32kE13tUsrOzycrKsqkpJSUFg8GAr6+v3XXc6hxvxWi8htHVFVfX0hVajEZXDIDRaCyF52bExWikbNmyJV2KiIjchF0z4g4cOJC4uDguXbqUZ9mFCxeIi4uzu3ckMzOT4cOHk5qaymeffYa3t/ct178xluX48eM27ceOHaNKlSp23xoSERERx2LXf5ctFguenp507tyZBx98kOrVqwPXB+Ju2LCBe++9F4vFQmxsrHUbg8FgfST5ZnJzc3nppZc4duwYixYtonLlyretpUmTJnh5ebFmzRruv/9+AHJycvj2229p166dPacnIiIiDsiu0PLRRx9Zf46Pj8+z/Pfff7dZB+4stIwfP55NmzYRFRVFWloa+/btsy6rX78+7u7uDBo0iLNnz1ofZ/bw8GD48OFER0fj7+9P3bp1iYuLIykpiaFDh9pzeiIiIuKA7AotGzZsKOw6ANi2bRsAEydOzPeYgYGBmM3mPINjhw0bhsViYe7cudZp/GNiYjQbroiISClisOhRiUJ36swV5sZtIyk5o6RLKVTVAyvQv2ttTmxYRnZackmXU6jcvXy5v3s/vCtXLelSRETkJu7qEZCkpCS2b99uMyNueHg45cuXL5TiRERERG6wO7RER0czZ84csrOzbdrd3Nx45plnePHFF++6OBEREZEb7Aot06dPZ/r06XTo0IF+/fpRo0YN4Ppjx4sWLWLmzJm4uroycuTIwqxVRERE/sLsCi1ffPEFDzzwADNmzLBpr1atGu3atWPEiBHExcUptIiIiEihsWtyubS0NNq2bXvT5e3atSM9Pd3uokRERET+zK7Q0qRJE/bv33/T5fv376dJkyZ2FyUiIiLyZ3aFlnfeeYe9e/fywQcfcPLkScxmM2azmZMnTzJhwgT27dvH+PHjC7tWERER+Quza0zLI488gsViYcGCBSxYsMD6QkOz+fqbf93d3XnkkUdstjEYDOzevfsuyxUREZG/KrtCS5cuXTAYDIVdi4iIiMhN2RVa8ptmX0RERKQo2TWmRURERKS42dXTsmLFijtar2fPnvbsXkRERCQPu0JLVFTUTZf9cayLQouIiIgUFrtCy4YNG/K0mc1mTp8+TVxcHGfPnuWjjz666+JEREREbrArtFStWjXf9mrVqhEeHs6zzz7L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3bt3s3v3bpo2bQpc74E5c+YMa9euxWQy0axZM+s2GzduJDs7mxkzZtCnTx/at29Pq1atbhrqTp48icVisWk7ceKE9Zz+SuwKLZcvX7b5or777jsaNmzIww8/zH333ceTTz7J/v37C61IERFxPM888wyenp688cYbXL58Oc/yU6dOMX/+/DvaV2RkJEeOHGHSpEkYjUa6d++e73orVqywjqeB67eSLl26ZDPNRtmyZfPcYgFo164dly5dYvXq1da23NxcFixYgKenpzVYHDp0iCtXruTZ/syZMxw9epSaNWvatIeFhfHTTz+xf/9+a49KvXr1KFeuHLNnz6ZMmTI0aNDAur7RaASwCSKpqaksX74833O+ePEi69evt35OS0tjxYoV1KtXj4CAgHy3Ka3senrojxdEbm4u//3vf20e5SpXrly+F4yIiJQe9957L5MnT2bMmDE89NBD1hlxs7Oz2bt3L2vXruXRRx+9o321b98ePz8/1q5dS7t27Wye0vkjX19f+vbty6OPPmp95Ll69eo8+eST1nUaNGjA6tWr+fDDDwkODsbT05OIiAh69+7NkiVLiIqK4pdffqFq1aqsW7eOPXv28Nprr1nH6Wzbto3o6GgiIiJo1KgRnp6enD59muXLl5Odnc0LL7xgU1PTpk2Jj4/HYDBYQ4vRaKRx48b88MMPNG/eHHd3d+v6rVu3xs3NjREjRtCnTx/S09NZtmwZFSpUyHdgbY0aNXj99dc5cOAAFSpUYPny5SQmJvLhhx/e0XdbmtgVWho0aMDSpUtp0aIFGzduJD093WbynlOnTt30ghMRkdKjY8eOrFy5kpiYGDZs2EBcXBzu7u4EBQURFRVlEyZuxd3d3TrB2s1uDQGMGDGC33//ndmzZ5Oenk54eDhvv/02ZcuWta7Tt29ffvvtN7766ivmzZtH1apViYiIoEyZMixYsIDJkyfzn//8h7S0NGrWrMmHH35oE646d+5Meno627Zt46effiI5ORkfHx9CQkJ4+umnadmypU1NN24J1apVi/Lly9u0//DDD9blN9SqVYupU6fyr3/9i48++oiKFSvy1FNP4e/vz2uvvZbnnGvUqMGbb77JpEmTOH78OIGBgfzzn/+kbdu2d/TdliYGy59vlN2BAwcO8Mwzz5CSkoLFYqFLly5MmTLFurxLly4EBwczefLkQi3WWZw6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fPIJn376KdOnT6dChQq89957NoOLP/nkE9566y2ioqLw8/NjwIABZGRkMHfu3JvuMygoiA8//JBp06YxfPhw6tWrx5QpU3jppZfsOse33nqLwMBAli1bxvz58/Hz86NZs2bWWz/Vq1dn2bJl/Otf/2L8+PFkZGQQEBBAs2bN7uplxgaLHS/R6NevH+XKlWP27NlcvXqV8PBwYmNjCQ8PJzc3l169enHPPfcwZ84cuwtzZqfOXGFu3LZSN8Nl9cAK9O9amxMblpGdVvD7oI7M3cuX+7v3w7ty1duvXAh0jTif4r5GpHj069cPb29vZs6cWeTHcXFx0dOud8munpZnn32WESNG8Pbbb1ufF09MTGT79u3MnDmTY8eOFdsLpkRERArqwIED7N69m127dt3xo8h3at26dZw7d466dety7do1Vq1axa5du5g+fXqhHuevyK7Q0r59ez788EM++OADli5dCsArr7yCxWLBy8uLjz76qMADskRERIrL448/jre3N88//zytWrUq1H17enry9ddfc+LECXJycqhVqxYff/wxDz74YKEe56/I7nlaevbsSefOndm+fTsnTpzAbDZz77330qZNG7tHcouIiBSH201kejfatm1r83oAKTx2hZZFixbRr18/PD09802Oubm5vPrqq3zyySd3XaCIiIgI2DlPy/vvv5/nrZM3ZGdnM3LkyDzP2YuIiIjcDbt6Wl544QXeeust3NzciIyMtLZnZGQwfPhwfv75Z6ZOnVpoRYqIiIjYFVqef/55srKyeO2116wvmEpOTmbYsGEcOXKE2bNn3/QdESIiIiL2sHsg7pgxY8jOzuYf//gHqampLFy4kIsXLxIbG0ujRo0Ks0YRERER+0MLwKuvvkpWVhbvvPMOFSpUYMGCBdStW7ewahMRERGxuqPQ8v777990mcFgoGzZstSrV886Z8sNhfHGTBERERG4w9CycOHC267z/fff8/3331s/GwwGhRYRkdvISksmOz2t2I/rXs4LDy/fAm+3cuVKPv/8c44fP47FYqFy5co0adKEl19+mQoVKgAQERFBhw4dNDO6FLo7Ci2HDh0q6jpERP6SstPTOLZ5JdnpqcV2TPdy3tTq8EiBQ8ucOXP45JNPGDx4MKNHj8ZisXD48GHi4+O5ePGiNbSIFJW7GtMiIiJ3Lzs91SleMLlgwQJ69epFVFSUta19+/Y888wzmM3mYqkhOzsbV1dXXFzsmmZMnNxdhZaEhAS2bt3K2bNnAahSpQrt2rWjWrVqhVKciIg4jpSUFCpVqpTvsvxCxKJFi/jss89ISUmhRYsWvP/++/j7+wPX5/WaPHky27Zt4/z581SoUIE2bdrwyiuv4O3tbd3HjVtNf/vb31i8eDHnzp1j+/bt+Pv789VXXxEbG8uJEyfw8/Pj0UcfZfTo0RiNRmu9kyZNYsuWLSQlJeHv70+TJk345z//WQTfjhQHu0PLxIkT+fzzz/OkaxcXFwYNGsSrr75618WJiIjjaNCgAV988QWBgYF06NCBgICAm667ceNGTp48yVtvvcXVq1f58MMPee+996yBITMzE5PJxJgxY/D39+fcuXPMnDmT559/ngULFtjs69tvv6V69eq8/vrruLi44OnpSWxsLB9//DGDBg0iKiqKo0eP8s9//hOTycTYsWMB+PDDD/n+++/5+9//TtWqVbl06RJbt24tui9IipxdoWXu3LnMmzePLl26MGTIEGrXrg3A0aNHmTdvHvPmzaNy5coMHjy4MGsVEZES9PbbbzNq1CjrQxaBgYE88MADDB48mMDAQJt1LRYLM2bMwN3dHYAzZ84wa9YszGYzLi4u+Pv7M378eOv6ubm5BAYG0rdvX44fP07NmjWty3JycpgzZw6enp4ApKWlMXXqVJ555hlefvllAFq3bo2bmxsTJ05k6NChlC9fngMHDvDwww/Tq1cv6766d+9eNF+OFAu7QsvSpUuJiIhgypQpNu2NGjXin//8J1lZWXzxxRcKLSIipUjdunVZtWoVP/74Iz/88AM7d+5kwYIFfPXVVyxatIh69epZ123WrJk1sADUrl2bnJwcEhMTrT00K1asYN68eZw8eZKMjAzruidOnLAJLS1atLAGFoC9e/eSkZFB165dyc3Ntba3atWKzMxMDh8+TPPmzalfvz7/+c9/CAgIoG3btppHrBSwK7ScOXOGgQMH3nR5mzZtbB5/FhGR0sHd3Z327dvTvn174Pp0F8OHD2f69OlMmzbNup6Pj0+e7QCysrIAWL9+Pa+++iq9e/dmzJgx+Pn5cenSJUaOHGld54Y/P5V09epVAJselD86d+4cAG+++Sa+vr7ExsYyadIk/va3v/Hss8/St29fe09fSphdoaVChQq3fAz60KFD1sFWIiJSerVt25b777+fo0ePFmi7tWvXUq9ePd59911r23//+9981zUYDDaffX2vP6o9bdo07rnnnjzr37hV5e3tzeuvv87rr7/O77//zueff8748eOpW7cuTZs2LVC94hju+JmxnTt3cuXKFQC6du3Kl19+yezZs2269DIyMpg9ezZffvklDz30UOFXKyIiJeby5ct52jIzMzl37hwVK1Ys0L4yMzNxc3OzaYuPj7+jbRs3bkzZsmU5f/48wcHBef6UL18+zzZBQUGMGzcOoMABSxzHHfe0DBw4kEmTJtGjRw9efPFFfvvtNz799FOmTp1qfQTu4sWL5Obm0qJFC0aPHl1kRYuIlCbu5bxvv5IDHK9Hjx488MADtGnThkqVKnHhwgUWLlzI1atXGTRoUIH21apVK959912mT59O48aN2bJlCz/++OMdbevj48Po0aP5+OOPOX/+PM2bN8doNJKQkMCGDRuIjo6mbNmy9OnTh06dOlGnTh2MRiMrVqzAzc1NvSxO7I5Di8Visf5ctmxZ5s+fz3fffWczT0ubNm1o3749ERERebrzREQkL/dyXtTq8EiJHLegRo0axaZNm5g4cSJXrlyhfPnyBAUFMW/ePFq2bFmgffXp04fTp0+zcOFCYmJiaNOmDZ988glPPvnkHW0/ZMgQKleuTGxsLAsXLsTV1ZV7772XDh06WHtwmjRpwooVKzh9+jQuLi7UrVuXmTNnWp94FedjsPwxjdzC/fffz8cff0yPHj2Kuiand+rMFebGbSMpOeP2KzuR6oEV6N+1Nic2LHOK2TsLwt3Ll/u798O7ctViOZ6uEedT3NeIiORVoHmQ1XsiIiIiJaVATw+98sorvPLKK3e0rsFg4Ndff7WrKBEREZE/K1BoadWqFTVq1CiiUkRERERurkChpWfPnkU6puXkyZPExMTw888/c/jwYWrVqsWqVatuu53FYmHOnDksXryYK1euUK9ePcaNG0doaGiR1SoiIiLFy6He7X348GG2bNlC9erVCzS6e86cOUydOpXBgwcza9YsAgICGDJkCAkJCUVYrYiIiBQnhwotERERbNmyhalTp9KgQYM72iYrK4tZs2YxZMgQBg8eTHh4OJ9++il+fn7ExMQUccUiIiJSXBwqtLi4FLycPXv2kJaWRrdu3axt7u7udOrUSa8gFxERKUXueEzLrd41VJKO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+OKLfP7557Ro0cK6/XfffcfIkSP58ssvCQ4OLqyvQESkVNHtISm14uPjqVixojUcGAwGHnroIVavXo3JZALAx8eHa9eu5bnV80c+Pj4cPnyYEydO5Ls8KSmJn376iW7dupGWlsaVK1e4cuUKV69epU2bNpw4cYILFy7cslZvb28ANm/eTE5Ojh1nKyJS+im0SKlkMpn45ptvaNGiBadPn+bkyZOcPHmSkJAQLl++zI8//ghA3759qVGjBsOGDaNdu3aMGzeOrVu32uxr9OjRpKam0qVLF3r06MFHH33EoUOHrMtPnTqFxWJhypQphIeH2/yJjo4GIDEx8Zb1Nm/enC5dujBt2jRatmzJc889x/Lly8nOzi7kb0ZExHnp9pCUStu2bWPIkCE3Xd6zZ08++ugjALKzs/nhhx/YunUrW7du5cyZMzbL4XpvyoYNG9i2bRvff/896enpjB8/nieeeIJ9+/bRu3dvhgwZQtu2bfM9XkhICF5eXje9PXTDvn372LRpE99//z2//PILderUYcmSJZQrV+4uvxEREeen0CKlUlRUFFu3buWtt97Ks2z9+vVs2rSJ7du3U6ZMGZtlZrOZd955hyVLlvDtt99SvXr1PNunp6fTv39/EhMT2bp1K4mJibRq1Yrhw4fz8ssv37KudevWMXr06JuGlj+Kj49n7NixvP/++w4xMZ6ISEnT00NS6mRmZvLtt9/StWtXunbtmmd5pUqVWLVqFRs3biQ8PJzy5ctbl7m4uBAUFARgvTVz9epVm3XKlSvHvffey7lz5wCoUKECzZs3Z8mSJfTv359KlSrZHO/KlSv4+/sDULZsWQBSU1Nt1klOTsbHxweDwWBtq1evnk0dIiJ/dQotUups3LiR9PR0IiIi8l0eGhqKv78/K1eu5JtvviE5OZmWLVtSuXJlzp49y8KFC6lXrx61a9cGoHv37jRv3pwGDRrg5+fHgQMHWLduHf3797fu8+2336Zv37706NGDJ598kmrVqnH58mX27dvH+fPnWblyJXA9iBiNRubMmUNqairu7u60bNmS+Ph44uLiePDBB7n33ntJT09n6dKleHl50a5du6L/0kREnIBuD0mpM2LECLZv386OHTusPRt/Nm7cOOLj4/nkk09YunQpv/32GykpKQQEBNC2bVteeOEFAgICAJgxYwYbN27kxIkTZGdnU6VKFSIjIxk6dChubm7WfSYkJDBt2jS2bdtGUlIS/v7+1K9fn169etGlSxfresuWLWPWrFmcPXsWk8nE559/jre3NzExMezZs4fLly/j7e1NSEgIo0aNomHDhkX7hYmIOAmFFhEREXEKeuRZREREnIJCi4iIiDgFhRYRERFxCgotIiIi4hQUWkRERMQpKLSIiIiIU1BoEREREaeg0CIiIiJOQaFFREREnIJCi4iIiDgFhRYRERFxCgotIiIi4hT+H2cKmoRjrUSQAAAAAElFTkSuQmCC\n", 103 | "text/plain": [ 104 | "
" 105 | ] 106 | }, 107 | "metadata": {}, 108 | "output_type": "display_data" 109 | } 110 | ], 111 | "source": [ 112 | "# USDC for ETH\n", 113 | "with boa.env.prank(user):\n", 114 | " pool.exchange_underlying(1, 0, int(3 * 10**5 * 10**18), 0)\n", 115 | " \n", 116 | "display_pool_chart(pool);" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 6, 122 | "id": "c692bdb7", 123 | "metadata": {}, 124 | "outputs": [ 125 | { 126 | "data": { 127 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAi0AAAF/CAYAAACFR/kTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAA9hAAAPYQGoP6dpAABT0ElEQVR4nO3dd3QU9f7/8edmUyCkEQjxkiBNiJSEElroBGkKBisIUgQRFATxokZFFBVFBb0QuEAwBKREQLxcghSRqqBIFVDx0gkBQk0PKZv9/cGP/boGMFnSNrwe53AO+5n5zLxnmUNe+cxnZgxms9mMiIiISCnnUNIFiIiIiOSHQouIiIjYBYUWERERsQsKLSIiImIXFFpERETELii0iIiIiF1QaBERERG7oNAiIiIidkGhRUREROyCY0kXYA9OnTpFVFQUv/zyC0eOHKFWrVqsXr3a5u1t2bKF2bNnc/jwYZycnLj//vv55JNPuOeeewqxahERkbJFoSUfjhw5wtatW2nUqBG5ubncyZsP/vvf//Lmm28yZMgQXnrpJdLS0ti9ezeZmZmFWLGIiEjZY9C7h/5ebm4uDg7Xr6SFh4dz6NAhm0ZaEhMT6dy5M//85z/p169fYZcpIiJSpmlOSz7cCCy3YzabiYqKolu3bjRs2JDOnTszf/58q3XWrl1Lbm4ujz/+eBFVKiIiUnYptBSSSZMmMX36dHr37k1kZCSPPPIIU6ZMISYmxrLOL7/8Qs2aNVm5ciWdOnWifv36hIWFsXXr1hKsXERExD5oTkshOH36NIsWLWLixIn06dMHgNatW3Pt2jVmzpxJnz59cHBw4OLFi5w4cYJp06bxyiuv4OPjw+LFi3nhhRdYuXIlderUKeEjERERKb000lIIduzYAUDXrl3Jycmx/GndujUXL17k3LlzwPVLSOnp6bz77rv07t2bNm3aMG3aNHx9fZk7d25JHoKIiEipp5GWQnD16lXMZjOtWrW66fJz587h5+eHh4cHgNV6Tk5ONG/enCNHjhRLrSIiIvZKoaUQeHp6YjAYWLJkCU5OTnmW16xZE4D77rvvltvQLc8iIiK3p8tDhSAkJAS4fktzYGBgnj9ubm4AdOrUCYAff/zR0jcrK4tdu3bRoEGD4i9cRETEjmikJR8yMjIsd/jEx8eTmprKunXrAGjRogU1a9akf//+vPrqqwwdOpRGjRqRnZ3NyZMn2blzJ//+978BaNCgAd26deOtt94iMTERHx8flixZwqVLlxg6dGiJHZ+IiIg90MPl8uHMmTN07tz5psu++OILWrZsidlsZvHixSxdupQTJ05QoUIFatasSffu3Rk8eLBl/fT0dD799FO++eYbUlNTadCgAa+88grBwcHFdDQiIiL2SaFFRERE7ILmtIiIiIhdUGgRERERu6DQIiIiInZBoUVERETsgkKLiIiI2AWFFhEREbELCi0iIiJiFxRaRERExC4otIiIiIhdUGgRERERu6DQIiIiInZBoUVERETsgkKLiIiI2AXHki7gz7Zu3crcuXM5evQoqamp+Pr68sADDzBq1Cjc3d1v2W/AgAH8/PPPedrXrFlD7dq1i7JkERERKSalKrQkJiYSFBTEgAED8PLy4siRI0RERHDkyBHmzZt3275Nmzbltddes2rz9/cvynJFRESkGJWq0BIWFmb1uWXLljg7O/PWW2+RkJCAr6/vLft6eHjQuHHjIq5QRERESkqpCi034+XlBUB2dnbJFlIAiUnpJKdeK+kyioSHWzm8PF1LugwREbkLlcrQYjKZyMnJ4ejRo8ycOZPQ0NC/vdTz888/07hxY0wmE40aNWLMmDE0b978jurIzMzEZDIVqI/BYOBqUhpfrd5DcnLGHe2/tPHwKM/jPYNxcTZgNptLuhwRu+HqqqAvUhhKZWjp1KkTCQkJALRr146pU6fedv3mzZsTFhZGjRo1uHDhAlFRUTzzzDMsXLiQJk2a2FzHoUOHCtzHyckJByd3zpxJ4PLVVJv3XRpVquhGWloqRxLP2dXIl0hJCw4OLukSRMoEg7kU/sp8+PBhMjIyOHr0KLNmzcLf35/o6GiMRmO++qenp9OzZ09q167N3Llzba7D1pGW8xdTmbfkB64mpdu879KooqcrQ/q15R4fN420iBSARlpECkepHGm5//77AWjSpAmBgYGEhYWxYcMGunfvnq/+rq6udOjQgfXr199RHS4uLjb1MxozMDo64uhYKr9emxkdHTEajZQvX76kSxERkbtQqX+4XEBAAE5OTpw+fbqkSxEREZESVOpDyy+//EJ2dnaBnrmSnp7Oli1bCAwMLMLKREREpDiVqusXo0aNomHDhgQEBFCuXDkOHz5MVFQUAQEBPPDAAwC88cYbrFy5kt9++w2A3bt3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KNWhZffu3axZs4bXXnvttuuFhoYSFBRE1apViYuLY/bs2fTr14+VK1dSrVo1m/d/6NChAvdxcnLCwcmd5ORkEhNTbd53aZTq5YLJlENKSjJpV6+WdDmFqnyugdS0VC5fSiQ7O7uky5EyJjg4uKRLECkTDGYbfq189tlnCQsLo0uXLpQrV64o6uL8+fM88cQT1K5dm3nz5t12PstfXbhwgR49etCrVy/eeecdm2uwdaTl/MVU5i35gatJZeuSVo1qlRj4UADHv/2SzJTEki6nULm4e1Hvof44V/TRSIsUOo20iBQOm0Za4uLieOWVV3B1daVLly6EhYUREhJy29uSCyI5OZlhw4bh5eVFREREgQILQJUqVQgODubXX3+9ozpsvV3aaMzA6OiIo2OpHsgqMKPREQNgNBrL4LEZcTAaKV++fEmXIiIit2DTT57169dz4MABVq1axbp161i1ahWVK1emZ8+ePPzww9SrV8/mgq5du8bw4cNJSUlh6dKluLu727wtERERKTtsvnsoKCiI8ePHs23bNiIjI2nVqhVLly7l0UcfpWfPnsydO5fz588XaJs5OTm89NJLHD9+nM8//xxfX1+baktISGDPnj0EBgba1F9ERERKnzse43dwcKBdu3a0a9eO5ORkJkyYwLp165g6dSqfffYZLVq0YPDgwXTs2PFvtzVx4kQ2b95MeHg4qamp7N+/37Ksfv36ODs7M2jQIM6ePcuGDRsAWL16NZs3b6ZDhw5UqVKFuLg4IiMjMRqNPPPMM3d6eCIiIlJKFMrEhN27d7Nq1SrWr19PUlISderUoXfv3jg6OrJixQqef/55RowYwZgxY267ne3btwMwefLkPMs2btyIv78/ubm5VpNj/f39uXDhAh988AEpKSm4u7vTqlUrRo8efUd3DomIiEjpYtPdQwBHjx5l1apVrF69mnPnzlGpUiV69uxJWFhYnjktb731Ft9++y07d+4slKJLu9PxV5gXs53EMnb3UHX/SjzdvTYnNy4nKzWppMspVM5untz/UH/cff1KuhQREbkFm0ZawsLC+N///oezszOdO3fm7bffpl27dre8y6dly5YsX778jgoVERGRu5tNocXDw4N3332XHj16/O2TagE6d+7Mxo0bbdmViIiICGBjaFm4cGGB1i9fvjx+fhp2FxEREduVqhcmioiIiNxKvkZa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5cqEh4ezb98+Ro4cyX333YebmxsxMTGsXbv2lts8e/YsQ4YMoWHDhkycOJEqVarg5OTE8OHDLbXn9xgTExNxdHTMU+efXb16lQULFrBgwYI8y25szxY2hZajR4/Sv39/cnNzCQoK4vTp0+Tk5ADX/2H27NlDeno6H3zwgc2FiZRlJXGba3Hw/4cXYW39OfvTOrLSUv6+gx1xruBOrY4P39Whxdvbm0uXLt102YULFwr0gxnAx8cHIM9zSf66D09PTwwGA0uWLLnpD7yaNWta/p7f+ZblypWjV69efP311wQHB/P7778zfvz4AtV/Mz4+PmRnZ5OSkmIVXAry7JXLly/j6+tr+Xzp0iWcnJyoWLEimZmZbNmyhfDwcAYMGGBZZ8mSJbfd5vfff096errVbdw5OTkkJSXlu64bvLy8yMnJ4fLly7cMLp6ennTo0IF+/frlWZafUHkrNoWWTz75BHd3d5YtWwaQ50FzHTp0uG3iE5GyeZurp3t5ALLSUshKLfh/hlK6NW/enMjISHbt2kXz5s0t7ampqezcubPAz+6655578PHxYcOGDVaXRNavX2+1XkhICHD9N/zCeoAdXL9EtHjxYj788ENq1KhBs2bN7nibDRs2BGDjxo2WOS25ubls3rw539vYsGGD1ZWKb7/9lgYNGmA0GklPTyc3N9cqvKWmprJp06bbbvPatWsYDAar0bC1a9daBhwKolWrVhgMBlasWMFzzz1303VCQkI4cuQI9evXx2g0Fngft2JTaNm1axcjR47E29ubq1ev5lletWpVy7Wvgjh16hRRUVH88ssvHDlyhFq1arF69eq/7RcaGkp8fHye9gMHDlgmAImIlFae7uXsYn9t27alWbNmjBo1ipEjR1KnTh0uXLjA559/joODg9Vv/vlhNBp57rnnmDRpEpUqVaJNmzZs3749z5Nwa9asSf/+/Xn11VcZOnQojRo1Ijs7m5MnT7Jz507+/e9/23Q8999/P4GBgezatYt//vOfNm3jr+rUqUOXLl14//33ycjIoGrVqixbtswSGvLjv//9L+XKlaN+/fqsWbOGXbt2ERkZCVy/3BYYGMjcuXPx9vbG0dGRyMhI3Nzcbjuac+PS2uuvv07fvn05cuQI0dHRNj08r2bNmvTt25dp06aRlJRESEgI165dY8uWLbz44ov4+voyevRoHn/8cYYO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06tXL8aMGcPvv//Op59+yvTp0y23wF24cIGcnBxatmzJ6NGji6xoEZGyxLmC+9+vVAr216tXLzp16kTbtm2pUqUKCQkJLFq0iKtXrzJo0KACbat169a8++67zJw5kyZNmrB161Z+/PHHfPX18PBg9OjRfPLJJ5w/f54WLVpgNBqJi4tj48aNREREUL58efr27UuXLl2oU6cORqORlStX4uTkpFEWO5bv0GI2my1/L1++PAsWLOC7776zek5L27Zt6dChA6GhoXmG80REJC/nCm7U6vhwiey3oEaNGsXmzZuZPHkyV65coWLFigQEBDB//nxatWpVoG317duXM2fOsGjRIqKiomjbti1Tp07lySefzFf/IUOG4OvrS3R0NIsWLcLR0ZF7772Xjh07WkZwmjZtysqVKzlz5gwODg7UrVuX2bNnW+54FftjMP85jdzG/fffzyeffEKvXr2Kuia7dzr+CvNitpOYlP73K9uR6v6VeLp7bU5uXG4XT+8sCGc3T+5/qD/uvn7Fsj+dI/anuM8REcmrQM9B1uiJiIiIlJQC3T30yiuv8Morr+RrXYPBwG+//WZTUSIiIiJ/VaDQ0rp1a2rUqFFEpYiIiIjcWoFCS+/evTWnRUREREqE3u0tIiIidkGhRUREROyCQouIiIjYhXzPabndu4ZEREREippGWkRERMQuKLSIiIiIXVBoEREREbug0CIiIiJ2QaFFRERE7IJCi4iIiNiFUhVaTp06xYQJEwgLC6N+/fr07NkzX/3MZjORkZF07NiRoKAg+vTpw/79+4u2WBERESlWpSq0HDlyhK1bt1K9enVq166d735z585l+vTpDB48mDlz5uDj48OQIUOIi4srwmpFRESkOJWq0BIaGsrWrVuZPn06DRo0yFefzMxM5syZw5AhQxg8eDAhISF8+umneHl5ERUVVcQVi4iISHEpVaHFwaHg5ezdu5fU1FR69OhhaXN2dqZLly5s27atMMsTERGREpTvx/iXVsePHwegVq1aVu21a9dmwYIFXLt2jXLlytm07czMTEwmU4H6GAwGTCYTppwccnJybNpvaWUy5WAGTCZTmTs2o8lErslERkYGZrO5SPelc8Q+3ck54urqWkRVidxd7D60JCcn4+zsjIuLi1W7h4cHZrOZpKQkm0PLoUOHCtzHyckJByd3kpOTSUxMtWm/pVWq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rRccHMyGDRvy3B1UsWJFatSowcmTJ286nwVg4cKFLFy4ME/71q1b9bRbEbljBvNfL2qL3Zs2bRpLlizh+++/12+3IiJSZmikpQw5fvw4J06cYNGiRfTr10+BRUREyhSNtJQhAwYMYP/+/bRr144pU6bg6upa0iWJiIgUGoUWERERsQu65VlERETsgkKLiIiI2AWFFhEREbELCi0iIiJiFxRaRERExC4otIiIiIhdUGiRMm/x4sUEBARYXtxYko4ePUpERARnzpwp6VJEROyOQouUebGxsfj5+XHgwAFOnTpVorUcPXqUGTNmEB8fX6J1iIjYI4UWKdPi4uLYt28fr7/+Ot7e3sTGxpZ0SSIiYiOFFinTYmNj8fT0pEOHDnTr1u2moeWbb77h0UcfpUmTJjRt2pRevXqxYMECy/Ls7GxmzJhB165dCQwMpGXLljz11FNs377dajvHjh1j9OjRtGjRgsDAQB599FE2btxoWf71118zZswYAAYOHEhAQAABAQHs3LkTgIMHDzJ06FBatmxJUFAQoaGhvP7660XxtYiI2CW9MFHKtNjYWLp06YKzszM9e/YkJiaGAwcOEBQUBMD27dt5+eWXCQkJYdy4ccD1F0/u3buXQYMGATBjxgzmzJnDE088QVBQEKmpqRw6dIhff/2VNm3aAHDkyBGeeuopfH19GTZsGK6urqxdu5aRI0cSERFBly5daN68OQMGDGDhwoWMGDGCWrVqAVC7dm0uX77M0KFDqVixIs899xweHh6cOXOGDRs2lMC3JiJSOundQ1JmHTp0iMcee4zo6Ghat26N2WymY8eOdO3alTfffBOASZMm8fXXX/Pzzz9jNBpvup2wsDDuuece5syZc8t9DR48mMuXL7NixQrL27XNZjNPPfUUV69eZf369QCsW7eOMWPG8MUXX9CyZUtL/++++46RI0fy1VdfERgYWFhfgYhImaLLQ1JmxcbGUrlyZUs4MBgMPPjgg6xZswaTyQSAh4cHGRkZeS71/JmHhwdHjhzh5MmTN12emJjITz/9RI8ePUhNTeXKlStcuXKFq1ev0rZtW06ePElCQsJta3V3dwdgy5YtZGdn23C0IiJln0KLlEkmk4lvvvmGli1bcubMGU6dOsWpU6cICgri0qVL/PjjjwD069ePGjVqMGzYMNq3b8/rr7/Otm3brLY1evRoUlJS6NatG7169eKjjz7i8OHDluWnT5/GbDYzbdo0QkJCrP5EREQAcPny5dvW26JFC7p168aMGTNo1aoVzz//PCtWrCArK6uQvxkREfuly0NSJm3fvp0hQ4bccnnv3r356KOPAMjKyuKHH35g27ZtbNu2jfj4eKvlcH00ZePGjWzfvp3vv/+etLQ0Jk6cyBNPPMH+/fvp06cPQ4YMoV27djfdX1BQEG5ubre8PHTD/v372bx5M99//z2//vorderUYenSpVSoUOEOvxEREfun0CJlUnh4ONu2bWPChAl5lm3YsIHNmzezY8cOypUrZ7UsNzeXd955h6VLl/Ltt99SvXr1PP3T0tJ4+umnuXz5Mtu2bePy5cu0bt2a4cOH8/LLL9+2rvXr1zN69OhbhpY/i42NZdy4cbz//vul4sF4IiIlTXcPSZlz7do1vv32W7p370737t3zLK9SpQqrV69m06ZNhISEULFiRcsyBwcHAgICACyXZq5evWq1ToUKFbj33ns5d+4cAJUqVaJFixYsXbqUp59+mipVqljt78qVK3h7ewNQvnx5AFJSUqzWSUpKwsPDA4PBYGmrV6+eVR0iInc7hRYpczZt2kRaWhqhoaE3Xd64cWO8vb1ZtWoV33zzDUlJSbRq1QpfX1/Onj3LokWLqFevHrVr1wbgoYceokWLFjRo0AAvLy8OHjzI+vXrefrppy3bfPvtt+nXrx+9evXiySefpFq1aly6dIn9+/dz/vx5Vq1aBVwPIkajkblz55KSkoKzszOtWrUiNjaWmJgYHnjgAe69917S0tJYtmwZbm5utG/fvui/NBERO6DLQ1LmjBgxgh07drBz507LyMZfvf7668TGxjJ16lSWLVvG77//TnJyMj4+PrRr144XX3wRHx8fAGbNmsWmTZs4efIkWVlZVK1albCwMIYOHYqTk5Nlm3FxccyYMYPt27eTmJiIt7c39evX55FHHqFbt26W9ZYvX86cOXM4e/YsJpOJL774And3d6Kioti7dy+XLl3C3d2doKAgRo0aRcOGDYv2CxMRsRMKLSIiImIXdMuziIiI2AWFFhEREbELCi0iIiJiFxRaRERExC4otIiIiIhdUGgRERERu6DQIiIiInZBoUVERETsgkKLiIiI2AWFFhEREbELCi0iIiJiFxRaRERExC78P3FiyH0IdBY5AAAAAElFTkSuQmCC\n", 128 | "text/plain": [ 129 | "
" 130 | ] 131 | }, 132 | "metadata": {}, 133 | "output_type": "display_data" 134 | } 135 | ], 136 | "source": [ 137 | "# Yearn (yUSDC) strategy earns more USDC\n", 138 | "with boa.env.prank(user):\n", 139 | " tokens[0].transfer(vault_tokens[0].address, 1 * 10**6 * 10**18)\n", 140 | " \n", 141 | "display_pool_chart(pool);" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 7, 147 | "id": "25c26540", 148 | "metadata": {}, 149 | "outputs": [ 150 | { 151 | "data": { 152 | "image/png": "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uXKEhITw+++/31cd9j4ubTTewOjsjLNzkfx67WY0OmMAjEZjMTw3I05GIyVLlizsUkRE5A6K3G+e1NRUBg0aRGJiIkuWLMHT07OwSxIREZEioEg9PZSZmcmbb77JiRMn+Prrrylfvrxd+7l06RJ79+4lMDAwjysUERGRwlKkelpGjhzJ5s2biYiIICkpiQMHDliX1a5dG1dXV/r27cv58+fZsGEDAKtWrWLz5s20bt2acuXKERMTw6xZszAajbz00kuFdCYiIiKS14pUaNmxYwcAY8eOzbJs48aN+Pv7YzabbQbH+vv7c/nyZb744gsSExPx9PSkadOmDBs27L6eHBIREZGipUiFlk2bNt1znQULFth8Dg4OztImIiIixU+RGtMiIiIicicKLSIiIuIQFFpERETEISi0iIiIiENQaBERERGHoNAiIiIiDkGhRURERByCQouIiIg4BIUWERERcQgKLSIiIuIQFFpERETEISi0iIiIiENQaBERERGHoNAiIiIiDkGhRURERByCQouIiIg4BIUWERERcQgKLSIiIuIQFFpERETEISi0iIiIiENQaBERERGHoNAiIiIiDkGhRURERByCQouIiIg4BIUWERERcQgKLSIiIuIQFFpERETEISi0iIiIiENQaBERERGHoNAiIiIiDkGhRURERByCXaHl5ZdfJjo6mtTU1LyuR0RERCRbzvZsFBMTwzvvvIO7uzvt27cnPDyc0NBQDAZDXtcnIiIiAtgZWtatW8ehQ4dYuXIla9euZeXKlZQtW5Ynn3ySp556ilq1auV1nSIiIvKAsyu0AAQFBREUFMT777/Pjh07WLlyJUuWLGHevHlUr16d8PBwunTpwkMPPZSX9YqIiMgD6r4H4jo5OdGyZUvGjx/Pli1b6NixI8eOHWPixImEhYXRr18/tmzZkqN9rVmzhldffZVWrVoRHBxMeHg43333HRaL5a7bWSwWZs2aRZs2bQgKCqJbt24cOHDgfk9NREREihC7e1put2fPHlauXMm6deuIj4+nRo0adO3aFWdnZ5YvX86rr77K4MGDeeONN+66n3nz5lGxYkUiIiIoXbo0O3fu5KOPPuLixYsMHTr0jtvNnj2byZMnM2LECAICAli0aBH9+/fnhx9+oFKlSnlxiiIiIlLIDJZ7dWPcwbFjx1i5ciWrVq3iwoULlClThief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44wfP94ahrp3707v3r3597//zbPPPouHhwePPfYYVatWZfLkySxfvpwmTZoQEhJC27ZtKVOmjHV/Pj4+PPLII+zZs8caSPbu3UuHDh1Yu3Ytx48fp3r16tYAc/u5LFy40GaAcq9evRgwYABz587NNrRMmTKFDh06APDcc8/RqVMnJkyY8MCFFrtuDzVu3DjLT6NGjQgICFBgERF5ACQlJQH/rxcjJxo1amQTWACqVq1KvXr1iI6OtrbFxcWxfft2unTpkqWXo2vXrja/Zzp16oSfnx9bt2695/G3bduGn58fTz75pLXNxcWF3r17k5KSwu7du4GbQWrZsmUMGDAAgO+//54PPviAFi1aMGrUKJsxmyEhIezZswe4+Z0cOXKEbt26Ubp0aWtY2bNnD15eXtSsWdO63e2BJTExkWvXrtG4cWNiYmJITEy0qbtcuXK0b9/e+tnDw4OuXbvyxx9/cOXKlXued3GSZ9P437hxg++++47Fixdz7ty5vNqtiIgUQbeCQ3Jyco638ff3z7Y9PDycffv2WX93rF27loyMjCw9JXBznMztDAYDlStXztHvnXPnzlG5cmWcnGx/9VWvXh2A8+fPW9s8PT355z//yaZNm9i0aROjR4+matWqLFy4kGnTplnXa9iwIVeuXOH06dPs378fg8FAcHAwDRs2tIaZPXv20KBBA5vj7t27l379+lnXDQ0N5auvvgLIEloqV66cJbxVqVLFek4PErtCy/vvv2+TVNPT03nhhRf48MMP+eyzz6wJUEREiicPDw/KlSvH0aNHc7zNneZreeKJJ3B2drb2tqxcuZK6detSrVq1PKn1flWsWJHnnnuOqKgovLy8bHqFbt3y2b17N3v27KF27dq4u7tbQ0tycjJ//vmnza2hM2fO0K9fP65fv05ERASzZs1i7ty51ke0zWZzgZ6fI7ErtOzatcumq+rWM/ETJkxg1apVlC1blqlTp+ZZkSIiUvS0bduWM2fOsH///vvaj4+PD23atCE6Oppz586xb9++bHtZAE6fPm3z2WKxcPr0aZtJT+80cLZixYqcPn06Syg4ceIE8P8G4N6Jt7c3lSpVsrklU6FCBSpUqMDevXvZu3cvDRs2BG72wJw7d461a9diMplo1KiRdZtNmzaRnp7O9OnT6d69O61bt6ZZs2Z3DHWnT5/GYrHYtJ06dcp6Tg8Su0LL1atXbb6on376ibp16/Lkk0/yyCOP8MILL3Do0KE8K1JERIqel19+GXd3dz788EOuXr2aZfmZM2eYP39+jvYVHh7OsWPHGDduHEajkSeeeCLb9VasWGEdTwM3byVduXLFZpqNkiVLZrnFAtCqVSuuXLnC6tWrrW2ZmZksWLAAd3d3a7A4cuQI165dy7L9uXPnOH78OFWrVrVpDwkJ4ddff+XQoUPWHpVatWpRqlQpZs2aRYkSJahTp451faPRCGATRBITE1m+fHm253z58mU2bNhg/ZyUlMSKFSuoVasWfn5+2W5TXNn19NDtF0RmZib/+9//bB7lKlWqVLYXjIiIFB8PP/wwEyZMYPjw4Tz++OPWGXHT09PZv38/a9eu5ZlnnsnRvlq3bo2Pjw9r166lVatWNk/p3M7b25sePXrwzDPPWB95rly5Mi+88IJ1nTp16rB69WrGjBlDYGAg7u7uhIWF0a1bN5YsWUJERAS///47FStWZN26dezbt4/333/fOk5nx44dTJkyhbCwMOrVq4e7uztnz55l+fLlpKen8/rrr9vU1LBhQ6KjozEYDNbQYjQaqV+/Pj///DONGzfG1dXVun7z5s1xcXFh8ODBdO/eneTkZJYtW0aZMmWyHVhbpUoVPvjgA3777TfKlCnD8uXLiY2NZcyYMTn6bosTu0JLnTp1WLp0KU2aNGHTpk0kJyfbTN5z5syZO15wd3P69GkiIyM5ePAgR48epVq1aqxateqe24WFhWU7GOnQoUNFcrZFEZHiol27dqxcuZLIyEg2btxIVFQUrq6uBAQEEBERYRMm7sbV1dU6wdqdbg0BDB48mL/++otZs2aRnJxMaGgon3zyCSVLlrSu06NHD/7880++//575s2bR8WKFQkLC6NEiRIsWLCACRMm8N///pekpCSqVq3KmDFjbMJVhw4dSE5OZseOHfz666/Ex8fj5eVFUFAQL730Ek2bNrWp6dYtoWrVqlG6dGmb9p9//tm6/JZq1aoxefJk/v3vf/Pll19StmxZXnzxRXx9fXn//feznHOVKlX46KOPGDduHCdPnsTf359//etftGzZMkffbXFiV2h58803efnll3n22WexWCx07NiRoKAg6/INGzbkeLKh2x09epStW7dSr149zGZzlnt4d9OxY0f69+9v03Z7shURkfxRpUqVe75f6K+/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5YcffrDu48yZMyxcuJCRI0dax1I2a9aM1NRUpk2bRrdu3XByunnjICMjg+HDh/P4449btz979qxNTdWrVyckJITly5dbQ8v169fZtGkTH3/88V3PJz4+noULF1KjRg0ASpYsSZ8+fTh48CANGzbk2LFjbNiwgS+//JKuXbtaz6lz5845/s7Cw8MZNGiQdduYmBimTZtmDS1PPPGEdV2LxUKjRo24dOkSS5YsuWtoef/9961/NplMNG/enNDQUNatW2czRvVe53jy5EmioqIYPny4tU642aFxy9SpU/H29mbu3LnWV+I0aNCAdu3asWzZMus1kVvFckZcEREpGlq0aGH9s7u7OxUqVODixYvAzf/5X758mfbt29tsc/svP4CdO3cC0KFDBzIzM60/zZo148qVK1y4cMFm/datW9+zrhdeeIGNGzcSFxcHQHR0NC4uLjZhJzvlypWz/jIHrL0yly5dArA+rdOuXTvrOk5OTrRt2/aeNd3y9++jQ4cO/P7779Y5yuLj4/n8889p27YtderUoU6dOixZsiTLrZ6/O3DgAC+99BJNmjShdu3a1KtXj5SUFE6dOpWrc/z111+xWCw899xzdzzWjh07CAsLw2g0Wv++vLy8qF27dpZbTblh9xvNzp8/z4wZM9i1axfXrl3jP//5D40aNbL++ZlnntEgJhGRYsRoNNpM7nk7s9mc7UsyPT09bT67uLiQnp4O3LwNAeDr62uzTtmyZW0+X79+HYvFQtOmTbM99oULF6hYsSJws1egVKlS9zyXTp06MXr0aFauXEmfPn34/vvv6dixIx4eHnfd7u9PE926XZKWlmY9JxcXlyzn/fdzvJsyZcrYfC5btiwZGRlcv36dsmXLEhERwf79+xkyZAiPPPIIHh4eREVFsWbNmjvu8/z58/Tv35+6desycuRIypUrh4uLC4MGDbLWntNzjIuLw9nZOUudt7t+/Trz589n/vz5WZbd2p897Aotx44do2fPnpjNZoKCgjhz5gyZmZnAzb+YvXv3kpKSwhdffGF3YSIiUrT4+vpy9erVbJddvnw5V7+YAfz8/ACyzEvy92N4e3tjMBhYvHhxtr/wqlatav1zTsdblihRgi5duvD9998TEhLCn3/+yYcffpir+rPj5+dHRkaGdYb2W3Iz90psbCzly5e3fr569SouLi6ULl2atLQ0tmzZ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4WbvV3E7N6PJhNlk4saNG1gslnw9lq4Rx3Q/14i7u3s+VSXyYClSoSUhIQEvL68s7d7e3sTHx99127CwMIKCgqhQoQIxMTHMmDGDHj16sGLFCipVqmRXPYcPH871Ni4uLji5eJKQkEBcXJJdxy2qknzcMJkySUxMIPn69cIuJ0+VNBtISk4i9mocGRkZ+XosXSOO6X6ukZCQkHyqSuTBUqRCy/348MMPrX9u2LAhzZs3p3PnzkRGRvLpp5/atc+6deva1dNy8UoSXl5emCzF5usFwMPTA6PRGU9PL1wxF3Y5ecrN0xuPUh74+vsVSE+LrhHHU5DXiIhkr0j9i+nl5UViYmKW9vj4eLy9vXO1r3LlyhESEsLvv/9udz32PiptNN7A6OyMs3OR+nrvm9HojAEwGo3F8NyMOBmNlCxZsoCOp2vE0RT0NSIiWRWpgbjVqlXLMnYlMTGRK1euUK1atUKqSkRERIqCIhVaWrVqxc6dO0lISLC2rV27FicnJ5o3b56rfV26dIm9e/cSGBiY12WKiIhIIShS/bfdu3dnwYIFDBkyhEGDBnHp0iXGjRtH9+7dbeZo6du3L+fPn2fDhg0ArFq1is2bN9O6dWvKlStHTEwMs2bNwmg08tJLLxXW6YiIiEgeKlKhxdvbm/nz5zNq1CiGDBlCqVKleO655xg+fLjNemaz2WaArL+/P5cvX+aLL74gMTERT09PmjZtyrBhw+x+ckhERESKliIVWgCqV6/OvHnz7rrOggULbD4HBwdnaRMREZHipUiNaRERERG5E4UWERERcQgKLSIiIuIQFFpERETEISi0iIiIiENQaBERERGHoNAiIiIiDkGhRURERByCQouIiIg4BIUWERERcQgKLSIiIuIQFFpERETEISi0iIiIiENQaBERERGHoNAiIiIiDkGhRURERByCQouIiIg4BIUWERERcQgKLSIiIuIQFFpERETEISi0iIiIiENQaBERERGHoNAiIiIiDkGhRURERByCQouIiIg4BIUWERERcQgKLSIiIuIQFFpERETEISi0iIiIiENQaBERERGHoNAiIiIiDkGhRURERByCQouIiIg4BIUWERERcQhFLrQcP36cl156ieDgYJo3b864ceNIT0+/53YWi4VZs2bRpk0bgoKC6NatGwcOHMj/gkVERKRAFKnQEh8fT9++fcnIyGDKlCkMHz6cpUuXMnbs2HtuO3v2bCZPnky/fv2YOXMmfn5+9O/fn5iYmAKoXERERPKbc2EXcLtvv/2W5ORkpk6dio+PDwAmk4mRI0cyaNAgypcvn+12aWlpzJw5k/79+9OvXz8AQkJC6NSpE5GRkXz66acFcwIiIiKSb4pUT8u2bdsIDQ21BhaAzp07Yzab2bFjxx2327dvH0lJSXTu3Nna5urqSvv27dm2bVt+liwiIiIFpEj1tJw4cYJnn33Wps3Lyws/Pz9OnDhx1+0AqlWrZtNevXp15s+fT2pqKiVKlMhVLX/99RdpaWm52uYWk8nMY6HlMZstdm1fVBmdnTh3LQ6nWs1xs5gLu5w8ZTA4ceLCZQyXYgvkeLpGHM/9XCNubm4EBATkQ1UiD5YiFVoSEhLw8vLK0u7t7U18fPxdt3N1dcXNzc2m3cvLC4vFQnx8fK5DC4DBYMj1NgDOzka8vUrata0jMHpk/TuS3NE1IiKSe0UqtBQl+l+RiIhI0VKkxrR4eXmRmJiYpT0+Ph5vb++7bpeenp7ldk5CQgIGg+Gu24qIiIhjKFKhpVq1alnGriQmJnLlypUs41X+vh3AyZMnbdpPnDhBhQoV7Lo1JCIiIkVLkQotrVq1YufOnSQkJFjb1q5di5OTE82bN7/jdg0aNMDDw4M1a9ZY2zIyMli/fj2tWrXK15pFRESkYBSpMS3du3dnwYIFDBkyhEGDBnHp0iXGjRtH9+7dbeZo6du3L+fPn2fDhg3AzZH5gwYNYsqUKfj6+lKzZk2ioqKIi4tjwIABhXU6IiIikoeKVGjx9vZm/vz5jBo1iiFDhlCqVCmee+45hg8fbrOe2WzGZDLZtA0cOBCLxcKcOXO4du0atWrVIjIykkqVKhXkKYiIiEg+MVgsluI1UYSIiIgUS0VqTIuIiIjInSi0iIiIiENQaBERERGHoNAiIiIiDkGhRURERByCQouIiIg4hCI1T4vkr4iICA4fPsyqVauyLBs9ejQbN25k06ZN1rYDBw4wdepU/vzzTxITEylbtix169ZlwIAB1KtXD4ApU6YwdepU4OZbsUuVKkWFChVo1KgRPXv2pHr16lmOlZ6ezuLFi1m5ciUnT57EZDJRuXJlOnToQN++fbN907fkr7S0NEJCQhgwYIDNvEiJiYk0btyYhx56iM2bN9ts8+qrr3L69GlWr14N3Pklo66urvz222+EhYVx7ty5u9YxdOhQXn/9dQICAvjnP/+Z7eSQd1smIsWbQotka+/evfTp04eWLVsycuRISpUqxenTp/npp584dOiQNbQAlChRgvnz5wOQnJzM//3f/7FkyRKWLl3K6NGjCQ8Pt66blpbGyy+/zIEDB+jZsydvvvkmrq6u/PnnnyxYsIDExETef//9Aj/fB52bmxt16tRh3759Nu379+/Hzc2N8+fPc+nSJZuZqffv30/79u1t1u/duzdPPvmkTZuT080O3alTp5Kenm5tHzp0KA0aNKB///7WtoceeijPzklEih+FFslWVFQUFStWZNq0aRiNRgBCQ0Pp3r07ZrPZZl0nJyeCg4Otn5s3b06PHj145ZVX+OCDD2jQoIF1ZuJJkyaxZ88eIiMjadasmXWbpk2b0qNHjyy/NKXgNGjQgKioKDIzM3F2vvlPw759+2jUqBHHjx9n7969PP7448DNl5Fev36dkJAQm3384x//sLkWble7dm2bz66urpQtW/aO64uI/J3GtBQTmzZtIiAggFOnTtm0x8fHExQUxKJFi3K1v4SEBHx9fa2B5Xa3/ud8N25ubnz00UdkZGSwbNkyAFJTU4mKiuKxxx6zCSy3bxMaGpqrOiXn9u/fz+DBg2nRogXBwcGEh4ezYsUK6/KQkBBu3LjBH3/8YW3bt28f9evXp379+jaB8tafGzRoUGD1i4gotBQTrVu3pnz58ixfvtym/db4lS5duuRqf3Xq1GH//v38+9//5vjx43bV9Mgjj1C+fHn2798PwOHDh0lJSaFly5Z27U/uz/nz52nQoAGjR49m+vTpdOjQgQ8//JD//ve/wP8LILcCSWZmJr/99tsdQ4ufnx8PP/ywzTHMZjOZmZk2P3/vmcup7PaVmZlp175EpHjQ7aFiwmg08swzz7B8+XLefPNNaw/J8uXLad++fa4Htw4YMICDBw8yffp0pk+fjo+PDy1atODFF1+kYcOGOd7PP/7xD65evQrA5cuXrW1S8J544gnrny0WC40aNeLSpUssWbKEp59+Gl9fX6pWrcr+/fvp168fR44cIS0tjXr16uHl5cWYMWO4ceMGJUuWZP/+/dn2skyYMIEJEybYtIWGhjJv3rxc15vdvkTkwabQUow899xzzJgxg+3bt9OmTRuOHDnC77//zjvvvJPrfXl4eDBn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\n", 153 | "text/plain": [ 154 | "
" 155 | ] 156 | }, 157 | "metadata": {}, 158 | "output_type": "display_data" 159 | } 160 | ], 161 | "source": [ 162 | "# Pool is tradable while share values change\n", 163 | "with boa.env.prank(user):\n", 164 | " pool.exchange_underlying(1, 0, int(3 * 10**5 * 10**18), 0)\n", 165 | " \n", 166 | "display_pool_chart(pool);" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "id": "edea1cc3", 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [] 176 | } 177 | ], 178 | "metadata": { 179 | "kernelspec": { 180 | "display_name": "Python 3 (ipykernel)", 181 | "language": "python", 182 | "name": "python3" 183 | }, 184 | "language_info": { 185 | "codemirror_mode": { 186 | "name": "ipython", 187 | "version": 3 188 | }, 189 | "file_extension": ".py", 190 | "mimetype": "text/x-python", 191 | "name": "python", 192 | "nbconvert_exporter": "python", 193 | "pygments_lexer": "ipython3", 194 | "version": "3.8.10" 195 | } 196 | }, 197 | "nbformat": 4, 198 | "nbformat_minor": 5 199 | } 200 | --------------------------------------------------------------------------------