Contracts were deployed to Rinkeby testnet using brownie cli and Alchemy nodes.
-> go to tokens dir
yarn hardhat run scripts/deploy.js --network rinkebyalchemy
Deploying contracts with the account: 0x1ff6aa202687612137c88D47369671A7654c0c56
DAI contract address: 0xF86176aF4687a9E65177913Ebe0A333D79E19fF4, supply: 100000000000000000000000
USDC contract address: 0x28D0C916Df5bDBB6636b90E25A97363d009eF7e0, supply: 100000000000
USDT contract address: 0x9BcB3F98236eE1eFB9455637Fa69E2BE27963725, supply: 100000000000
sbUSD contract address: 0xFe8cD5c88B3e0135b83d914C75dA211eCA13B1E5, supply: 100000000000000000000000
DAI and sbUSD have 18 decimals. USDC and USDT have 6 decimals.
-> go to curve dao dir
brownie run deployment/deploy_dao development --network rinkebyalchemy
Running 'scripts/deployment/deploy_dao.py::development'...
(...)
ERC20CRV deployed at: 0xD87FAaC4eE2954a1900A0EEE3846640257C5D3aB
VotingEscrow deployed at: 0x5af132d964D78b205904234564EB5E4a83F96272
GaugeController deployed at: 0x6BaA06a8b785C868f45eDc435D405C1EFe0eaaCb
PoolProxy deployed at: 0x5640568e9c925d71E62d95A6B69a5bf48f2B0D4A
Minter deployed at: 0x209d2b2e6F5EA6f9A6392E9e84C3bDde56E4D803
FeeDistributor deployed at: 0xB681Cb29c440Ac3E92169F00Aac93c868CBbe812
LiquidityGauge deployed at: 0xB10e4Ec05fDcAd9700d28B0063a69634802124f6
(...)
Deployment complete! Total gas used: 13114289
-> go to 3pool dir
brownie run deploy --network rinkebyalchemy -I
Running 'scripts/deploy.py::main'...
(...)
CurveTokenV2 deployed at: 0x9B19C4CA339DA7fd81FCb2F6eA55062107b8966b
StableSwap3Pool deployed at: 0xAa8684e82B496423559587e39986E0f85D988952
LiquidityGaugeV3 deployed at: 0xc864afaC0c7882Bd8Db55385FAC5bFBe82bf4E8e
(...)
Gas used in deployment: 0.0271 ETH
Configuration and checks:
-> Approve base pool to use tokens
>>> dai = ERC20Mock.at('0xF86176aF4687a9E65177913Ebe0A333D79E19fF4')
>>> dai.approve('0xAa8684e82B496423559587e39986E0f85D988952', 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> usdc = ERC20Mock.at('0x28D0C916Df5bDBB6636b90E25A97363d009eF7e0')
>>> usdc.approve('0xAa8684e82B496423559587e39986E0f85D988952', 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> usdt = ERC20Mock.at('0x9BcB3F98236eE1eFB9455637Fa69E2BE27963725')
>>> usdt.approve('0xAa8684e82B496423559587e39986E0f85D988952', 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> crv3 = CurveTokenV2.at('0x9B19C4CA339DA7fd81FCb2F6eA55062107b8966b')
>>> crv3.approve('0xAa8684e82B496423559587e39986E0f85D988952', 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
#---> init pool with creating initial pool balances (otherwise you will get in D0 = 0 and division by 0 error)
#-> allow my self to send tokens
>>> dai.approve(ADMIN, 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> usdc.approve(ADMIN, 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> usdt.approve(ADMIN, 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> sbUsd.approve(ADMIN, 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> crv3.approve(ADMIN, 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
#-> send DAI, USDCm, USDT to admin account (which is basally the 3Pool contract account) -- here we send 10 000 of each coin
>>> dai.transferFrom(ADMIN, '0xAa8684e82B496423559587e39986E0f85D988952', 10000*10**18, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> usdc.transferFrom(ADMIN, '0xAa8684e82B496423559587e39986E0f85D988952', 10000*10**6, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> usdt.transferFrom(ADMIN, '0xAa8684e82B496423559587e39986E0f85D988952', 10000*10**6, {'from': ADMIN, 'priority_fee': '3 gwei'})
#-> assign admin base tokens (DAI, USDCm, UDST) to be the pool tokens (there is no funds transfer just assign that ERC20 pool(admin) tokens are also internal 3pool contract tokens)
# it must be done otherwise there will be a divins by zero (and you can not even add liquidity)
>>> pool = StableSwap3Pool.at('0xAa8684e82B496423559587e39986E0f85D988952')
>>> pool.donate_admin_fees()
#--> add liquidity function test
#-> add 3 dai, 3 usdc and 3 usdt to pool form transaction sender account
>>> pool.add_liquidity([3000000000000000000, 3000000, 3000000], 0, {'from': ADMIN, 'priority_fee': '3 gwei'})
{
Transaction sent: 0x0dc7f88fe01f588ae499b4d2593da0c6d3500bc2b5329c836508e1d902d6ab42
StableSwap3Pool.add_liquidity confirmed Block: 10122972 Gas used: 179373 (80.94%) Gas price: 3.00000001 gwei
}
#--> make exchange test on 3Pool
#-> perform exchange 1 usdc to 1 usdt
>>> expected = pool.get_dy(1, 2, 1*10**6) * 0.99 # = 989604.0
>>> pool.exchange(1, 2, 1*10**6, expected, {'from': ADMIN, 'priority_fee': '3 gwei'})
{
Transaction sent: 0x397c91eabc25f8b2a9313b77db254027deece43a423c61baaff2588c8a9a4f7e
StableSwap3Pool.exchange confirmed Block: 10122990 Gas used: 108439 (75.52%) Gas price: 3.00000001 gwei
}
ADMIN is deployer and owner account
-> go to metapool dir
brownie run deploy --network rinkebyalchemy -I
Running 'scripts/deploy.py::main'...
Factory deployed at: 0x2c3CfDed0C08F399b6C51910f38AadF0A47191cf #Gas used: 2861190
MetaUSDBalances deployed at: 0xf3646529c010b8B6f364D7AB661647ff600FB288 #Gas used: 5339051
OwnerProxy deployed at: 0xF5067Fde1878eEb578388B9dA52f0d67C4A40e62 #Gas used: 1022108
DepositZapUSD deployed at: 0x8780f4341e0e4f869Ab246a0A894d712be2BCB5F #Gas used: 1355519
Configuration and checks:
#--> approve spending by Uniswap router (on rinkeby) as fallback on high slippage :
>>> dai.approve('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> usdc.approve('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> usdt.approve('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> sbUsd.approve('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
#-> create Meta-pool
>>> base3PoolAddr = '0xAa8684e82B496423559587e39986E0f85D988952'
>>> sbUsdAddr = '0xFe8cD5c88B3e0135b83d914C75dA211eCA13B1E5'
>>> factory.deploy_metapool(base3PoolAddr, "sbUsd Metapool", "sbUsd", sbUsdAddr, 10, 4000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
{
Transaction sent: 0xbfa7ffca0f44ff5c6173eab5be60c21c87bae6ed739d0cd4880af6ed24a40207
Factory.deploy_metapool confirmed Block: 10128257 Gas used: 855145 (90.91%) Gas price: 3.00000001 gwei
}
#-> get the addres of deplyed metapool
>>> factory.pool_list(0)
0xf3646529c010b8B6f364D7AB661647ff600FB288
#------> check Metapool
>>> meta = MetaUSDBalances.at('0xf3646529c010b8B6f364D7AB661647ff600FB288')
#-> allow transfers for metaPool
>>> sbUsd.approve('0xf3646529c010b8B6f364D7AB661647ff600FB288', 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> crv3.approve('0xf3646529c010b8B6f364D7AB661647ff600FB288', 20000000000000000000000000, {'from': ADMIN, 'priority_fee': '3 gwei'})
#-----> init initial balances for metapool for sbUSD and crv3 (they can not be 0) - otherwise it will be division by 0
>>> sbUsd.transferFrom(ADMIN, '0xf3646529c010b8B6f364D7AB661647ff600FB288', 10000*10**18, {'from': ADMIN, 'priority_fee': '3 gwei'})
>>> crv3.transferFrom(ADMIN, '0xf3646529c010b8B6f364D7AB661647ff600FB288', 10000*10**18, {'from': ADMIN, 'priority_fee': '3 gwei'})
#-> allow transfers for metaPool when it will fallback to using uniswap
(...)
#-> test add liquidity (add 10 sbUsd and 10 LP to the pool)
>>> meta.add_liquidity([10*10**18, 10*10**18], 0, {'from': ADMIN, 'priority_fee': '3 gwei'})
{
Transaction sent: 0x3e469e95cafbced3a0c58095745a201c90afbc818acadfb1ffb6e39f2f53ef89
MetaUSDBalances.add_liquidity confirmed Block: 10128378 Gas used: 176731 (79.71%) Gas price: 3.000000015 gwei
}
Do exchange using DAI and sbUSD (we can see that 0.999 DAI was bought for 1 sbUSD so we have the peg):
#----> do exchange on underlying assets (buy DAI with one sbUSD)
>>> meta.get_dy_underlying(0, 1, 10**18)
#--- result = 999390675280087667 ~= 0.999 DAI for one sbUSD
>>> expected = meta.get_dy_underlying(0, 1, 10**18) * 0.9
>>> dai.balanceOf(ADMIN)
89101930192066263375045
>>> sbUsd.balanceOf(ADMIN)
88489000000000000000000
>>> meta.exchange_underlying(0, 1, 10**18, expected, {'from': ADMIN, 'priority_fee': '3 gwei'})
{
Transaction sent: 0x7432ec1b3e285c8f4837a8382a0c546547a613dd673e3460eaf72b8ef348fd83
MetaUSDBalances.exchange_underlying confirmed Block: 10128452 Gas used: 231171 (71.15%) Gas price: 3.000000012 gwei
}
>>> dai.balanceOf(ADMIN)
89102929563925958886444
>>> sbUsd.balanceOf(ADMIN)
88488000000000000000000
Do exchange using Uniswap when slippage is too high:
#----> use uniswap fallback
>>> dai.balanceOf(ADMIN)
89103928916970489340209
>>> sbUsd.balanceOf(ADMIN)
88487000000000000000000
>>> expected = 1999315415451497164 # (to simulate slippage too high)
>>> meta.exchange_underlying_with_opt(0, 1, 10**18, expected, 1, {'from': ADMIN, 'priority_fee': '3 gwei'})
{
Transaction sent: 0xd2e44d64a634d8e1c1e04d535ddb7e76c910b8f074c51bf45b7c213c569a740a
MetaUSDBalances.exchange_underlying_with_opt confirmed Block: 10132896 Gas used: 316955 (76.76%) Gas price: 3.193318674 gwei
}
>>> dai.balanceOf(ADMIN)
89104909658467529263339
>>> sbUsd.balanceOf(ADMIN)
88486000000000000000000
Uniswap was used instead of 1Inch cos 1Inch v2-protocol is already deprecated and new AggregationProtocol is recommended to be used with their web API rather than smart contract calls (also it would be quite hard to do it).
On Rinkeby testnet there no contracts useful help with high slippage so in that situation so none was implemented.
But if we would be on mainnet following could be used:
- use Mooniswap which solves (not 100% but a lot) the front running and slippage issues
- use Sorbet finance and their limit orders based on Gelato and Uniswap
- use Gelato and implement delayed swap execution in hope the slippage will be smaller.
- use Flahbots bundle or MistX (latter is also using Flashbots) to avoid front running and send transaction directly to miners, but this had to be submitted off-chain not from smart contracts