├── .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 |
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": "iVBORw0KGgoAAAANSUhEUgAAAi0AAAF/CAYAAACFR/kTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAA9hAAAPYQGoP6dpAABWC0lEQVR4nO3dd3hUZf7+8fdkUiCkEQi4EKRECS0hEFroBGkiBitIjSCCghTLil1UBBV0IbA0Q5cIiMsCUkSqCytKFwSXTmihpvfJ/P7gx3wdEyAZUmbi/bquXDLPOc85nzOZy7nznOecYzCbzWZERERE7JxTSRcgIiIikh8KLSIiIuIQFFpERETEISi0iIiIiENQaBERERGHoNAiIiIiDkGhRURERByCQouIiIg4BIUWERERcQjOJV2AIzhz5gzR0dEcOHCAY8eOUatWLdasWWPz9rZu3crMmTM5evQoLi4u1KlTh88++4z77ruvEKsWEREpXRRa8uHYsWNs27aNhg0bkpOTw708+eDf//43b731FoMGDWL06NGkpKSwe/duMjIyCrFiERGR0segZw/dXU5ODk5ON8+kjR07lkOHDtk00hIfH0/Hjh155ZVX6NOnT2GXKSIiUqppTks+3Aosd2I2m4mOjqZLly40aNCAjh07Mn/+fKt11q1bR05ODk8++WQRVSoiIlJ6KbQUkvHjxzN16lR69uzJ7Nmzeeyxx5g0aRIxMTGWdQ4cOEDNmjVZuXIlHTp0oF69ekRERLBt27YSrFxERMQxaE5LITh79iyLFy9m3Lhx9OrVC4CWLVuSnp7O9OnT6dWrF05OTly5coVTp04xZcoUXnvtNfz8/Pjqq6948cUXWblyJQ8++GAJH4mIiIj90khLIdi5cycAnTt3Jjs72/LTsmVLrly5wsWLF4Gbp5BSU1P54IMP6NmzJ61atWLKlClUrlyZOXPmlOQhiIiI2D2NtBSCGzduYDabadGiRZ7LL168SNWqVfHy8gKwWs/FxYWmTZty7NixYqlVRETEUSm0FAJvb28MBgNLlizBxcUl1/KaNWsC8MADD9x2G7rkWURE5M50eqgQhIWFATcvaQ4KCsr14+HhAUCHDh0A+O9//2vpm5mZyS+//EL9+vWLv3AREREHopGWfEhLS7Nc4XP+/HmSk5NZv349AM2aNaNmzZr07duXv//97wwePJiGDRuSlZXF6dOn2bVrF//85z8BqF+/Pl26dOGdd94hPj4ePz8/lixZwtWrVxk8eHCJHZ+IiIgj0M3l8uHcuXN07Ngxz2ULFy6kefPmmM1mvvrqK5YuXcqpU6coV64cNWvWpGvXrkRGRlrWT01N5fPPP+e7774jOTmZ+vXr89prrxEaGlpMRyMiIuKY7Da0pKSk0K1bN+Li4vjmm28ICgq67bpms5k5c+awZMkSrl+/Tt26dXnjjTcICQkpvoJFRESkSNntnJZ//vOfmEymfK07Z84cpk6dSmRkJLNmzcLPz49BgwYRGxtbxFWKiIhIcbHL0HLixAmWLFnCSy+9dNd1MzIymDVrFoMGDSIyMpKwsDA+//xzfHx8iI6OLoZqRUREpDjYZWj56KOP6N27t+VS4TvZu3cvycnJdOvWzdLm6upKp06d2L59e1GWKSIiIsXI7q4eWr9+Pf/73/+Iiori8OHDd13/5MmTANSqVcuqPSAggAULFpCenk6ZMmVsqiUjIyPfp6hERG7H3d29pEsQKRXsKrSkpaUxceJExowZY7m3yd0kJibi6uqKm5ubVbuXlxdms5mEhASbQ8uhQ4ds6ici8ke6OlCkcNhVaJkxYwYVKlTgiSeeKOlSAGjQoIFGWkREROyE3YSW8+fPM3fuXKZPn05SUhJw854mt/6bkpJCuXLlcvXz8vIiMzOTjIwMq9GWxMREDAYD3t7eNtf059EbERERKTl2E1rOnTtHVlYWzz//fK5lAwYMoGHDhixbtizXsltzWU6dOkWdOnUs7SdPnqRKlSo2nxoSERER+2I3oaVu3bosXLjQqu3IkSNMmDCBcePG3fbmco0bN8bDw4N169ZZQktWVhbff/89bdu2LfK6RUREpHjYTWjx8vKiefPmeS6rX7++5YGCAwcO5MKFC2zcuBG4eQpn6NChREVF4evrS+3atYmJiSE+Pl7P8xERESlF7Ca05FdOTk6uybFDhgzBbDYzd+5cy238o6OjqVatWglVKSIiIoXNbp89JCIiIvJHdnlHXBEREZE/U2gRERERh6DQIiIiIg5BoUVEREQcgsNdPeQI4hNSSUxOL+kyCp2TwYB3GTNkpZV0KUXCtZwHbh6230FZRESKlkJLEUhMTmfFmj0kJJWu4OL/Nx8iWvtz4af1ZKYklXQ5hcq1nCe12j+q0CIiYscUWopIQlI68QmpJV1GofL2LAtAZkoSmckJJVyNiIj81WhOi4iIiDgEhRYRERFxCAotIiIi4hAUWkRERMQhKLSIiIiIQ1BoEREREYeg0CIiIiIOQaFFREREHIJCi4iIiDgEhRYRERFxCAotIiIi4hAUWkRERMQhKLSIiIiIQ1BoEREREYeg0CIiIiIOQaFFREREHIJzSRfwR9u2bWPOnDkcP36c5ORkKleuzEMPPcSIESPw9PS8bb/+/fvz888/52pfu3YtAQEBRVmyiIiIFBO7Ci3x8fEEBwfTv39/fHx8OHbsGFFRURw7doy5c+fesW/jxo15/fXXrdr8/f2LslwREREpRnYVWiIiIqxeN2/eHFdXV9555x3i4uKoXLnybft6eXkREhJSxBWKiIhISbH7OS0+Pj4AZGVllWwhIiIiUqLsaqTlFpPJRHZ2NsePH2f69OmEh4ff9VTPzz//TEhICCaTiYYNGzJq1CiaNm16T3VkZGRgMpkK1MdgMGAymTBlZ5OdnX1P+7c3JlM2Zv7v91OaGE0mckwm0tLSMJvNJV2OlDLu7u4lXYJIqWCXoaVDhw7ExcUB0KZNGyZPnnzH9Zs2bUpERAQ1atTg8uXLREdH8+yzz7Jo0SIaNWpkcx2HDh0qcB8XFxecXDxJTEwkPj7Z5n3bo2QfN0ymbJKSEkm5caOkyylUZXMMJKckc+1qvEb1pNCFhoaWdAkipYLBbId/Vh49epS0tDSOHz/OjBkz8Pf3Z968eRiNxnz1T01N5ZFHHiEgIIA5c+bYXIetIy2XriQzd8l/uJGQavO+7VGNahUY0D2Qk99/TUZSfEmXU6jcPH2o270vruX9NNIihU4jLSKFwy5HWurUqQNAo0aNCAoKIiIigo0bN9K1a9d89Xd3d6ddu3Zs2LDhnupwc3OzqZ/RmIbR2RlnZ7t8e21mNDpjAIxGYyk8NiNORiNly5Yt6VJEROQ27H4ibmBgIC4uLpw9e7akSxEREZESZPeh5cCBA2RlZRXoniupqals3bqVoKCgIqxMREREipNdjfGPGDGCBg0aEBgYSJkyZTh69CjR0dEEBgby0EMPAfDmm2+ycuVKfvvtNwB2797Nl19+SadOnahatSqXL19m3rx5XLlyhSlTppTk4YiIiEghsqvQEhwczNq1a5k9ezZms5mqVavy1FNPMXjwYFxdXQHIycmxmhzr5+dHVlYWX3zxBfHx8ZQtW5ZGjRoxbtw4goODS+pQREREpJDZ5dVDju7s+evMjdlBfCm7eqi6fwX6dQ3g9KblZCYnlHQ5hcrVw5s63fviWblqSZciIiK3YfdzWkRERERAoUVEREQchEKLiIiIOASFFhEREXEICi0iIiLiEBRaRERExCEotIiIiIhDUGgRERERh6DQIiIiIg5BoUVEREQcgkKLiIiIOASFFhEREXEICi0iIiLiEBRaRERExCEotIiIiIhDUGgRERERh6DQIiIiIg5BoUVEREQcgkKLiIiIOASFFhEREXEICi0iIiLiEBRaRERExCEotIiIiIhDUGgRERERh2BXoWXbtm3069ePFi1a0KBBAzp27MiECRNISkq6a9/ly5fTpUsXgoKCePTRR9myZUsxVCwiIiLFxbmkC/ij+Ph4goOD6d+/Pz4+Phw7doyoqCiOHTvG3Llzb9vvu+++45133mHYsGG0aNGCtWvXMmLECL766itCQkKK7wBERESkyNhVaImIiLB63bx5c1xdXXnnnXeIi4ujcuXKefabOnUq3bt3Z/To0QC0aNGC//3vf0yfPp05c+YUddkiIiJSDOzq9FBefHx8AMjKyspzeWxsLKdPn6Zbt25W7Q8//DD//e9/yczMLOoSRUREpBjY1UjLLSaTiezsbI4fP8706dMJDw/H398/z3VPnjwJQM2aNa3aAwICyMrKIjY2loCAAJvqyMjIwGQyFaiPwWDAZDJhys4mOzvbpv3aK5MpGzP/9/spTYwmEzkmE2lpaZjN5pIuR0oZd3f3ki5BpFSwy9DSoUMH4uLiAGjTpg2TJ0++7boJCQkAeHl5WbXfen1ruS0OHTpU4D4uLi44uXiSmJhIfHyyzfu2R8k+bphM2SQlJZJy40ZJl1OoyuYYSE5J5trV+NuO6onYKjQ0tKRLECkV7DK0zJ49m7S0NI4fP86MGTMYNmwY8+bNw2g0FmsdDRo0sGmk5dKVZLy8vDCZ7fLttZmHpwdGozOenl64klPS5RQqN09vPMp54Ovvp5EWERE7ZZffqnXq1AGgUaNGBAUFERERwcaNG+natWuudb29vQFISkrCz8/P0p6YmGi13BZubm429TMa0zA6O+PsbJdvr82MRmcMgNFoLIXHZsTJaKRs2bIlXYqIiNyG3U/EDQwMxMXFhbNnz+a5vFatWsD/zW255eTJk7i4uFCtWrUir1FERESKnt2HlgMHDpCVlXXbibjVqlWjRo0arF+/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+fn7ExMQUccUiIiJSXBwqtLi4FLycPXv2kJaWRrdu3axt7u7udOrUSa8gFxERKUXueEzLrd41VJKOHTsGQK1atWzaa9euzfz588nMzKRMmTJ27TsrKwuTyVSgbQwGAyaTCVNurs3bR0sDkykXC2AymUrduRlNJswmE9euXeMOpy6ym64R53Q318gf31AsIvaz64WJjiQlJQV3d3c8PDxs2n18fLBYLCQnJ9sdWg4ePFjgbdzc3HBx8yYlJYWkpDS7juuo0vw8MJlySU1NIf3/v2W1tChrNpCWnkbi5SRycnKK9Fi6RpzT3VwjYWFhRVSVyF+L04eWotSwYUO7elrOX0rDx8cHk6V0fb1e3l4Yja54e/vgjrmkyylUHt6+eJXzwj8woFh6WnSNOJ/ivEZEJH9O/y+mj48P2dnZZGVl2fS2pKSkYDAYrK8wt8efe2/ulNF4DaOrK66uTv/12jAaXTEARqOxFJ6bERejkbJlyxbT8XSNOJvivkZEJC+HGohrjxtjWY4fP27TfuzYMapUqWL3rSERERFxLE4fWpo0aYKXlxdr1qyxtuXk5PDtt9/Srl27EqxMRERECpND9d9eu3aNLVu2AHDmzBnS0tJYu3YtAM2bN8ff359BgwZx9uxZ1q9fD1y/hTN8+HCio6Px9/enbt26xMXFkZSUxNChQ0vsXERERKRwOVRoSUxM5MUXX7Rpu/H5888/p0WLFpjN5jyDY4cNG4bFYmHu3LnWafxjYmKoVq1asdUuIiIiRcuhQktgYCC///77LddZsGBBnjaDwcDw4cMZPnx4UZUmIiIiJczpx7SIiIjIX4NCi4iIiDgFhRYRERFxCgotIiIi4hQUWkRERMQpKLSIiIiIU1BoEREREaeg0CIiIiJOQaFFREREnIJCi4iIiDgFhRYRERFxCgotIiIi4hQUWkRERMQpKLSIiIiIU1BoEREREaeg0CIiIiJOQaFFREREnIJCi4iIiDgFhRYRERFxCgotIiIi4hQUWkRERMQpKLSIiIiIU1BoEREREaeg0CIiIiJOQaFFREREnIJCi4iIiDgFhRYRERFxCgotIiIi4hQUWkRERMQpKLSIiIiIU1BoEREREaeg0CIiIiJOQaFFREREnIJrSRfwZ0ePHuX9999n7969lCtXjsjISF566SXc3d1vuV1ERARnzpzJ075//348PDyKqlwREREpJg4VWpKTkxk0aBA1atQgOjqaCxcuMHHiRDIzM3nrrbduu32XLl0YMmSITdvtwo6IiIg4B4cKLV988QXp6elMmzYNPz8/AEwmE+PHj2f48OFUrlz5lttXrFiR0NDQoi9UREREip1DjWnZunUr4eHh1sAC0K1bN8xmM9u2bSu5wkRERKTEOVRPy7Fjx3jsscds2nx8fAgICODYsWO33T4+Pp6lS5fi5uZG06ZNGTt2LEFBQXbXk5WVhclkKtA2BoMBk8mEKTeX3Nxcu4/tiEymXCxc7/0qbedmNJkwm0xcu3YNi8VSpMfSNeKc7uYa8fT0LKKqRP5aHCq0pKSk4OPjk6fd19eX5OTkW24bERFBSEgIVapUISEhgZkzZ9K3b19WrFhBtWrV7Krn4MGDBd7Gzc0NFzdvUlJSSEpKs+u4jirNzwOTKZfU1BTSr14t6XIKVVmzgbT0NBIvJ5GTk1Okx9I14pzu5hoJCwsroqpE/locKrTcjTfeeMP6c9OmTWndujXdunUjJiaGd955x659NmzY0K6elvOX0vDx8cFkKTVfLwBe3l4Yja54e/vgjrmkyylUHt6+eJXzwj8woFh6WnSNOJ/ivEZEJH8O9S+mj48PqampedqTk5Px9fUt0L4qVapEWFgYv/zyi9312PuotNF4DaOrK66uDvX13jWj0RUDYDQaS+G5GXExGilbtmwxHU/XiLMp7mtERPJyqIG4tWrVyjN2JTU1lUuXLlGrVq0SqkpEREQcgUOFlnbt2rF9+3ZSUlKsbWvXrsXFxYXWrVsXaF8XLlxg9+7dBAcHF3aZIiIiUgIcqv+2T58+LFiwgJEjRzJ8+HAuXLjApEmT6NOnj80cLYMGDeLs2bOsX78egFWrVrFp0ybat29PpUqVSEhIYPbs2RiNRp5++umSOh0REREpRA4VWnx9fZk/fz7vvfceI0eOpFy5cjz++OOMGTPGZj2z2WwzQDYwMJCLFy/ywQcfkJqaire3Ny1btmT06NF2PzkkIiIijsWhQgtA7dq1mTdv3i3XWbBggc3n0NDQPG0iIiJSujjUmBYRERGRm1FoEREREaeg0CIiIiJOQaFFREREnIJCi4iIiDgFhRYRERFxCgotIiIi4hQUWkRERMQpKLSIiIiIU1BoEREREaeg0CIiIiJOQaFFREREnIJCi4iIiDgFhRYRERFxCgotIiIi4hQUWkRERMQpKLSIiIiIU1BoEREREaeg0CIiIiJOQaFFREREnIJCi4iIiDgFhRYRERFxCgotIiIi4hQUWkRERMQpKLSIiIiIU1BoEREREaeg0CIiIiJOQaFFREREnIJCi4iIiDgFhRYRERFxCgotIiIi4hQUWkRERMQpOFxoOXr0KE8//TShoaG0bt2aSZMmkZ2dfdvtLBYLs2fPpkOHDoSEhNC7d2/27dtX9AWLiIhIsXCo0JKcnMygQYPIyckhOjqaMWPGsHTpUiZOnHjbbefMmcPUqVMZPHgws2bNIiAggCFDhpCQkFAMlYuIiEhRcy3pAv7oiy++ID09nWnTpuHn5weAyWRi/PjxDB8+nMqVK+e7XVZWFrNmzWLIkCEMHjwYgLCwMLp27UpMTAzvvPNO8ZyAiIiIFBmH6mnZunUr4eHh1sAC0K1bN8xmM9u2bbvpdnv27CEtLY1u3bpZ29zd3enUqRNbt24typJFRESkmDhUT8uxY8d47LHHbNp8fHwICAjg2LFjt9wOoFatWjbttWvXZv78+WRmZlKmTJkC1fL777+TlZVVoG1uMJnMPBheGbPZYtf2jsro6sKZK0m41GuNh8Vc0uUUKoPBhWPnLmK4kFgsx9M14nzu5hrx8PAgKCioCKoS+WtxqNCSkpKCj49PnnZfX1+Sk5NvuZ27uzseHh427T4+PlgsFpKTkwscWgAMBkOBtwFwdTXi61PWrm2dgdEr79+RFIyuERGRgnOo0OJI9L8iERERx+JQY1p8fHxITU3N056cnIyvr+8tt8vOzs5zOyclJQWDwXDLbUVERMQ5OFRoqVWrVp6xK6mpqVy6dCnPeJU/bwdw/Phxm/Zjx45RpUoVu24NiYiIiGNxqNDSrl07tm/fTkpKirVt7dq1uLi40Lp165tu16RJE7y8vFizZo21LScnh2+//ZZ27doVac0iIiJSPBxqTEufPn1YsGABI0eOZPjw4Vy4cIFJkybRp08fmzlaBg0axNmzZ1m/fj1wfWT+8OHDiY6Oxt/fn7p16xIXF0dSUhJDhw4tqdMRERGRQuRQocXX15f58+fz3nvvMXLkSMqVK8fjjz/OmDFjbNYzm82YTCabtmHDhmGxWJg7dy5XrlyhXr16xMTEUK1ateI8BRERESkiBovFUromihAREZFSyaHGtIiIiIjcjEKLiIiIOAWFFhEREXEKCi0iIiLiFBRaRERExCkotIiIiIhTcKh5WqRoRUVFcfDgQVatWpVn2YQJE9iwYQMbN260tu3bt49p06bx22+/kZqaSsWKFWnYsCFDhw6lUaNGAERHRzNt2jTg+luxy5UrR5UqVWjWrBn9+vWjdu3aeY6VnZ3N4sWLWblyJcePH8dkMlG9enU6d+7MoEGD8n3TtxStrKwswsLCGDp0qM28SKmpqTRv3px77rmHTZs22Wzz3HPPcfLkSVavXg3c/CWj7u7uHDhwgIiICM6cOXPLOkaNGsULL7xAUFAQ//jHP/KdHPJWy0SkdFNokXzt3r2bgQMH0rZtW8aPH0+5cuU4efIk3333Hfv377eGFoAyZcowf/58ANLT0/nf//7HkiVLWLp0KRMmTCAyMtK6blZWFs888wz79u2jX79+vPTSS7i7u/Pbb7+xYMECUlNTee2114r9fP/qPDw8aNCgAXv27LFp37t3Lx4eHpw9e5YLFy7YzEy9d+9eOnXqZLP+gAEDePjhh23aXFyud+hOmzaN7Oxsa/uoUaNo0qQJQ4YMsbbdc889hXZOIlL6KLRIvuLi4qhatSrTp0/HaDQCEB4eTp8+fTCbzTbruri4EBoaav3cunVr+vbty7PPPsvrr79OkyZNrDMTT5kyhV27dhETE0OrVq2s27Rs2ZK+ffvm+aUpxadJkybExcWRm5uLq+v1fxr27NlDs2bNOHr0KLt37+ahhx4Crr+M9OrVq4SFhdns429/+5vNtfBH9evXt/ns7u5OxYoVb7q+iMifaUxLKbFx40aCgoI4ceKETXtycjIhISEsWrSoQPtLSUnB39/fGlj+6Mb/nG/Fw8ODN998k5ycHJYtWwZAZmYmcXFxPPjggzaB5Y/bhIeHF6hOuXN79+5lxIgRtGnThtDQUCIjI1mxYoV1eVhYGNeuXePXX3+1tu3Zs4fGjRvTuHFjm0B54+cmTZoUW/0iIgotpUT79u2pXLkyy5cvt2m/MX6lR48eBdpfgwYN2Lt3L//61784evSoXTXdd999VK5cmb179wJw8OBBMjIyaNu2rV37k7tz9uxZmjRpwoQJE5gxYwadO3fmjTfe4D//+Q/wfwHkRiDJzc3lwIEDNw0tAQEB3HvvvTbHMJvN5Obm2vz5c8/cncpvX7m5uXbtS0RKB90eKiWMRiOPPvooy5cv56WXXrL2kCxfvpxOnToVeHDr0KFD+fnnn5kxYwYzZszAz8+PNm3a8NRTT9G0adM73s/f/vY3Ll++DMDFixetbVL8unfvbv3ZYrHQrFkzLly4wJIlS+jVqxf+/v7UrFmTvXv3MnjwYA4dOkRWVhaNGjXCx8eHDz/8kGvXrlG2bFn27t2bby/L5MmTmTx5sk1beHg48+bNK3C9+e1LRP7aFFpKkccff5yZM2fy/fff06FDBw4dOsQvv/zCK6+8UuB9eXl5MXfuXPbv38/mzZvZvXs369at45tvvuG9997jiSeeuKP9WCwWDAaDTdufP0vxSE5OJjo6mg0bNnDhwgXrm9L9/Pys64SFhbF161bgem9KUFAQnp6eBAUF4e7uzs8//0xQUBDHjx+nd+/eeY4xcOBAHnnkEZs2Ly8vu+rNb19w/ToXkb8mhZZSJDAwkNatW/Pll1/SoUMHli9fTmBgIC1btgSu98bc+EX1Z2az2Tr48o9CQkIICQkBICEhgQEDBjB58uQ7Di3nz5+nRo0aAFSqVAmAc+fOFfTUpBBERUWxd+9eRo4cyX333YeXlxdxcXGsWbPGuk6TJk348ssvOX36tHU8C4CrqysNGzZkz549ZGRkYLFY8gzChetP/wQHBxdKvYW5LxEpHTSmpZR54okn2Lx5MxcuXCA+Pp5HH33U2rPh7+9vvVXzZxcvXsTf3/+W+65WrRpdu3YlKSnppvv5o8OHD3PhwgXrL76GDRvi6enJ999/X8CzkruVlZXF5s2bee655xgwYADh4eEEBwdjsVhs1rsRRPbs2cPevXutf3eAdVzLnj178PT0pF69esV6DiIiCi2lTMeOHfHx8eHvf/87ycnJPProo9ZlzZo1IyUlhZ07d9psk5aWxo4dO2jWrJm17Wah5MSJE7i7u992jExWVhbvvfce7u7u1l6ZMmXK8NRTT7F+/Xp++umnfLf58ccf7/hc5c5lZ2djNptxc3OztqWlpdlMJghQo0YNKlSowDfffMP58+fzhJaff/6Z3bt3ExISkm/PnIhIUdK/OqWMm5sbPXv2JCYmhjZt2tgMem3Tpg1NmzZl1KhRjBw5kjp16nDx4kU+++wzXFxcGDBggHXdN954A5PJROfOnalRowZpaWmsW7eOTZs2MWjQINzd3a3rms1m9u3bB0BGRoZ1crmEhAQmTpxIYGCgdd0XX3yRAwcO8Oyzz9KvXz9atWqFm5sbhw4dYtGiRTzwwAN67LkIeHt7ExwczJw5c/D398fV1ZXZs2fj5eXFlStXbNZt0qQJ3333HQEBATZ/d6GhoaSkpLB3716ef/75fI9z7tw567XwR/Xr17e5ZkRE7KHQUgp16tSJmJgYHnvsMZt2FxcXZs2axdSpU4mNjeXixYt4eXnRsmVLoqOjrWNOAPr168eKFSuYNWsWly5dokyZMtx7771MmDCBXr162ew3MzPTOijT09OTwMBAwsPDmTZtWp5p/D08PIiJibFO4x8XF4fZbKZ69epERkYyaNCgIvpW5JNPPuGtt94iKioKPz8/BgwYQEZGBnPnzrVZLywsjPXr1+d5Oqh8+fLUqFGDEydO5DueBWDBggUsWLAgT/uWLVs0262I3DWD5c83tcXpTZkyhcWLF/P999/rf7ciIlJqqKelFDl27BjHjx9n4cKF9O3bV4FFRERKFfW0lCIDBgxg3759tG3blsmTJ+Pp6VnSJYmIiBQahRYRERFxCnrkWURERJyCQouIiIg4BYUWERERcQoKLSIiIuIUFFpERETEKSi0iIiIiFNQaJFSb9GiRQQFBVlf3FiSjhw5QnR0NKdPny7pUkREnI5Ci5R68fHxVK1alf3793Py5MkSreXIkSNMmzaNM2fOlGgdIiLOSKFFSrWEhAT27t3LuHHj8Pf3Jz4+vqRLEhEROym0SKkWHx+Pr68v7du3p0uXLvmGlm+++YZHH32Uxo0b06RJE3r06MH8+fOty3Nycpg2bRqdO3cmODiYFi1a8NRTT7Ft2zab/Rw9epTRo0fTvHlzgoODefTRR9mwYYN1+VdffcWLL74IwMCBAwkKCiIoKIgdO3YAcODAAYYOHUqLFi0ICQkhIiKCcePGFcXXIiLilPTCRCnV4uPj6dSpE+7u7jz88MPExcWxf/9+QkJCANi2bRsvv/wy4eHhjB07Frj+4sk9e/YwaNAgAKZNm8asWbN44oknCAkJIS0tjYMHD/LLL7/QunVrAA4fPsxTTz1F5cqVGTZsGJ6enqxZs4aRI0cSHR1Np06daNasGQMGDGDBggWMGDGCWrVqAVC7dm0SExMZOnQo5cuX59lnn8XHx4fTp0+zfv36EvjWREQck949JKXWwYMHeeyxx4iNjaVVq1ZYLBY6dOhA586def311wGYMGECX331Ff/9738xGo357icyMpJ77rmHWbNm3fRYgwcPJjExkeXLl1vfrm2xWHjqqae4evUq69atA2Dt2rW8+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/pVWqlwsmUw4pKcmkXb1a0uUUqvK5BlLTUrl8KZHs7Owi3ZfOEft0J+dIcHBwEVUlcnex+9BSlBo2bGjTSMv5i6l4eHhgMpetr9fN3Q2j0RF3dw+cyS3pcgqVi7snbhXc8Pb3KZaRFp0j9qc4zxERuTm7/x/Tw8ODrKwsMjMzrUZbkpOTMRgMeHp62rztv47e5JfRmIHR0RFHR7v/eq0YjY4YAKPRWAaPzYiD0Uj58uWLaX86R+xNcZ8jIpJXqZqIa4sbc1lOnDhh1X78+HGqVq1q86UhERERKV3sPrQ0bdoUNzc31q5da2nLzs7m22+/pX379iVYmYiIiBSmUjV+m5GRwdatWwGIj48nNTWVdevWAdCiRQu8vb0ZNGgQZ8+eZcOGDcD1SzjDhw8nIiICb29v6tatS0xMDImJiQwdOrTEjkVEREQKV6kKLZcvX2bMmDFWbTc+f/HFF7Rs2ZLc3Nw8k2OHDRuG2Wxm3rx5XLlyhXr16hEVFUW1atWKrXYREREpWqUqtPj7+/PHH3/cdp2FCxfmaTMYDAwfPpzhw4cXVWkiIiJSwux+TouIiIjcHRRaRERExC4otIiIiIhdUGgRERERu6DQIiIiInZBoUVERETsgkKLiIiI2AWFFhEREbELCi0iIiJiFxRaRERExC4otIiIiIhdUGgRERERu6DQIiIiInZBoUVERETsgkKLiIiI2AWFFhEREbELCi0iIiJiFxRaRERExC4otIiIiIhdUGgRERERu6DQIiIiInZBoUVERETsgkKLiIiI2AWFFhEREbELCi0iIiJiFxRaRERExC4otIiIiIhdcCzpAv7q2LFjvP/+++zbt48KFSoQFhbGSy+9hLOz8237hYaGEh8fn6f9wIEDuLi4FFW5IiIiUkxKVWhJSkpi0KBB1KhRg4iICBISEpg8eTLXrl1jwoQJf9u/W7duDBkyxKrt78KOiIiI2IdSFVq+/PJL0tLSmDFjBl5eXgCYTCYmTpzI8OHD8fX1vW3/ypUr07hx46IvVERERIpdqZrTsm3bNkJCQiyBBaBHjx7k5uayffv2kitMRERESlypGmk5fvw4jz32mFWbh4cHPj4+HD9+/G/7x8bGsmzZMpycnGjWrBnjxo0jICDA5noyMzMxmUwF6mMwGDCZTJhycsjJybF536WRyZSDmeujX2Xt2IwmE7kmExkZGZjN5iLdl84R+3Qn54irq2sRVSVydylVoSU5ORkPD4887Z6eniQlJd22b2hoKEFBQVStWpW4uDhmz55Nv379WLlyJdWqVbOpnkOHDhW4j5OTEw5O7iQnJ5OYmGrTfkurVC8XTKYcUlKSSbt6taTLKVTlcw2kpqVy+VIi2dnZRbovnSP26U7OkeDg4CKqSuTuUqpCy50YP3685e/NmjWjTZs29OjRg6ioKN555x2bttmwYUObRlrOX0zFw8MDk7nMfL0AuLm7YTQ64u7ugTO5JV1OoXJx98Stghve/j7FMtKic8T+FOc5IiI3V6r+x/Tw8CAlJSVPe1JSEp6engXaVpUqVQgODubXX3+1uR5bb5U2GjMwOjri6Fiqvt47ZjQ6YgCMRmMZPDYjDkYj5cuXL6b96RyxN8V9johIXqVqIm6tWrXyzF1JSUnh4sWL1KpVq4SqEhERkdKgVIWW9u3bs2PHDpKTky1t69atw8HBgTZt2hRoWwkJCezZs4fAwMDCLlNERERKQKkav+3bty8LFy5k5MiRDB8+nISEBD7++GP69u1r9YyWQYMGcfbsWTZs2ADA6tWr2bx5Mx06dKBKlSrExcURGRmJ0WjkmWeeKanDERERkUJUqkKLp6cnCxYs4L333mPkyJFUqFCBxx9/nLFjx1qtl5ubazVB1t/fnwsXLvDBBx+QkpKCu7s7rVq1YvTo0TbfOSQiIiKlS6kKLQC1a9dm/vz5t11n4cKFVp8bN26cp01ERETKllI1p0VERETkVhRaRERExC4otIiIiIhdUGgRERERu6DQIiIiInZBoUVERETsgkKLiIiI2AWFFhEREbELCi0iIiJiFxRaRERExC4otIiIiIhdUGgRERERu6DQIiIiInZBoUVERETsgkKLiIiI2AWFFhEREbELCi0iIiJiFxRaRERExC4otIiIiIhdUGgRERERu6DQIiIiInZBoUVERETsgkKLiIiI2AWFFhEREbELCi0iIiJiFxRaRERExC4otIiIiIhdKHWh5dixYzzzzDM0btyYNm3a8PHHH5OVlfW3/cxmM5GRkXTs2JGgoCD69OnD/v37i75gERERKRalKrQkJSUxaNAgsrOziYiIYOzYsSxbtozJkyf/bd+5c+cyffp0Bg8ezJw5c/Dx8WHIkCHExcUVQ+UiIiJS1BxLuoA/+/LLL0lLS2PGjBl4eXkBYDKZmDhxIsOHD8fX1/em/TIzM5kzZw5Dhgxh8ODBAAQHB9O9e3eioqJ45513iucAREREpMiUqpGWbdu2ERISYgksAD169CA3N5ft27ffst/evXtJTU2lR48eljZnZ2e6dOnCtm3birJkERERKSalaqTl+PHjPPbYY1ZtHh4e+Pj4cPz48dv2A6hVq5ZVe+3atVmwYAHXrl2jXLlyBarljz/+IDMzs0B9bjCZcnkgxJfcXLNN/Usro6MD8VcScajXBhdzbkmXU6gMBgeOn7uAIeFysexP54j9uZNzxMXFhYCAgCKoSuTuUqpCS3JyMh4eHnnaPT09SUpKum0/Z2dnXFxcrNo9PDwwm80kJSUVOLQAGAyGAvcBcHQ04ulR3qa+9sDolvffSApG54iISMGVqtBSmui3IhERkdKlVM1p8fDwICUlJU97UlISnp6et+2XlZWV53JOcnIyBoPhtn1FRETEPpSq0FKrVq08c1dSUlK4ePFinvkqf+0HcOLECav248ePU7VqVZsuDYmIiEjpUqpCS/v27dmxYwfJycmWtnXr1uHg4ECbNm1u2a9p06a4ubmxdu1aS1t2djbffvst7du3L9KaRUREpHiUqjktffv2ZeHChYwcOZLhw4eTkJDAxx9/TN++fa2e0TJo0CDOnj3Lhg0bgOsz84cPH05ERATe3t7UrVuXmJgYEhMTGTp0aEkdjoiIiBSiUhVaPD09WbBgAe+99x4jR46kQoUKPP7444wdO9ZqvdzcXEwmk1XbsGHDMJvNzJs3jytXrlCvXj2ioqKoVq1acR6CiIiIFBGD2WwuWw+KEBERkTKpVM1pEREREbkVhRYRERGxCwotIiIiYhcUWkRERMQuKLSIiIiIXVBoEREREbtQqp7TIkUrPDycQ4cOsXr16jzLJk2axMaNG9m0aZOlbf/+/cyYMYPff/+dlJQUKleuTMOGDRk6dCiNGjUCICIighkzZgDX34pdoUIFqlatSvPmzenfvz+1a9fOs6+srCyWLFnCqlWrOHHiBCaTierVq9O1a1cGDRp00zd9S9HKzMwkODiYoUOHWj0XKSUlhRYtWnDPPfewefNmqz7PP/88p06dYs2aNcCtXzLq7OzMwYMHCQ0NJT4+/rZ1jBo1ihdffJGAgABeffXVmz4c8nbLRKRsU2iRm9qzZw8DBw6kXbt2TJw4kQoVKnDq1Cm+++47Dhw4YAktAOXKlWPBggUApKWl8b///Y+lS5eybNkyJk2aRFhYmGXdzMxMnn32Wfbv30///v156aWXcHZ25vfff2fhwoWkpKTwxhtvFPvx3u1cXFxo0KABe/futWrft28fLi4unD17loSEBKsnU+/bt48uXbpYrT9gwAB69uxp1ebgcH1Ad8aMGWRlZVnaR40aRdOmTRkyZIil7Z577im0YxKRskehRW4qJiYGPz8/Zs6cidFoBCAkJIS+ffuSm5trta6DgwONGze2fG7Tpg39+vXjueee480336Rp06aWJxNPmzaN3bt3ExUVRevWrS19WrVqRb9+/fL80JTi07RpU2JiYsjJycHR8fp/DXv37qV58+YcO3aMPXv28OCDDwLXX0Z69epVgoODrbbxj3/8w+pc+LP69etbfXZ2dqZy5cq3XF9E5K80p6WM2LRpEwEBAZw8edKqPSkpiaCgIBYvXlyg7SUnJ+Pt7W0JLH924zfn23FxceGtt94iOzub5cuXA3Dt2jViYmJ44IEHrALLn/uEhIQUqE7Jv3379jFixAjatm1L48aNCQsLY+XKlZblwcHBZGRk8Ntvv1na9u7dS5MmTWjSpIlVoLzx96ZNmxZb/SIiCi1lRIcOHfD19WXFihVW7Tfmr/Tq1atA22vQoAH79u3jX//6F8eOHbOppvvuuw9fX1/27dsHwKFDh0hPT6ddu3Y2bU/uzNmzZ2natCmTJk1i1qxZdO3alfHjx/Of//wH+L8AciOQ5OTkcPDgwVuGFh8fH+69916rfeTm5pKTk2P1568jc/l1s23l5OTYtC0RKRt0eaiMMBqNPProo6xYsYKXXnrJMkKyYsUKunTpUuDJrUOHDuWXX35h1qxZzJo1Cy8vL9q2bctTTz1Fs2bN8r2df/zjH1y6dAmACxcuWNqk+D300EOWv5vNZpo3b05CQgJLly7lkUcewdvbm5o1a7Jv3z4GDx7M4cOHyczMpFGjRnh4ePDhhx+SkZFB+fLl2bdv301HWaZMmcKUKVOs2kJCQpg/f36B673ZtkTk7qbQUoY8/vjjzJ49m++//56OHTty+PBhfv31V1555ZUCb8vNzY158+Zx4MABtmzZwp49e1i/fj3ffPMN7733Hk888US+tmM2mzEYDFZtf/0sxSMpKYmIiAg2btxIQkKC5U3pXl5elnWCg4PZtm0bcH00JSAgAFdXVwICAnB2duaXX34hICCAEydO0KdPnzz7GDhwIA8//LBVm5ubm0313mxbcP08F5G7k0JLGeLv70+bNm346quv6NixIytWrMDf359WrVoB10djbvyg+qvc3FzL5Ms/CwoKIigoCIC4uDgGDBjAlClT8h1azp8/T40aNQCoUqUKAOfOnSvooUkhCA8PZ9++fYwcOZL77rsPNzc3YmJiWLt2rWWdpk2b8tVXX3HmzBnLfBYAR0dHGjZsyN69e0lPT8dsNueZhAvX7/4JDAwslHoLc1siUjZoTksZ88QTT7BlyxYSEhKIjY3l0UcftYxseHt7Wy7V/NWFCxfw9va+7barVatG9+7dSUxMvOV2/uzIkSMkJCRYfvA1bNgQV1dXvv/++wIeldypzMxMtmzZwvPPP8+AAQMICQkhMDAQs9lstd6NILJ371727dtn+bcDLPNa9u7di6urK/Xq1SvWYxARUWgpYzp37oyHhwf//Oc/SUpK4tFHH7Usa968OcnJyezatcuqT2pqKjt37qR58+aWtluFkpMnT+Ls7Py3c2QyMzN57733cHZ2tozKlCtXjqeeeooNGzbw008/3bTPjz/+mO9jlfzLysoiNzcXJycnS1tqaqrVwwQBatSoQaVKlfjmm284f/58ntDyyy+/sGfPHoKCgm46MiciUpT0v04Z4+TkRO/evYmKiqJt27ZWk17btm1Ls2bNGDVqFCNHjqROnTpcuHCBzz//HAcHBwYMGGBZd/z48ZhMJrp27UqNGjVITU1l/fr1bN68mUGDBuHs7GxZNzc3l/379wOQnp5uebhcXFwckydPxt/f37LumDFjOHjwIM899xz9+/endevWODk5cfjwYRYvXkynTp1023MRcHd3JzAwkLlz5+Lt7Y2joyORkZG4ublx5coVq3WbNm3Kd999h4+Pj9W/XePGjUlOTmbfvn288MILN93PuXPnLOfCn9WvX9/qnBERsYVCSxnUpUsXoqKieOyxx6zaHRwcmDNnDtOnTyc6OpoLFy7g5uZGq1atiIiIsMw5Aejfvz8rV65kzpw5XLx4kXLlynHvvfcyadIkHnnkEavtXrt2zTIp09XVFX9/f0JCQpgxY0aex/i7uLgQFRVleYx/TEwMubm5VK9enbCwMAYNGlRE34pMnTqVCRMmEB4ejpeXFwMGDCA9PZ158+ZZrRccHMyGDRvy3B1UsWJFatSowcmTJ286nwVg4cKFLFy4ME/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 |
--------------------------------------------------------------------------------