├── tests ├── __init__.py ├── tricrypto │ ├── __init__.py │ ├── simulation_int_many.py │ ├── fuzz │ │ ├── simulation_int_many.py │ │ ├── pypystart │ │ ├── plot_curve.py │ │ ├── plot_curve_diff.py │ │ └── exponential.py │ ├── test_a_gamma.py │ ├── test_stateful_ramp_nocheck.py │ ├── forked │ │ ├── conftest.py │ │ ├── test_zap_add_liquidity.py │ │ ├── test_zap_remove_liquidity_one_coin.py │ │ └── test_zap_remove_liquidity.py │ ├── test_stateful_multiprecision.py │ ├── test_stateful_ramp.py │ ├── test_stateful_admin_fee.py │ ├── test_simulate.py │ └── test_gas_realistic.py ├── twocrypto │ ├── __init__.py │ ├── simulation_int_many.py │ ├── fuzz │ │ ├── simulation_int_many.py │ │ └── pypystart │ ├── forked_aave │ │ ├── test_zap_oracle_a.py │ │ ├── test_zap_exchange_underlying_a.py │ │ ├── test_zap_add_liquidity_a.py │ │ ├── test_zap_remove_liquidity_one_coin_a.py │ │ ├── test_zap_remove_liquidity_a.py │ │ └── conftest.py │ ├── forked_mainnet │ │ ├── test_zap_oracle.py │ │ ├── test_zap_exchange_underlying.py │ │ ├── test_zap_add_liquidity.py │ │ ├── test_zap_remove_liquidity.py │ │ ├── test_zap_remove_liquidity_one_coin.py │ │ └── conftest.py │ ├── test_a_gamma.py │ ├── test_stateful_ramp_nocheck.py │ ├── test_exchange.py │ ├── conftest.py │ ├── test_callback.py │ ├── test_stateful_multiprecision.py │ ├── test_stateful_ramp.py │ ├── test_stateful_admin_fee.py │ ├── test_simulate.py │ └── test_gas_realistic.py └── token │ ├── conftest.py │ └── test_transfer.py ├── scripts ├── __init__.py ├── check_migration_matic.py ├── check_migration.py ├── deploy_mainnet_cvx_pool.py ├── deploy_mainnet_spell_pool.py ├── deploy_mainnet_t_pool.py ├── deploy_mainnet_euroc_metapool.py ├── deploy_mainnet_xaut_metapool.py ├── deploy_poly_eurs_metapool.py ├── deploy_mainnet_eurs_pool.py ├── deploy_mainnet_crv_pool.py ├── deploy_poly_eurt_metapool.py ├── deploy_arbi_eurs_metapool.py ├── deploy_plain_polygon.py ├── deploy_mainnet_eurt_metapool.py ├── deploy_fantom.py ├── deploy_harmony_metapool.py └── deploy.py ├── .gitattributes ├── requirements.txt ├── .flake8 ├── .gitignore ├── deployment-logs ├── 2022-03-09. EURS-polygon │ ├── package.json │ ├── eurs.log │ └── zap.json ├── 2021-07-15 │ └── log.txt ├── 2022-01-03. T │ └── deployment.log ├── 2021-11-24. CRV │ └── deployment.log ├── 2021-12-11. CVX │ ├── deployment.log │ └── CurveTokenV4.abi ├── 2022-01-03. SPELL │ └── deployment.log ├── 2021-11-01. EURS on mainnet │ └── deployment.log ├── 2021-10-30. EURS │ ├── deployment.log │ └── zap.json ├── 2021-12-22. XAUT │ ├── deployment.log │ └── zap.json ├── 2022-06-29. EUROC │ ├── deployment.log │ └── zap.json ├── 2021-10-31. EURT on mainnet │ ├── deployment.log │ └── zap.json ├── 2021-05-28 │ ├── log.log │ ├── CurveCryptoViews3.vy │ └── token.json ├── 2021-07-13 │ ├── addresses.txt │ └── CryptoViews.vy ├── 2021-09-17. Fantom │ └── deployment.log ├── 2021-10-30. EURT │ ├── deployment.log │ └── zap.json ├── 2021-09-13. Arbitrum │ ├── log.txt │ └── zap.json ├── 2021-05-24 │ ├── log.log │ ├── zap.json │ ├── CurveCryptoViews3.vy │ └── token.json ├── 2021-08-14. polygon │ ├── log.txt │ ├── zap.json │ └── CryptoViews.vy ├── 2021-10-04. Avax │ ├── deployment.log │ └── zap.json ├── 2021-10-17. Hamony │ ├── deployment.log │ └── zap.json ├── 2021-08-27. Polygon redeployment │ ├── log.txt │ ├── zap.json │ └── CryptoViews.vy ├── 17-May-plain-mainnet.txt └── 14-May-plain-mainnet.txt ├── brownie-config.yaml ├── hardhat.config.js └── contracts └── testing ├── TestMath.vy ├── CallbackTestZap.vy ├── ERC20Mock.vy └── WETH.vy /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/tricrypto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/twocrypto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/tricrypto/simulation_int_many.py: -------------------------------------------------------------------------------- 1 | ../simulation_int_many.py -------------------------------------------------------------------------------- /tests/twocrypto/simulation_int_many.py: -------------------------------------------------------------------------------- 1 | ../simulation_int_many.py -------------------------------------------------------------------------------- /tests/tricrypto/fuzz/simulation_int_many.py: -------------------------------------------------------------------------------- 1 | ../simulation_int_many.py -------------------------------------------------------------------------------- /tests/twocrypto/fuzz/simulation_int_many.py: -------------------------------------------------------------------------------- 1 | ../../simulation_int_many.py -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.vy linguist-language=Python 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | black 2 | brownie-token-tester>=0.1.0 3 | eth-brownie 4 | flake8 5 | isort 6 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 160 3 | ignore = E731,E126,E121,E123,E221,E114,E116,E402,E226,W503,E501 4 | -------------------------------------------------------------------------------- /tests/tricrypto/fuzz/pypystart: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pypy3 --jit enable_opts=all,threshold=50,vec=1,vec_all=1 $1 $2 4 | -------------------------------------------------------------------------------- /tests/twocrypto/fuzz/pypystart: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pypy3 --jit enable_opts=all,threshold=50,vec=1,vec_all=1 $1 $2 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.pytest_cache 2 | .venv 3 | /build 4 | *.pyc 5 | .pypy 6 | .hypothesis 7 | /node_modules 8 | /package-lock.json 9 | -------------------------------------------------------------------------------- /deployment-logs/2022-03-09. EURS-polygon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "hardhat": "^2.6.4" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /deployment-logs/2021-07-15/log.txt: -------------------------------------------------------------------------------- 1 | Migration zap: 0xD10d54830714003575d9f472d62268A29C902E5A 2 | Deposit zap: 0x3993d34e7e99Abf6B6f367309975d1360222D446 3 | -------------------------------------------------------------------------------- /deployment-logs/2022-01-03. T/deployment.log: -------------------------------------------------------------------------------- 1 | Swap address: 0x752eBeb79963cf0732E9c0fec72a49FD1DEfAEAC 2 | Token address: 0xCb08717451aaE9EF950a2524E33B6DCaBA60147B 3 | -------------------------------------------------------------------------------- /deployment-logs/2021-11-24. CRV/deployment.log: -------------------------------------------------------------------------------- 1 | Swap address: 0x8301AE4fc9c624d1D396cbDAa1ed877821D7C511 2 | Token address: 0xEd4064f376cB8d68F770FB1Ff088a3d0F3FF5c4d 3 | -------------------------------------------------------------------------------- /deployment-logs/2021-12-11. CVX/deployment.log: -------------------------------------------------------------------------------- 1 | Swap address: 0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4 2 | Token address: 0x3A283D9c08E8b55966afb64C515f5143cf907611 3 | -------------------------------------------------------------------------------- /deployment-logs/2022-01-03. SPELL/deployment.log: -------------------------------------------------------------------------------- 1 | Swap address: 0x98638FAcf9a3865cd033F36548713183f6996122 2 | Token address: 0x8282BD15dcA2EA2bDf24163E8f2781B30C43A2ef 3 | -------------------------------------------------------------------------------- /deployment-logs/2021-11-01. EURS on mainnet/deployment.log: -------------------------------------------------------------------------------- 1 | Swap address: 0x98a7F18d4E56Cfe84E3D081B40001B3d5bD3eB8B 2 | Token address: 0x3D229E1B4faab62F621eF2F6A610961f7BD7b23B 3 | -------------------------------------------------------------------------------- /deployment-logs/2021-10-30. EURS/deployment.log: -------------------------------------------------------------------------------- 1 | Swap address: 0xA827a652Ead76c6B0b3D19dba05452E06e25c27e 2 | Token address: 0x3dFe1324A0ee9d86337d06aEB829dEb4528DB9CA 3 | Zap address: 0x25e2e8d104BC1A70492e2BE32dA7c1f8367F9d2c 4 | -------------------------------------------------------------------------------- /deployment-logs/2021-12-22. XAUT/deployment.log: -------------------------------------------------------------------------------- 1 | Swap address: 0xAdCFcf9894335dC340f6Cd182aFA45999F45Fc44 2 | Token address: 0x8484673cA7BfF40F82B041916881aeA15ee84834 3 | Zap address: 0xc5FA220347375ac4f91f9E4A4AAb362F22801504 4 | -------------------------------------------------------------------------------- /deployment-logs/2022-06-29. EUROC/deployment.log: -------------------------------------------------------------------------------- 1 | Swap address: 0xE84f5b1582BA325fDf9cE6B0c1F087ccfC924e54 2 | Token address: 0x70fc957eb90E37Af82ACDbd12675699797745F68 3 | Zap address: 0xd446A98F88E1d053d1F64986E3Ed083bb1Ab7E5A 4 | -------------------------------------------------------------------------------- /deployment-logs/2022-03-09. EURS-polygon/eurs.log: -------------------------------------------------------------------------------- 1 | Swap address: 0x9b3d675FDbe6a0935E8B7d1941bc6f78253549B7 2 | Token address: 0x7BD9757FbAc089d60DaFF1Fa6bfE3BC99b0F5735 3 | Zap address: 0x4DF7eF55E99a56851187822d96B4E17D98A47DeD 4 | 5 | -------------------------------------------------------------------------------- /deployment-logs/2021-10-31. EURT on mainnet/deployment.log: -------------------------------------------------------------------------------- 1 | Swap address: 0x9838eCcC42659FA8AA7daF2aD134b53984c9427b 2 | Token address: 0x3b6831c0077a1e44ED0a21841C3bC4dC11bCE833 3 | Zap address: 0x5D0F47B32fDd343BfA74cE221808e2abE4A53827 4 | -------------------------------------------------------------------------------- /brownie-config.yaml: -------------------------------------------------------------------------------- 1 | reports: 2 | exclude_paths: 3 | - contracts/testing/*.* 4 | only_include_project: false 5 | 6 | networks: 7 | development: 8 | cmd_settings: 9 | default_balance: 1000000 10 | chain_id: 1337 11 | -------------------------------------------------------------------------------- /deployment-logs/2021-05-28/log.log: -------------------------------------------------------------------------------- 1 | Swap: 0x80466c64868E1ab14a1Ddf27A676C3fcBE638Fe5 2 | Token: 0xcA3d75aC011BF5aD07a98d02f18225F9bD9A6BDF 3 | 4 | Math: 0x656Dd75d33a6241A0C4C2368eb00441Ad3113ec0 5 | Views: 0xCfB3CFEaE8c3F39aECDf7ec275a00d29ECA08535 6 | -------------------------------------------------------------------------------- /deployment-logs/2021-07-13/addresses.txt: -------------------------------------------------------------------------------- 1 | Swap: 0xD51a44d3FaE010294C616388b506AcdA1bfAAE46 2 | Token: 0xc4AD29ba4B3c580e6D59105FFf484999997675Ff 3 | Math: 0x8F68f4810CcE3194B6cB6F3d50fa58c2c9bDD1d5 4 | Views: 0x40745803C2faA8E8402E2Ae935933D07cA8f355c 5 | -------------------------------------------------------------------------------- /deployment-logs/2021-09-17. Fantom/deployment.log: -------------------------------------------------------------------------------- 1 | Deployed at: 2 | Swap: 0x3a1659Ddcf2339Be3aeA159cA010979FB49155FF 3 | Token: 0x58e57cA18B7A47112b877E31929798Cd3D703b0f 4 | Math: 0x939986418baFb4E2d82A76E320767Ff02d250203 5 | Views: 0x4643A6600eae4851677A1f16d5e40Ef868c71717 6 | -------------------------------------------------------------------------------- /deployment-logs/2021-10-30. EURT/deployment.log: -------------------------------------------------------------------------------- 1 | Swap address: 0xB446BF7b8D6D4276d0c75eC0e3ee8dD7Fe15783A 2 | Token address: 0x600743B1d8A96438bD46836fD34977a00293f6Aa 3 | --(not used) Zap address: 0x15E4D087C13DF6e65B20A9b68a4dE485B00046Fc 4 | Zap address: 0x225FB4176f0E20CDb66b4a3DF70CA3063281E855 5 | -------------------------------------------------------------------------------- /deployment-logs/2021-09-13. Arbitrum/log.txt: -------------------------------------------------------------------------------- 1 | Swap: 0x960ea3e3C7FB317332d990873d354E18d7645590 2 | Token: 0x8e0B8c8BB9db49a46697F3a5Bb8A308e744821D2 3 | Math: 0x2F0AF8eC2f5893392843a0F647A30A141dba9DaF 4 | Views: 0x48A68C5511DfC355007b7B794890F26653A7bF93 5 | Zap: 0xF97c707024ef0DD3E77a0824555a46B622bfB500 6 | -------------------------------------------------------------------------------- /deployment-logs/2021-05-24/log.log: -------------------------------------------------------------------------------- 1 | Swap address: 0x751B1e21756bDbc307CBcC5085c042a0e9AaEf36 2 | Token address: 0x8096ac61db23291252574D49f036f0f9ed8ab390 3 | Zap address: 0x3FCD5De6A9fC8A99995c406c77DDa3eD7E406f81 4 | Views: 0x3c0EbfF09d6bB028EeD1Dd29669c2E3bf98C9D7f 5 | Math: 0x1689aCD208d817B32eEAcCcE8121278c5766F530 6 | -------------------------------------------------------------------------------- /deployment-logs/2021-08-14. polygon/log.txt: -------------------------------------------------------------------------------- 1 | Math address: 0xcc3A9bA96d54dd5c638ba2dC5884E7793b13fAd4 2 | Views address: 0x048A8016EE2623B772ce857011137125B43FcF11 3 | Swap address: 0x92577943c7aC4accb35288aB2CC84D75feC330aF 4 | Token address: 0xbece5d20A8a104c54183CC316C8286E3F00ffC71 5 | Zap address: 0x3Fa8ebd5d16445b42e0b6A54678718C94eA99aBC 6 | -------------------------------------------------------------------------------- /deployment-logs/2021-10-04. Avax/deployment.log: -------------------------------------------------------------------------------- 1 | Math address: 0x06Ce8086965234400FDecAb190B115C2C0717047 2 | Views address: 0x3Fd1A0ae6a996b8f972f0089A4820f05c611Ec17 3 | Swap address: 0xB755B949C126C04e0348DD881a5cF55d424742B2 4 | Token address: 0x1daB6560494B04473A0BE3E7D83CF3Fdf3a51828 5 | Zap address: 0x58e57cA18B7A47112b877E31929798Cd3D703b0f 6 | -------------------------------------------------------------------------------- /deployment-logs/2021-10-17. Hamony/deployment.log: -------------------------------------------------------------------------------- 1 | Math address: 0x0b1fb299e8BA940c70E64b1Dfc78A542Dc55b500 2 | Views address: 0xDFFD35f857B3C5f06B7439cD014f0f3a171a2149 3 | Swap address: 0x0e3Dc2BcbFEa84072A0c794B7653d3db364154e0 4 | Token address: 0x99E8eD28B97c7F1878776eD94fFC77CABFB9B726 5 | Zap address: 0x76147c0C989670D106b57763a24410A2a22e335E 6 | -------------------------------------------------------------------------------- /deployment-logs/2021-08-27. Polygon redeployment/log.txt: -------------------------------------------------------------------------------- 1 | Math address: 0xe59547896E5FC17Be96c885106dbafEE760AAdDE 2 | Views address: 0xb401e0482f02127543a9D4bb4d5be30C70437F35 3 | Swap address: 0x92215849c439E1f8612b6646060B4E3E5ef822cC 4 | Token address: 0xdAD97F7713Ae9437fa9249920eC8507e5FbB23d3 5 | Zap address: 0x1d8b86e3D88cDb2d34688e87E72F388Cb541B7C8 6 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_aave/test_zap_oracle_a.py: -------------------------------------------------------------------------------- 1 | from brownie import interface 2 | 3 | 4 | def test_oracle(crypto_zap): 5 | base_pool = interface.StableSwap3Pool(crypto_zap.base_pool()) 6 | vp = base_pool.get_virtual_price() 7 | price = 1 / 0.8 8 | 9 | assert (crypto_zap.price_oracle() - vp * price) / (vp * price) < 1e-4 10 | assert (crypto_zap.price_scale() - vp * price) / (vp * price) < 1e-4 11 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_mainnet/test_zap_oracle.py: -------------------------------------------------------------------------------- 1 | from brownie import interface 2 | 3 | 4 | def test_oracle(crypto_zap): 5 | base_pool = interface.StableSwap3Pool(crypto_zap.base_pool()) 6 | vp = base_pool.get_virtual_price() 7 | price = 1 / 0.8 8 | 9 | assert (crypto_zap.price_oracle() - vp * price) / (vp * price) < 1e-4 10 | assert (crypto_zap.price_scale() - vp * price) / (vp * price) < 1e-4 11 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | 2 | // autogenerated by brownie 3 | // do not modify the existing settings 4 | module.exports = { 5 | networks: { 6 | hardhat: { 7 | hardfork: "london", 8 | // base fee of 0 allows use of 0 gas price when testing 9 | initialBaseFeePerGas: 0, 10 | // brownie expects calls and transactions to throw on revert 11 | throwOnTransactionFailures: true, 12 | throwOnCallFailures: true 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /tests/tricrypto/fuzz/plot_curve.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pylab 3 | 4 | from simulation_int_many import newton_y 5 | 6 | 7 | A = 135 * 3**3 * 10000 8 | D = 3 9 | 10 | 11 | def get_y(x, gamma): 12 | X = [10**18] * 3 13 | X[0] = int(x * 1e18) 14 | return newton_y(A, int(1e18 * gamma), X, int(D * 1e18), 1) / 1e18 15 | 16 | 17 | x = np.logspace(-1, 1, 500) 18 | 19 | for gamma in [1e-2, 1e-3, 1e-4, 1e-6]: 20 | y = [get_y(_x, gamma) for _x in x] 21 | pylab.plot(x, y, label=str(gamma)) 22 | 23 | pylab.legend() 24 | pylab.grid() 25 | pylab.show() 26 | -------------------------------------------------------------------------------- /tests/tricrypto/fuzz/plot_curve_diff.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pylab 3 | 4 | from simulation_int_many import newton_y 5 | 6 | 7 | A = 135 * 3**3 * 10000 8 | D = 3 9 | 10 | 11 | def get_y(x, gamma): 12 | X = [10**18] * 3 13 | X[0] = int(x * 1e18) 14 | return newton_y(A, int(1e18 * gamma), X, int(D * 1e18), 1) / 1e18 15 | 16 | 17 | x = np.logspace(-1, 1, 500) 18 | 19 | for gamma in [1e-2, 1e-3, 1e-4, 1e-6]: 20 | y = [get_y(_x, gamma) for _x in x] 21 | pylab.plot(x, y - (1 - x), label=str(gamma)) 22 | 23 | pylab.legend() 24 | pylab.grid() 25 | pylab.show() 26 | -------------------------------------------------------------------------------- /tests/token/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="session") 5 | def alice(accounts): 6 | return accounts[0] 7 | 8 | 9 | @pytest.fixture(scope="session") 10 | def bob(accounts): 11 | return accounts[1] 12 | 13 | 14 | @pytest.fixture(scope="session") 15 | def charlie(accounts): 16 | return accounts[2] 17 | 18 | 19 | @pytest.fixture(scope="module") 20 | def token(alice, CurveTokenV5): 21 | return CurveTokenV5.deploy("Sample Token", "ST", {"from": alice}) 22 | 23 | 24 | @pytest.fixture(autouse=True) 25 | def isolate(fn_isolation): 26 | pass 27 | 28 | -------------------------------------------------------------------------------- /contracts/testing/TestMath.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.12 2 | N_COINS: constant(uint256) = 3 3 | A_MULTIPLIER: constant(uint256) = 100 4 | 5 | @internal 6 | @pure 7 | def sort(A0: uint256[N_COINS]) -> uint256[N_COINS]: 8 | """ 9 | Insertion sort from high to low 10 | """ 11 | A: uint256[N_COINS] = A0 12 | for i in range(1, N_COINS): 13 | x: uint256 = A[i] 14 | cur: uint256 = i 15 | for j in range(N_COINS): 16 | y: uint256 = A[cur-1] 17 | if y > x: 18 | break 19 | A[cur] = y 20 | cur -= 1 21 | if cur == 0: 22 | break 23 | A[cur] = x 24 | return A 25 | 26 | 27 | @external 28 | @view 29 | def pub_sort(A0: uint256[N_COINS]) -> uint256[N_COINS]: 30 | return self.sort(A0) 31 | -------------------------------------------------------------------------------- /tests/tricrypto/test_a_gamma.py: -------------------------------------------------------------------------------- 1 | def test_A_gamma(crypto_swap): 2 | assert crypto_swap.A() == 135 * 3**3 * 10000 3 | assert crypto_swap.gamma() == int(7e-5 * 1e18) 4 | 5 | 6 | def test_ramp_A_gamma(chain, crypto_swap, accounts): 7 | initial_A = 135 * 3**3 * 10000 8 | future_A = 300 * 3**3 * 10000 9 | initial_gamma = int(7e-5 * 1e18) 10 | future_gamma = int(2e-4 * 1e18) 11 | t0 = chain.time() 12 | t1 = t0 + 7 * 86400 13 | crypto_swap.ramp_A_gamma(future_A, future_gamma, t1, {'from': accounts[0]}) 14 | 15 | for i in range(1, 8): 16 | chain.sleep(86400) 17 | chain.mine() 18 | assert abs(crypto_swap.A() - (initial_A + (future_A - initial_A) * i / 7)) < 1e-4 * initial_A 19 | assert abs(crypto_swap.gamma() - (initial_gamma + (future_gamma - initial_gamma) * i / 7)) < 1e-4 * initial_gamma 20 | -------------------------------------------------------------------------------- /tests/twocrypto/test_a_gamma.py: -------------------------------------------------------------------------------- 1 | def test_A_gamma(crypto_swap): 2 | assert crypto_swap.A() == 90 * 2**2 * 10000 3 | assert crypto_swap.gamma() == int(2.8e-4 * 1e18) 4 | 5 | 6 | def test_ramp_A_gamma(chain, crypto_swap, accounts): 7 | initial_A = 90 * 2**2 * 10000 8 | future_A = 180 * 2**2 * 10000 9 | initial_gamma = int(2.8e-4 * 1e18) 10 | future_gamma = int(5e-4 * 1e18) 11 | t0 = chain.time() 12 | t1 = t0 + 7 * 86400 13 | crypto_swap.ramp_A_gamma(future_A, future_gamma, t1, {'from': accounts[0]}) 14 | 15 | for i in range(1, 8): 16 | chain.sleep(86400) 17 | chain.mine() 18 | assert abs(crypto_swap.A() - (initial_A + (future_A - initial_A) * i / 7)) < 1e-4 * initial_A 19 | assert abs(crypto_swap.gamma() - (initial_gamma + (future_gamma - initial_gamma) * i / 7)) < 1e-4 * initial_gamma 20 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_aave/test_zap_exchange_underlying_a.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | import itertools as it 4 | 5 | INITIAL_AMOUNTS = [24000 * 10**6, 10000 * 10**18, 10000 * 10**6, 10000 * 10**6] 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def lp_token_amount(alice, crypto_zap, token): 10 | crypto_zap.add_liquidity(INITIAL_AMOUNTS, 0, {"from": alice}) 11 | token.approve(crypto_zap, 2 ** 256 - 1, {"from": alice}) 12 | return token.balanceOf(alice) 13 | 14 | 15 | @pytest.mark.parametrize("i,j,scale", it.product(range(4), range(4), [0.3, 0.6, 0.9])) 16 | def test_exchange_underlying(alice, crypto_zap, lp_token_amount, underlying_coins, i, j, scale): 17 | dx = int(scale * INITIAL_AMOUNTS[i]) 18 | 19 | dx_taken = underlying_coins[i].balanceOf(alice) 20 | dy = underlying_coins[j].balanceOf(alice) 21 | 22 | if i == j: 23 | with brownie.reverts(): 24 | crypto_zap.exchange_underlying(i, j, dx, 0, {'from': alice}) 25 | return 26 | 27 | dy_calc = crypto_zap.get_dy_underlying(i, j, dx) 28 | 29 | with brownie.reverts(): 30 | crypto_zap.exchange_underlying(i, j, dx, int(1.1 * dy_calc), {'from': alice}) 31 | 32 | crypto_zap.exchange_underlying(i, j, dx, int(0.9 * dy_calc), {'from': alice}) 33 | 34 | dy = underlying_coins[j].balanceOf(alice) - dy 35 | dx_taken -= underlying_coins[i].balanceOf(alice) 36 | 37 | assert dx == dx_taken 38 | assert dy > 0 39 | assert abs(dy - dy_calc) / dy < 1e-2 40 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_mainnet/test_zap_exchange_underlying.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | import itertools as it 4 | 5 | INITIAL_AMOUNTS = [24000 * 10**6, 10000 * 10**18, 10000 * 10**6, 10000 * 10**6] 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def lp_token_amount(alice, crypto_zap, token): 10 | crypto_zap.add_liquidity(INITIAL_AMOUNTS, 0, {"from": alice}) 11 | token.approve(crypto_zap, 2 ** 256 - 1, {"from": alice}) 12 | return token.balanceOf(alice) 13 | 14 | 15 | @pytest.mark.parametrize("i,j,scale", it.product(range(4), range(4), [0.3, 0.6, 0.9])) 16 | def test_exchange_underlying(alice, crypto_zap, lp_token_amount, underlying_coins, i, j, scale): 17 | dx = int(scale * INITIAL_AMOUNTS[i]) 18 | 19 | dx_taken = underlying_coins[i].balanceOf(alice) 20 | dy = underlying_coins[j].balanceOf(alice) 21 | 22 | if i == j: 23 | with brownie.reverts(): 24 | crypto_zap.exchange_underlying(i, j, dx, 0, {'from': alice}) 25 | return 26 | 27 | dy_calc = crypto_zap.get_dy_underlying(i, j, dx) 28 | 29 | with brownie.reverts(): 30 | crypto_zap.exchange_underlying(i, j, dx, int(1.1 * dy_calc), {'from': alice}) 31 | 32 | crypto_zap.exchange_underlying(i, j, dx, int(0.9 * dy_calc), {'from': alice}) 33 | 34 | dy = underlying_coins[j].balanceOf(alice) - dy 35 | dx_taken -= underlying_coins[i].balanceOf(alice) 36 | 37 | assert dx == dx_taken 38 | assert dy > 0 39 | assert abs(dy - dy_calc) / dy < 1e-2 40 | -------------------------------------------------------------------------------- /tests/tricrypto/fuzz/exponential.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import hypothesis.strategies as st 3 | from hypothesis import given, settings 4 | 5 | 6 | def halfpow(power, precision): 7 | """ 8 | 1e18 * 0.5 ** (power/1e18) 9 | """ 10 | intpow = power // 10**18 11 | otherpow = power - intpow * 10**18 12 | if intpow > 59: 13 | return 0 14 | result = 10**18 // 2**intpow 15 | 16 | term = 10**18 17 | x = 5 * 10**17 18 | S = 10**18 # Avoiding negative numbers here 19 | neg = False 20 | for i in range(1, 256): 21 | K = i * 10**18 22 | c = (K - 10**18) 23 | if otherpow > c: 24 | c = otherpow - c 25 | neg = not neg 26 | else: 27 | c -= otherpow 28 | term = term * (c * x // 10**18) // K 29 | if neg: 30 | S -= term 31 | else: 32 | S += term 33 | assert S >= 0 34 | if term < precision: 35 | return result * S // 10**18 36 | raise ValueError("Did not converge") 37 | 38 | 39 | class TestExp(unittest.TestCase): 40 | @given( 41 | st.integers(0, 10**22), 42 | st.integers(10, 10**15) 43 | ) 44 | @settings(max_examples=10000) 45 | def test_exp(self, power, precision): 46 | pow_int = halfpow(power, precision) / 1e18 47 | pow_ideal = 0.5 ** (power / 1e18) 48 | assert abs(pow_int - pow_ideal) < max(5 * precision / 1e18, 5e-16) 49 | 50 | 51 | if __name__ == "__main__": 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /tests/tricrypto/test_stateful_ramp_nocheck.py: -------------------------------------------------------------------------------- 1 | from brownie.test import strategy 2 | from .test_stateful import NumbaGoUp 3 | 4 | MAX_SAMPLES = 20 5 | MAX_COUNT = 100 6 | MAX_D = 10**12 * 10**18 # $1T is hopefully a reasonable cap for tests 7 | ALLOWED_DIFFERENCE = 0.02 8 | 9 | 10 | class RampTest(NumbaGoUp): 11 | future_gamma = strategy('uint256', min_value=int(7e-5 * 1e18 / 9), max_value=int(7e-5 * 1e18 * 9)) 12 | future_A = strategy('uint256', min_value=135 * 3**3 * 10000 // 9, max_value=135 * 3**3 * 10000 * 9) 13 | 14 | def initialize(self, future_A, future_gamma): 15 | self.swap.ramp_A_gamma(future_A, future_gamma, self.chain.time() + 14*86400, {'from': self.accounts[0]}) 16 | 17 | def rule_exchange(self, exchange_amount_in, exchange_i, exchange_j, user): 18 | super()._rule_exchange(exchange_amount_in, exchange_i, exchange_j, user, False) 19 | 20 | def rule_remove_liquidity_one_coin(self, token_amount, exchange_i, user): 21 | super().rule_remove_liquidity_one_coin(token_amount, exchange_i, user, False) 22 | 23 | def invariant_virtual_price(self): 24 | # Invariant is not conserved here 25 | pass 26 | 27 | 28 | def test_ramp(crypto_swap, token, chain, accounts, coins, state_machine): 29 | from hypothesis._settings import HealthCheck 30 | 31 | state_machine(RampTest, chain, accounts, coins, crypto_swap, token, 32 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': MAX_COUNT, 'suppress_health_check': HealthCheck.all()}) 33 | -------------------------------------------------------------------------------- /scripts/check_migration_matic.py: -------------------------------------------------------------------------------- 1 | from brownie import accounts 2 | from brownie import PoolMigratorMatic23 as PoolMigratorMatic 3 | from brownie import Contract 4 | 5 | 6 | OLD_TOKEN = "0xbece5d20A8a104c54183CC316C8286E3F00ffC71" 7 | OLD_GAUGE = "0x9bd996Db02b3f271c6533235D452a56bc2Cd195a" 8 | 9 | NEW_TOKEN = "0xdAD97F7713Ae9437fa9249920eC8507e5FbB23d3" 10 | NEW_GAUGE = "0x3B6B158A76fd8ccc297538F454ce7B4787778c7C" 11 | 12 | 13 | def main(): 14 | accounts.load('babe') 15 | FROM = DEPLOYER = accounts[0] 16 | 17 | migrator = PoolMigratorMatic.deploy({'from': DEPLOYER}) 18 | 19 | old_gauge_token = Contract.from_explorer(OLD_GAUGE) 20 | old_lp_token = Contract.from_explorer(OLD_TOKEN) 21 | new_gauge_token = Contract.from_explorer(NEW_GAUGE) 22 | new_lp_token = Contract.from_explorer(NEW_TOKEN) 23 | 24 | print('Old Gauge:', old_gauge_token.balanceOf(FROM)/1e18) 25 | print('Old LP:', old_lp_token.balanceOf(FROM)/1e18) 26 | print('New Gauge:', new_gauge_token.balanceOf(FROM)/1e18) 27 | print('New LP:', new_lp_token.balanceOf(FROM)/1e18) 28 | 29 | old_gauge_token.approve(migrator, 2**256-1, {'from': FROM}) 30 | old_lp_token.approve(migrator, 2**256-1, {'from': FROM}) 31 | 32 | migrator.migrate_to_new_pool({'from': FROM}) 33 | 34 | print('Old Gauge:', old_gauge_token.balanceOf(FROM)/1e18) 35 | print('Old LP:', old_lp_token.balanceOf(FROM)/1e18) 36 | print('New Gauge:', new_gauge_token.balanceOf(FROM)/1e18) 37 | print('New LP:', new_lp_token.balanceOf(FROM)/1e18) 38 | 39 | print('Migrator:', migrator.address) 40 | -------------------------------------------------------------------------------- /scripts/check_migration.py: -------------------------------------------------------------------------------- 1 | from brownie import network 2 | from brownie import PoolMigrator 3 | from brownie import Contract 4 | 5 | 6 | FROM = "0x7a16fF8270133F063aAb6C9977183D9e72835428" 7 | DEPLOYER = "0xbabe61887f1de2713c6f97e567623453d3C79f67" 8 | 9 | OLD_TOKEN = "0xcA3d75aC011BF5aD07a98d02f18225F9bD9A6BDF" 10 | OLD_GAUGE = "0x6955a55416a06839309018A8B0cB72c4DDC11f15" 11 | 12 | NEW_TOKEN = "0xc4AD29ba4B3c580e6D59105FFf484999997675Ff" 13 | NEW_GAUGE = "0xDeFd8FdD20e0f34115C7018CCfb655796F6B2168" 14 | 15 | 16 | def main(): 17 | assert network.show_active() != 'mainnet' 18 | 19 | migrator = PoolMigrator.deploy({'from': DEPLOYER}) 20 | 21 | old_gauge_token = Contract.from_explorer(OLD_GAUGE) 22 | old_lp_token = Contract.from_explorer(OLD_TOKEN) 23 | new_gauge_token = Contract.from_explorer(NEW_GAUGE) 24 | new_lp_token = Contract.from_explorer(NEW_TOKEN) 25 | 26 | print('Old Gauge:', old_gauge_token.balanceOf(FROM)/1e18) 27 | print('Old LP:', old_lp_token.balanceOf(FROM)/1e18) 28 | print('New Gauge:', new_gauge_token.balanceOf(FROM)/1e18) 29 | print('New LP:', new_lp_token.balanceOf(FROM)/1e18) 30 | 31 | old_gauge_token.approve(migrator, 2**256-1, {'from': FROM}) 32 | old_lp_token.approve(migrator, 2**256-1, {'from': FROM}) 33 | 34 | migrator.migrate_to_new_pool({'from': FROM}) 35 | 36 | print('Old Gauge:', old_gauge_token.balanceOf(FROM)/1e18) 37 | print('Old LP:', old_lp_token.balanceOf(FROM)/1e18) 38 | print('New Gauge:', new_gauge_token.balanceOf(FROM)/1e18) 39 | print('New LP:', new_lp_token.balanceOf(FROM)/1e18) 40 | -------------------------------------------------------------------------------- /tests/twocrypto/test_stateful_ramp_nocheck.py: -------------------------------------------------------------------------------- 1 | from brownie.test import strategy 2 | from .test_stateful import NumbaGoUp 3 | 4 | MAX_SAMPLES = 20 5 | MAX_COUNT = 100 6 | MAX_D = 10**12 * 10**18 # $1T is hopefully a reasonable cap for tests 7 | ALLOWED_DIFFERENCE = 0.02 8 | 9 | 10 | class RampTest(NumbaGoUp): 11 | future_gamma = strategy('uint256', min_value=int(2.8e-4 * 1e18 / 9), max_value=int(2.8e-4 * 1e18 * 9)) 12 | future_A = strategy('uint256', min_value=90 * 2**2 * 10000 // 9, max_value=90 * 2**2 * 10000 * 9) 13 | 14 | def initialize(self, future_A, future_gamma): 15 | self.swap.ramp_A_gamma(future_A, future_gamma, self.chain.time() + 14*86400, {'from': self.accounts[0]}) 16 | 17 | def rule_exchange(self, exchange_amount_in, exchange_i, user): 18 | try: 19 | super()._rule_exchange(exchange_amount_in, exchange_i, user, False) 20 | except Exception: 21 | if exchange_amount_in > 10**9: 22 | # Small swaps can fail at ramps 23 | raise 24 | 25 | def rule_remove_liquidity_one_coin(self, token_amount, exchange_i, user): 26 | super().rule_remove_liquidity_one_coin(token_amount, exchange_i, user, False) 27 | 28 | def invariant_virtual_price(self): 29 | # Invariant is not conserved here 30 | pass 31 | 32 | 33 | def test_ramp(crypto_swap, token, chain, accounts, coins, state_machine): 34 | from hypothesis._settings import HealthCheck 35 | 36 | state_machine(RampTest, chain, accounts, coins, crypto_swap, token, 37 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': MAX_COUNT, 'suppress_health_check': HealthCheck.all()}) 38 | -------------------------------------------------------------------------------- /tests/tricrypto/forked/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie_tokens import MintableForkToken 3 | 4 | 5 | COINS = [ 6 | "0xdAC17F958D2ee523a2206206994597C13D831ec7", # usdt 7 | "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", # wbtc 8 | "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # weth 9 | ] 10 | SWAP = "0xD51a44d3FaE010294C616388b506AcdA1bfAAE46" 11 | TOKEN = "0xc4AD29ba4B3c580e6D59105FFf484999997675Ff" 12 | 13 | 14 | @pytest.fixture(scope="session") 15 | def alice(accounts): 16 | return accounts[0] 17 | 18 | 19 | @pytest.fixture(scope="session") 20 | def bob(accounts): 21 | return accounts[1] 22 | 23 | 24 | @pytest.fixture(scope="session") 25 | def charlie(accounts): 26 | return accounts[2] 27 | 28 | 29 | @pytest.fixture(scope="module") 30 | def coins(): 31 | return [MintableForkToken(addr) for addr in COINS] 32 | 33 | 34 | @pytest.fixture(scope="module") 35 | def decimals(): 36 | return [6, 8, 18] 37 | 38 | 39 | @pytest.fixture(scope="module") 40 | def crypto_swap(CurveCryptoSwap): 41 | return CurveCryptoSwap.at(SWAP) 42 | 43 | 44 | @pytest.fixture(scope="module") 45 | def crypto_zap(alice, DepositZap): 46 | return DepositZap.deploy( 47 | SWAP, {"from": alice} 48 | ) 49 | 50 | 51 | @pytest.fixture(scope="module") 52 | def crypto_lp_token(CurveTokenV4): 53 | return CurveTokenV4.at(TOKEN) 54 | 55 | 56 | @pytest.fixture(scope="module", autouse=True) 57 | def pre_mining(alice, crypto_zap, coins, decimals): 58 | """Mint a bunch of test tokens""" 59 | for coin, decimal in zip(coins, decimals): 60 | coin._mint_for_testing(alice, 100_000 * 10 ** decimal) 61 | coin.approve(crypto_zap, 2 ** 256 - 1, {"from": alice}) 62 | -------------------------------------------------------------------------------- /scripts/deploy_mainnet_cvx_pool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | network, 5 | CurveTokenV4, 6 | CurveCryptoSwap2ETH, 7 | ) 8 | 9 | # Addresses are taken for Polygon 10 | COINS = [ 11 | "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH 12 | "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B" # CVX 13 | ] 14 | FEE_RECEIVER = "0xeCb456EA5365865EbAb8a2661B0c503410e9B347" 15 | 16 | 17 | def main(): 18 | accounts.load('babe') 19 | 20 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=convex-finance&vs_currencies=eth").json() 21 | INITIAL_PRICE = int(p['convex-finance']['eth'] * 1e18) 22 | txparams = {"from": accounts[0]} 23 | if network.show_active() == 'mainnet': 24 | txparams.update({'required_confs': 5, 'priority_fee': '2 gwei'}) 25 | print('CVX price:', INITIAL_PRICE / 1e18, 'ETH') 26 | 27 | token = CurveTokenV4.deploy("Curve CVX-ETH", "crvCVXETH", txparams) 28 | 29 | swap = CurveCryptoSwap2ETH.deploy( 30 | accounts[0], 31 | FEE_RECEIVER, 32 | 10 * 2**2 * 10000, # A 33 | int(1.45e-4 * 1e18), # gamma 34 | int(2.6e-3 * 1e10), # mid_fee 35 | int(4.5e-3 * 1e10), # out_fee 36 | 2 * 10**12, # allowed_extra_profit - same as tricrypto2 37 | int(2.3e-4 * 1e18), # fee_gamma 38 | int(1.46e-4 * 1e18), # adjustment_step 39 | 5 * 10**9, # admin_fee 40 | 600, # ma_half_time 41 | INITIAL_PRICE, # price 42 | token, 43 | COINS, 44 | txparams 45 | ) 46 | token.set_minter(swap, txparams) 47 | 48 | print("Swap address:", swap.address) 49 | print("Token address:", token.address) 50 | 51 | return swap, token 52 | -------------------------------------------------------------------------------- /scripts/deploy_mainnet_spell_pool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | network, 5 | CurveTokenV5, 6 | CurveCryptoSwap2ETH, 7 | ) 8 | 9 | # Addresses are taken for Polygon 10 | COINS = [ 11 | "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH 12 | "0x090185f2135308BaD17527004364eBcC2D37e5F6" # SPELL 13 | ] 14 | FEE_RECEIVER = "0xeCb456EA5365865EbAb8a2661B0c503410e9B347" 15 | 16 | 17 | def main(): 18 | accounts.load('babe') 19 | 20 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=spell-token&vs_currencies=eth").json() 21 | INITIAL_PRICE = int(p['spell-token']['eth'] * 1e18) 22 | txparams = {"from": accounts[0]} 23 | if network.show_active() == 'mainnet': 24 | txparams.update({'required_confs': 5, 'priority_fee': '2 gwei'}) 25 | print('SPELL price:', INITIAL_PRICE / 1e18, 'ETH') 26 | 27 | token = CurveTokenV5.deploy("Curve SPELL-ETH", "crvSPELLETH", txparams) 28 | 29 | swap = CurveCryptoSwap2ETH.deploy( 30 | accounts[0], 31 | FEE_RECEIVER, 32 | 10 * 2**2 * 10000, # A 33 | int(1.45e-4 * 1e18), # gamma 34 | int(2.6e-3 * 1e10), # mid_fee 35 | int(4.5e-3 * 1e10), # out_fee 36 | 2 * 10**12, # allowed_extra_profit - same as tricrypto2 37 | int(2.3e-4 * 1e18), # fee_gamma 38 | int(1.46e-4 * 1e18), # adjustment_step 39 | 5 * 10**9, # admin_fee 40 | 600, # ma_half_time 41 | INITIAL_PRICE, # price 42 | token, 43 | COINS, 44 | txparams 45 | ) 46 | token.set_minter(swap, txparams) 47 | 48 | print("Swap address:", swap.address) 49 | print("Token address:", token.address) 50 | 51 | return swap, token 52 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_aave/test_zap_add_liquidity_a.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | 4 | 5 | INITIAL_AMOUNTS = [24000 * 10**6, 10000 * 10**18, 10000 * 10**6, 10000 * 10**6] 6 | 7 | 8 | @pytest.mark.parametrize("idx", range(4)) 9 | def test_add_single_coins(alice, crypto_zap, idx, token): 10 | amounts = [0] * 4 11 | amounts[idx] = INITIAL_AMOUNTS[idx] 12 | 13 | calc_amount = crypto_zap.calc_token_amount(amounts) 14 | amount = token.balanceOf(alice) 15 | crypto_zap.add_liquidity(amounts, 0, {"from": alice}) 16 | amount = token.balanceOf(alice) - amount 17 | 18 | assert amount > 0 19 | assert abs(amount - calc_amount) / amount < 0.01 20 | 21 | 22 | def test_add_multiple_coins(alice, crypto_zap, token): 23 | calc_amount = crypto_zap.calc_token_amount(INITIAL_AMOUNTS) 24 | crypto_zap.add_liquidity( 25 | INITIAL_AMOUNTS, 0, {"from": alice} 26 | ) 27 | amount = token.balanceOf(alice) 28 | 29 | assert amount > 0 30 | assert abs(amount - calc_amount) / amount < 0.01 31 | 32 | 33 | def test_add_no_coins(alice, crypto_zap): 34 | # No coins added, revert in crypto_swap 35 | with brownie.reverts(): 36 | crypto_zap.add_liquidity([0] * 4, 0, {"from": alice}) 37 | 38 | 39 | @pytest.mark.parametrize("idx", range(4)) 40 | def test_alternate_receiver(alice, bob, crypto_zap, idx, token): 41 | amounts = [0] * 4 42 | amounts[idx] = INITIAL_AMOUNTS[idx] 43 | crypto_zap.add_liquidity(amounts, 0, bob, {"from": alice}) 44 | assert token.balanceOf(bob) > 0 45 | 46 | 47 | def test_min_amount_revert(alice, crypto_zap): 48 | with brownie.reverts(): 49 | crypto_zap.add_liquidity( 50 | INITIAL_AMOUNTS, 2 ** 256 - 1, {"from": alice} 51 | ) 52 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_mainnet/test_zap_add_liquidity.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | 4 | 5 | INITIAL_AMOUNTS = [24000 * 10**6, 10000 * 10**18, 10000 * 10**6, 10000 * 10**6] 6 | 7 | 8 | @pytest.mark.parametrize("idx", range(4)) 9 | def test_add_single_coins(alice, crypto_zap, idx, token): 10 | amounts = [0] * 4 11 | amounts[idx] = INITIAL_AMOUNTS[idx] 12 | 13 | calc_amount = crypto_zap.calc_token_amount(amounts) 14 | amount = token.balanceOf(alice) 15 | crypto_zap.add_liquidity(amounts, 0, {"from": alice}) 16 | amount = token.balanceOf(alice) - amount 17 | 18 | assert amount > 0 19 | assert abs(amount - calc_amount) / amount < 0.01 20 | 21 | 22 | def test_add_multiple_coins(alice, crypto_zap, token): 23 | calc_amount = crypto_zap.calc_token_amount(INITIAL_AMOUNTS) 24 | crypto_zap.add_liquidity( 25 | INITIAL_AMOUNTS, 0, {"from": alice} 26 | ) 27 | amount = token.balanceOf(alice) 28 | 29 | assert amount > 0 30 | assert abs(amount - calc_amount) / amount < 0.01 31 | 32 | 33 | def test_add_no_coins(alice, crypto_zap): 34 | # No coins added, revert in crypto_swap 35 | with brownie.reverts(): 36 | crypto_zap.add_liquidity([0] * 4, 0, {"from": alice}) 37 | 38 | 39 | @pytest.mark.parametrize("idx", range(4)) 40 | def test_alternate_receiver(alice, bob, crypto_zap, idx, token): 41 | amounts = [0] * 4 42 | amounts[idx] = INITIAL_AMOUNTS[idx] 43 | crypto_zap.add_liquidity(amounts, 0, bob, {"from": alice}) 44 | assert token.balanceOf(bob) > 0 45 | 46 | 47 | def test_min_amount_revert(alice, crypto_zap): 48 | with brownie.reverts(): 49 | crypto_zap.add_liquidity( 50 | INITIAL_AMOUNTS, 2 ** 256 - 1, {"from": alice} 51 | ) 52 | -------------------------------------------------------------------------------- /tests/twocrypto/test_exchange.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | from brownie.test import given, strategy 3 | from hypothesis import settings # noqa 4 | from .conftest import INITIAL_PRICES 5 | 6 | MAX_SAMPLES = 50 7 | 8 | 9 | @given( 10 | amount=strategy('uint256', min_value=10**6, max_value=2 * 10**6 * 10**18), # Can be more than we have 11 | i=strategy('uint8', min_value=0, max_value=2), 12 | j=strategy('uint8', min_value=0, max_value=2)) 13 | @settings(max_examples=MAX_SAMPLES) 14 | def test_exchange(crypto_swap_with_deposit, token, coins, accounts, amount, i, j): 15 | user = accounts[1] 16 | 17 | if i == j or i > 1 or j > 1: 18 | with brownie.reverts(): 19 | crypto_swap_with_deposit.get_dy(i, j, 10**6) 20 | with brownie.reverts(): 21 | crypto_swap_with_deposit.exchange(i, j, 10**6, 0, {'from': user}) 22 | 23 | else: 24 | prices = [10**18] + INITIAL_PRICES 25 | amount = amount * 10**18 // prices[i] 26 | coins[i]._mint_for_testing(user, amount) 27 | 28 | calculated = crypto_swap_with_deposit.get_dy(i, j, amount) 29 | measured_i = coins[i].balanceOf(user) 30 | measured_j = coins[j].balanceOf(user) 31 | d_balance_i = crypto_swap_with_deposit.balances(i) 32 | d_balance_j = crypto_swap_with_deposit.balances(j) 33 | 34 | crypto_swap_with_deposit.exchange(i, j, amount, int(0.999 * calculated), {'from': user}) 35 | 36 | measured_i -= coins[i].balanceOf(user) 37 | measured_j = coins[j].balanceOf(user) - measured_j 38 | d_balance_i = crypto_swap_with_deposit.balances(i) - d_balance_i 39 | d_balance_j = crypto_swap_with_deposit.balances(j) - d_balance_j 40 | 41 | assert amount == measured_i 42 | assert calculated == measured_j 43 | 44 | assert d_balance_i == amount 45 | assert -d_balance_j == measured_j 46 | -------------------------------------------------------------------------------- /contracts/testing/CallbackTestZap.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.1 2 | from vyper.interfaces import ERC20 3 | 4 | interface Swap: 5 | def exchange_extended(i: uint256, j: uint256, dx: uint256, min_dy: uint256, 6 | sender: address, receiver: address, cb: Bytes[4]) -> uint256: nonpayable 7 | 8 | input_amount: public(uint256) 9 | output_amount: public(uint256) 10 | POOL: immutable(address) 11 | 12 | 13 | @external 14 | def __init__(_pool: address): 15 | POOL = _pool 16 | 17 | 18 | @external 19 | def good_callback(sender: address, receiver: address, coin: address, dx: uint256, dy: uint256): 20 | assert msg.sender == POOL 21 | ERC20(coin).transferFrom(sender, POOL, dx) 22 | 23 | # Debug info only, not needed in practice 24 | self.input_amount = dx 25 | self.output_amount = dy 26 | 27 | 28 | @external 29 | def evil_callback(sender: address, receiver: address, coin: address, dx: uint256, dy: uint256): 30 | assert msg.sender == POOL 31 | # Transfer the saved imput amount, not dx, to fool the pool 32 | ERC20(coin).transferFrom(sender, POOL, self.input_amount) 33 | 34 | # Debug info only, not needed in practice 35 | self.input_amount = dx 36 | self.output_amount = dy 37 | 38 | 39 | @external 40 | def set_evil_input_amount(x: uint256): 41 | self.input_amount = x 42 | 43 | 44 | @external 45 | def good_exchange(i: uint256, j: uint256, dx: uint256, min_dy: uint256) -> uint256: 46 | return Swap(POOL).exchange_extended( 47 | i, j, dx, min_dy, msg.sender, msg.sender, 48 | method_id("good_callback(address,address,address,uint256,uint256)")) 49 | 50 | 51 | @external 52 | def evil_exchange(i: uint256, j: uint256, dx: uint256, min_dy: uint256) -> uint256: 53 | return Swap(POOL).exchange_extended( 54 | i, j, dx, min_dy, msg.sender, msg.sender, 55 | method_id("evil_callback(address,address,address,uint256,uint256)")) 56 | -------------------------------------------------------------------------------- /scripts/deploy_mainnet_t_pool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | network, 5 | CurveTokenV5, 6 | CurveCryptoSwap2ETH, 7 | ) 8 | 9 | # Addresses are taken for Polygon 10 | COINS = [ 11 | "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH 12 | "0xCdF7028ceAB81fA0C6971208e83fa7872994beE5" # T 13 | ] 14 | FEE_RECEIVER = "0xeCb456EA5365865EbAb8a2661B0c503410e9B347" 15 | 16 | 17 | def main(): 18 | accounts.load('babe') 19 | 20 | p_nu = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=nucypher&vs_currencies=eth").json() 21 | p_nu = p_nu['nucypher']['eth'] * 1e18 22 | p_keep = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=keep-network&vs_currencies=eth").json() 23 | p_keep = p_keep['keep-network']['eth'] * 1e18 24 | INITIAL_PRICE = int((p_nu/3.26 * p_keep/4.78) ** 0.5) # Geometric mean 25 | txparams = {"from": accounts[0]} 26 | if network.show_active() == 'mainnet': 27 | txparams.update({'required_confs': 5, 'priority_fee': '2 gwei'}) 28 | print('T price:', INITIAL_PRICE / 1e18, 'ETH') 29 | 30 | token = CurveTokenV5.deploy("Curve T-ETH", "crvTETH", txparams) 31 | 32 | swap = CurveCryptoSwap2ETH.deploy( 33 | accounts[0], 34 | FEE_RECEIVER, 35 | 10 * 2**2 * 10000, # A 36 | int(1.45e-4 * 1e18), # gamma 37 | int(2.6e-3 * 1e10), # mid_fee 38 | int(4.5e-3 * 1e10), # out_fee 39 | 2 * 10**12, # allowed_extra_profit - same as tricrypto2 40 | int(2.3e-4 * 1e18), # fee_gamma 41 | int(1.46e-4 * 1e18), # adjustment_step 42 | 5 * 10**9, # admin_fee 43 | 600, # ma_half_time 44 | INITIAL_PRICE, # price 45 | token, 46 | COINS, 47 | txparams 48 | ) 49 | token.set_minter(swap, txparams) 50 | 51 | print("Swap address:", swap.address) 52 | print("Token address:", token.address) 53 | 54 | return swap, token 55 | -------------------------------------------------------------------------------- /contracts/testing/ERC20Mock.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.2.0 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 | -------------------------------------------------------------------------------- /deployment-logs/17-May-plain-mainnet.txt: -------------------------------------------------------------------------------- 1 | Transaction sent: 0xaa314a286afcdd03d23cda1fea96ed51504caec2a6376c4b272c1a9cf75a4c85 2 | Gas price: 60.0 gwei Gas limit: 1922346 Nonce: 302 3 | Transaction sent: 0x1f0167a084974baa352b8be154af496daeea67ce87ee657be236ac3e80b79964 4 | Gas price: 67.5 gwei Gas limit: 1922346 Nonce: 302 5 | Transaction sent: 0x2f3820a8fb94dba9c020feeb233abf757da7e9e76cc9b0b0b07b447b1095ab97 6 | Gas price: 77.06250082 gwei Gas limit: 1922346 Nonce: 302 7 | Transaction sent: 0xad12dd6bcea21d270dc36aabf84dc99cfed81f31a9fb3169b685b8987c495d95 8 | Gas price: 60.0 gwei Gas limit: 971235 Nonce: 303 9 | Transaction sent: 0x347263ac2d9ec6bd01845a7ef4bd49bd2274927dc9d8f267cb293a45046d67df 10 | Gas price: 67.5 gwei Gas limit: 971235 Nonce: 303 11 | Transaction sent: 0xba871a6fe27c6da0269eb20f950bd6c0a9601ddc280a15adc73227de226ddc4b 12 | Gas price: 60.0 gwei Gas limit: 863293 Nonce: 304 13 | Transaction sent: 0x952c0f6a23fd949556c1cceb13963bc09bc0ad2fa68d1712a27f5c0b7037fddd 14 | Gas price: 67.0 gwei Gas limit: 863293 Nonce: 304 15 | Transaction sent: 0x87c74c50ce6041debb3fbdf6140c177387c5eaac68dd1cb45a517ff17963b6ec 16 | Gas price: 59.0 gwei Gas limit: 6111251 Nonce: 305 17 | Transaction sent: 0x52984f5137be9bbcb7b8943884061a2384523064d010b0f258ef019f67d05e69 18 | Gas price: 66.375 gwei Gas limit: 6111251 Nonce: 305 19 | Transaction sent: 0xe0e6bf6058003b17f5b011bc9a97e14914e9210b1e283eed04d44bf7831d9adb 20 | Gas price: 76.9 gwei Gas limit: 6111251 Nonce: 305 21 | Transaction sent: 0x80f566b28d36e5752a6de3377cfbbe6a9076f6263ca4d93ff9ab0d75d14bb757 22 | Gas price: 63.0 gwei Gas limit: 29500 Nonce: 306 23 | Transaction sent: 0xb26047ff8a8d673ba2b1c760349dec0a19295f0de5a7bb11654d9f3705b71007 24 | Gas price: 70.0 gwei Gas limit: 29500 Nonce: 306 25 | Deployed at: 26 | Swap: 0x5a0D404d2042b92eB7948ac5943128E1088fD24b 27 | Token: 0x31aA15da826DA3311788B208D31CaE34074D1Dc7 28 | (0x5a0D404d2042b92eB7948ac5943128E1088fD24b, 0x31aA15da826DA3311788B208D31CaE34074D1Dc7) 29 | -------------------------------------------------------------------------------- /tests/twocrypto/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 coins(ERC20Mock, accounts): 8 | yield [ERC20Mock.deploy(name, name, 18, {"from": accounts[0]}) 9 | for name in ['USD', 'EUR']] 10 | 11 | 12 | @pytest.fixture(scope="module", autouse=True) 13 | def token(CurveTokenV5, accounts): 14 | yield CurveTokenV5.deploy("Curve EUR-USD", "crvEURUSD", {"from": accounts[0]}) 15 | 16 | 17 | @pytest.fixture(scope="module", autouse=True) 18 | def crypto_swap(CurveCryptoSwap2, token, coins, accounts): 19 | swap = CurveCryptoSwap2.deploy( 20 | accounts[0], 21 | accounts[0], 22 | 90 * 2**2 * 10000, # A 23 | int(2.8e-4 * 1e18), # gamma 24 | int(5e-4 * 1e10), # mid_fee 25 | int(4e-3 * 1e10), # out_fee 26 | 10**10, # allowed_extra_profit 27 | int(0.012 * 1e18), # fee_gamma 28 | int(0.55e-5 * 1e18), # adjustment_step 29 | 0, # admin_fee 30 | 600, # ma_half_time 31 | INITIAL_PRICES[0], 32 | token, 33 | coins, 34 | {'from': accounts[0]}) 35 | token.set_minter(swap, {"from": accounts[0]}) 36 | 37 | return swap 38 | 39 | 40 | def _crypto_swap_with_deposit(crypto_swap, coins, accounts): 41 | user = accounts[1] 42 | quantities = [10**6 * 10**36 // p for p in [10**18] + INITIAL_PRICES] 43 | for coin, q in zip(coins, quantities): 44 | coin._mint_for_testing(user, q) 45 | coin.approve(crypto_swap, 2**256-1, {'from': user}) 46 | 47 | # Very first deposit 48 | crypto_swap.add_liquidity(quantities, 0, {'from': user}) 49 | 50 | return crypto_swap 51 | 52 | 53 | @pytest.fixture(scope="module") 54 | def crypto_swap_with_deposit(crypto_swap, coins, accounts): 55 | return _crypto_swap_with_deposit(crypto_swap, coins, accounts) 56 | 57 | 58 | @pytest.fixture(autouse=True) 59 | def isolation(fn_isolation): 60 | pass 61 | -------------------------------------------------------------------------------- /tests/twocrypto/test_callback.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | from brownie.test import given, strategy 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def zap(CallbackTestZap, crypto_swap_with_deposit, coins, accounts): 8 | user = accounts[0] 9 | zap = CallbackTestZap.deploy(crypto_swap_with_deposit, {'from': user}) 10 | coins[0].approve(zap, 2**256 - 1, {'from': user}) 11 | coins[1].approve(zap, 2**256 - 1, {'from': user}) 12 | return zap 13 | 14 | 15 | @given(i=strategy('uint8', max_value=1)) 16 | @given(amount=strategy('uint256', min_value=1000 * 10**6, max_value=100 * 10**18)) 17 | def test_good(crypto_swap_with_deposit, zap, coins, accounts, i, amount): 18 | user = accounts[0] 19 | 20 | dy = crypto_swap_with_deposit.get_dy(i, 1-i, amount) 21 | 22 | coins[i]._mint_for_testing(user, amount // 2) 23 | with brownie.reverts(): 24 | zap.good_exchange(i, 1-i, amount, 0, {'from': user}) 25 | 26 | coins[i]._mint_for_testing(user, amount - amount // 2) 27 | 28 | in_0 = coins[i].balanceOf(user) 29 | out_0 = coins[1-i].balanceOf(user) 30 | zap.good_exchange(i, 1-i, amount, 0, {'from': user}) 31 | in_1 = coins[i].balanceOf(user) 32 | out_1 = coins[1-i].balanceOf(user) 33 | 34 | assert in_0 - in_1 == amount 35 | assert out_1 - out_0 == dy 36 | assert zap.input_amount() == amount 37 | assert zap.output_amount() == dy 38 | 39 | 40 | @given(i=strategy('uint8', max_value=1)) 41 | @given(amount=strategy('uint256', min_value=1000 * 10**6, max_value=100 * 10**18)) 42 | def test_evil(crypto_swap_with_deposit, zap, coins, accounts, i, amount): 43 | user = accounts[0] 44 | 45 | coins[i]._mint_for_testing(user, amount * 2) 46 | zap.set_evil_input_amount(amount * 2) 47 | with brownie.reverts(): 48 | zap.evil_exchange(i, 1-i, amount, 0, {'from': user}) 49 | zap.set_evil_input_amount(amount // 2) 50 | with brownie.reverts(): 51 | zap.evil_exchange(i, 1-i, amount, 0, {'from': user}) 52 | 53 | zap.set_evil_input_amount(amount) 54 | zap.evil_exchange(i, 1-i, amount, 0, {'from': user}) 55 | -------------------------------------------------------------------------------- /scripts/deploy_mainnet_euroc_metapool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | CurveTokenV5, 5 | CurveCryptoSwap2, 6 | ZapTwo 7 | ) 8 | from brownie import interface 9 | import json 10 | 11 | COINS = [ 12 | "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c", # EUROC 13 | "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" # 3crv 14 | ] 15 | STABLESWAP = "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7" 16 | FEE_RECEIVER = "0xeCb456EA5365865EbAb8a2661B0c503410e9B347" 17 | 18 | 19 | def main(): 20 | accounts.load('babe') 21 | 22 | virtual_price = interface.StableSwap3Pool(STABLESWAP).get_virtual_price() 23 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=tether-eurt&vs_currencies=usd").json() 24 | INITIAL_PRICE = int(virtual_price / p['tether-eurt']['usd']) 25 | # txparams = {"from": accounts[0]} 26 | txparams = {"from": accounts[0], 'required_confs': 5, 'priority_fee': '2 gwei'} 27 | print('EUR price:', 1e18 / INITIAL_PRICE, '3crv') 28 | 29 | token = CurveTokenV5.deploy("Curve EUROC-3Crv", "EUROC3CRV", txparams) 30 | 31 | swap = CurveCryptoSwap2.deploy( 32 | accounts[0], 33 | FEE_RECEIVER, 34 | 5000 * 2**2 * 10000, # A 35 | int(1e-4 * 1e18), # gamma 36 | int(5e-4 * 1e10), # mid_fee 37 | int(45e-4 * 1e10), # out_fee 38 | 10**10, # allowed_extra_profit 39 | int(5e-3 * 1e18), # fee_gamma 40 | int(0.55e-5 * 1e18), # adjustment_step ? 41 | 5 * 10**9, # admin_fee 42 | 1200, # ma_half_time 43 | INITIAL_PRICE, # price 44 | token, 45 | COINS, 46 | txparams 47 | ) 48 | token.set_minter(swap, txparams) 49 | 50 | zap = ZapTwo.deploy(swap, STABLESWAP, txparams) 51 | 52 | print("Swap address:", swap.address) 53 | print("Token address:", token.address) 54 | print("Zap address:", zap.address) 55 | 56 | with open("swap.json", "w") as f: 57 | json.dump(swap.abi, f) 58 | 59 | with open("token.json", "w") as f: 60 | json.dump(token.abi, f) 61 | 62 | with open("zap.json", "w") as f: 63 | json.dump(zap.abi, f) 64 | 65 | return swap, token, zap 66 | -------------------------------------------------------------------------------- /deployment-logs/14-May-plain-mainnet.txt: -------------------------------------------------------------------------------- 1 | Transaction sent: 0x97e2631749ae9946800b02a3b5e9e742745d75b090395772e39e2d3108ecc80f 2 | Gas price: 112.0 gwei Gas limit: 1922346 Nonce: 294 3 | CurveCryptoMath3.constructor confirmed - Block: 12434071 Gas used: 1747588 (90.91%) 4 | CurveCryptoMath3 deployed at: 0xc54106A999bedE585C9574f2e4CB31df46CbDd83 5 | 6 | Transaction sent: 0xb9a8aba3b6a728c3457fb95a66fd2d491f4a8b7cddaf8a7a5460492180fff82d 7 | Gas price: 95.0 gwei Gas limit: 971235 Nonce: 295 8 | Transaction sent: 0xdf3a9e101d77d72e56bd07a1a0cdb509eb5402c643bdf0b97810861b0a0a0614 9 | Gas price: 106.875 gwei Gas limit: 971235 Nonce: 295 10 | Transaction sent: 0x764e2440f7840edf04cd4fb36dae362bbede567e50cf09250d4e55df11475471 11 | Gas price: 120.234375 gwei Gas limit: 971235 Nonce: 295 12 | Transaction sent: 0xcb10eff2708d6db2643ec8191b2c11e8809a4c53f92d66bf5ac26dfa89c2c577 13 | Gas price: 134.0 gwei Gas limit: 971235 Nonce: 295 14 | Transaction sent: 0xe099f03f664813056a48b5f94e8472cb602655445514297140d2f1c975fd5229 15 | Gas price: 101.0 gwei Gas limit: 863293 Nonce: 296 16 | Transaction sent: 0x890e6cad88f6bc13e2080ae1fa4a51a658c1ced3c3b557f287e1c20b68d63563 17 | Gas price: 113.625 gwei Gas limit: 863293 Nonce: 296 18 | Transaction sent: 0x01abab0d1bcde4bb6352eb10cafb2847605ed4766801e51839cc89dfcdadda8b 19 | Gas price: 127.828125 gwei Gas limit: 863293 Nonce: 296 20 | Transaction sent: 0x5d40773e0a1b04fd06db72602d59359c8888d359caf653ebe011b2b7632bd7c5 21 | Gas price: 115.0 gwei Gas limit: 6069836 Nonce: 297 22 | Transaction sent: 0xdd5214a64f869c2cc41a440c3b73b49ab1e0543846f4b688d7a24f727d8616c4 23 | Gas price: 136.0 gwei Gas limit: 6069836 Nonce: 297 24 | Transaction sent: 0x4d3d082036a009a942b94bb14e487f1f0c6011e4d9ddf508dc0a35ea78e14a5d 25 | Gas price: 115.0 gwei Gas limit: 29500 Nonce: 298 26 | Transaction sent: 0x751a075b474d3aa84b84d2fe6b0e06dfb1c136d4dd4c9c5db954e34c4c34f252 27 | Gas price: 134.1 gwei Gas limit: 29500 Nonce: 298 28 | Deployed at: 29 | Swap: 0x65a8b215F34Df5c30c64523D638eA698065FFa15 30 | Token: 0x0D89774935e3d88b91f0CEA0991F4FCD7d9f5470 31 | (0x65a8b215F34Df5c30c64523D638eA698065FFa15, 0x0D89774935e3d88b91f0CEA0991F4FCD7d9f5470) -------------------------------------------------------------------------------- /deployment-logs/2021-09-13. Arbitrum/zap.json: -------------------------------------------------------------------------------- 1 | [{"stateMutability": "payable", "type": "fallback"}, {"inputs": [{"name": "_pool", "type": "address"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"inputs": [{"name": "_amounts", "type": "uint256[3]"}, {"name": "_min_mint_amount", "type": "uint256"}], "name": "add_liquidity", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "payable", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[3]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "add_liquidity", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "payable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[3]"}], "name": "remove_liquidity", "outputs": [{"name": "", "type": "uint256[3]"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[3]"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity", "outputs": [{"name": "", "type": "uint256[3]"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "name": "remove_liquidity_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 1268, "inputs": [], "name": "pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1298, "inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1373, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] -------------------------------------------------------------------------------- /scripts/deploy_mainnet_xaut_metapool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | CurveTokenV5, 5 | CurveCryptoSwap2, 6 | ZapTwo 7 | ) 8 | from brownie import interface 9 | import json 10 | 11 | # Addresses are taken for Polygon 12 | COINS = [ 13 | "0x68749665FF8D2d112Fa859AA293F07A622782F38", # XAUT 14 | "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" # 3crv 15 | ] 16 | STABLESWAP = "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7" 17 | FEE_RECEIVER = "0xeCb456EA5365865EbAb8a2661B0c503410e9B347" 18 | 19 | 20 | def main(): 21 | accounts.load('babe') 22 | 23 | virtual_price = interface.StableSwap3Pool(STABLESWAP).get_virtual_price() 24 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=tether-gold&vs_currencies=usd").json() 25 | INITIAL_PRICE = int(virtual_price / p['tether-gold']['usd']) 26 | # txparams = {"from": accounts[0]} 27 | txparams = {"from": accounts[0], 'required_confs': 5, 'priority_fee': '2 gwei'} 28 | print('Gold price:', 1e18 / INITIAL_PRICE, '3crv') 29 | 30 | token = CurveTokenV5.deploy("Curve XAUT-3Crv", "crvXAUTUSD", txparams) 31 | 32 | swap = CurveCryptoSwap2.deploy( 33 | accounts[0], 34 | FEE_RECEIVER, 35 | 5000 * 2**2 * 10000, # A 36 | int(1e-4 * 1e18), # gamma 37 | int(5e-4 * 1e10), # mid_fee 38 | int(45e-4 * 1e10), # out_fee 39 | 10**10, # allowed_extra_profit 40 | int(5e-3 * 1e18), # fee_gamma 41 | int(0.55e-5 * 1e18), # adjustment_step ? 42 | 5 * 10**9, # admin_fee 43 | 600, # ma_half_time 44 | INITIAL_PRICE, # price 45 | token, 46 | COINS, 47 | txparams 48 | ) 49 | token.set_minter(swap, txparams) 50 | 51 | zap = ZapTwo.deploy(swap, STABLESWAP, txparams) 52 | 53 | print("Swap address:", swap.address) 54 | print("Token address:", token.address) 55 | print("Zap address:", zap.address) 56 | 57 | with open("swap.json", "w") as f: 58 | json.dump(swap.abi, f) 59 | 60 | with open("token.json", "w") as f: 61 | json.dump(token.abi, f) 62 | 63 | with open("zap.json", "w") as f: 64 | json.dump(zap.abi, f) 65 | 66 | return swap, token, zap 67 | -------------------------------------------------------------------------------- /tests/tricrypto/test_stateful_multiprecision.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from .test_stateful import NumbaGoUp 3 | from .conftest import _compiled_swap, _crypto_views, _crypto_swap, _crypto_swap_with_deposit 4 | 5 | COINS = [ 6 | ('USDC', 6), 7 | ('WBTC', 8), 8 | ('WETH', 18)] 9 | 10 | INITIAL_PRICES = [47500 * 10**18, 1500 * 10**18] 11 | 12 | MAX_SAMPLES = 20 13 | MAX_COUNT = 20 14 | 15 | 16 | # Fixtures 17 | @pytest.fixture(scope="module", autouse=True) 18 | def coins_mp(ERC20Mock, accounts): 19 | yield [ERC20Mock.deploy(name, name, decimals, {"from": accounts[0]}) 20 | for name, decimals in COINS] 21 | 22 | 23 | @pytest.fixture(scope="module", autouse=True) 24 | def token_mp(CurveTokenV4, accounts): 25 | yield CurveTokenV4.deploy("Curve USD-BTC-ETH", "crvUSDBTCETH", {"from": accounts[0]}) 26 | 27 | 28 | @pytest.fixture(scope="module", autouse=True) 29 | def crypto_views_mp(CurveCryptoViews3, crypto_math, accounts, coins_mp): 30 | yield _crypto_views(CurveCryptoViews3, crypto_math, accounts, coins_mp) 31 | 32 | 33 | @pytest.fixture(scope="module", autouse=True) 34 | def compiled_swap_mp(crypto_math, token_mp, crypto_views_mp, coins_mp): 35 | return _compiled_swap(crypto_math, token_mp, crypto_views_mp, coins_mp) 36 | 37 | 38 | @pytest.fixture(scope="module", autouse=True) 39 | def crypto_swap_mp(compiled_swap_mp, token_mp, accounts): 40 | return _crypto_swap(compiled_swap_mp, token_mp, accounts) 41 | 42 | 43 | @pytest.fixture(scope="module") 44 | def crypto_swap_with_deposit_mp(crypto_swap_mp, coins_mp, accounts): 45 | return _crypto_swap_with_deposit(crypto_swap_mp, coins_mp, accounts) 46 | 47 | 48 | class Multiprecision(NumbaGoUp): 49 | def rule_exchange(self, exchange_amount_in, exchange_i, exchange_j, user): 50 | exchange_amount_in = exchange_amount_in // 10**(18-self.decimals[exchange_i]) 51 | super().rule_exchange(exchange_amount_in, exchange_i, exchange_j, user) 52 | 53 | 54 | def test_multiprecision(crypto_swap_mp, token_mp, chain, accounts, coins_mp, state_machine): 55 | from hypothesis._settings import HealthCheck 56 | 57 | state_machine(Multiprecision, chain, accounts, coins_mp, crypto_swap_mp, token_mp, 58 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': MAX_COUNT, 'suppress_health_check': HealthCheck.all()}) 59 | -------------------------------------------------------------------------------- /contracts/testing/WETH.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.2.0 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 | @payable 25 | @external 26 | def __init__(): 27 | self.name = "Wrapped Ether" 28 | self.symbol = "WETH" 29 | self.decimals = 18 30 | 31 | 32 | @external 33 | @view 34 | def totalSupply() -> uint256: 35 | return self.total_supply 36 | 37 | 38 | @external 39 | @view 40 | def allowance(_owner : address, _spender : address) -> uint256: 41 | return self.allowances[_owner][_spender] 42 | 43 | 44 | @external 45 | def transfer(_to : address, _value : uint256) -> bool: 46 | self.balanceOf[msg.sender] -= _value 47 | self.balanceOf[_to] += _value 48 | log Transfer(msg.sender, _to, _value) 49 | return True 50 | 51 | 52 | @external 53 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 54 | self.balanceOf[_from] -= _value 55 | self.balanceOf[_to] += _value 56 | self.allowances[_from][msg.sender] -= _value 57 | log Transfer(_from, _to, _value) 58 | return True 59 | 60 | 61 | @external 62 | def approve(_spender : address, _value : uint256) -> bool: 63 | self.allowances[msg.sender][_spender] = _value 64 | log Approval(msg.sender, _spender, _value) 65 | return True 66 | 67 | 68 | @external 69 | def _mint_for_testing(_target: address, _value: uint256) -> bool: 70 | self.total_supply += _value 71 | self.balanceOf[_target] += _value 72 | log Transfer(ZERO_ADDRESS, _target, _value) 73 | 74 | return True 75 | 76 | 77 | @payable 78 | @external 79 | def deposit(): 80 | self.balanceOf[msg.sender] += msg.value 81 | 82 | 83 | @payable 84 | @external 85 | def __default__(): 86 | self.balanceOf[msg.sender] += msg.value 87 | 88 | 89 | @external 90 | def withdraw(_amount: uint256): 91 | self.balanceOf[msg.sender] -= _amount 92 | raw_call(msg.sender, b"", value=_amount) 93 | -------------------------------------------------------------------------------- /scripts/deploy_poly_eurs_metapool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | network, 5 | CurveTokenV4, 6 | CurveCryptoSwap2, 7 | ZapTwoAave, 8 | ) 9 | from brownie import interface 10 | import json 11 | 12 | # Addresses are taken for Polygon 13 | COINS = [ 14 | "0xE111178A87A3BFf0c8d18DECBa5798827539Ae99", # EURS 15 | "0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171" # 3Crv 16 | ] 17 | SWAP = "0x445FE580eF8d70FF569aB36e80c647af338db351" 18 | FEE_RECEIVER = "0x0000000000000000000000000000000000000000" 19 | 20 | 21 | def main(): 22 | accounts.load('babe') 23 | virtual_price = interface.StableSwap2Pool(SWAP).get_virtual_price() 24 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=stasis-eurs&vs_currencies=usd").json() 25 | INITIAL_PRICE = int(virtual_price / p['stasis-eurs']['usd']) 26 | if network.show_active() == 'polygon-main': 27 | txparams = {"from": accounts[-1], 'required_confs': 20, 'gasPrice': '30 gwei'} 28 | else: 29 | txparams = {"from": accounts[-1]} 30 | print('Euro price:', 1e18 / INITIAL_PRICE, '3crv') 31 | 32 | token = CurveTokenV4.deploy("Curve EURS-3Crv", "crvEURSUSD", txparams) 33 | coins = [interface.ERC20(addr) for addr in COINS] 34 | 35 | swap = CurveCryptoSwap2.deploy( 36 | accounts[0], 37 | FEE_RECEIVER, 38 | 5000 * 2**2 * 10000, # A 39 | int(1e-4 * 1e18), # gamma 40 | int(5e-4 * 1e10), # mid_fee 41 | int(45e-4 * 1e10), # out_fee 42 | 10**10, # allowed_extra_profit 43 | int(5e-3 * 1e18), # fee_gamma 44 | int(0.55e-5 * 1e18), # adjustment_step ? 45 | 5 * 10**9, # admin_fee 46 | 600, # ma_half_time 47 | INITIAL_PRICE, # price 48 | token, 49 | coins, 50 | txparams 51 | ) 52 | token.set_minter(swap, txparams) 53 | 54 | zap = ZapTwoAave.deploy(swap.address, SWAP, txparams) 55 | 56 | print("Swap address:", swap.address) 57 | print("Token address:", token.address) 58 | print("Zap address:", zap.address) 59 | 60 | with open("swap.json", "w") as f: 61 | json.dump(swap.abi, f) 62 | 63 | with open("token.json", "w") as f: 64 | json.dump(token.abi, f) 65 | 66 | with open("zap.json", "w") as f: 67 | json.dump(zap.abi, f) 68 | 69 | return swap, token, zap 70 | -------------------------------------------------------------------------------- /tests/tricrypto/forked/test_zap_add_liquidity.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | 4 | 5 | INITIAL_AMOUNTS = [40_000 * 10 ** 6, 1.1 * 10 ** 8, 15 * 10 ** 18] 6 | 7 | 8 | @pytest.mark.parametrize("idx", range(3)) 9 | def test_add_single_coins(alice, crypto_zap, idx, crypto_lp_token): 10 | amounts = [0, 0, 0] 11 | amounts[idx] = INITIAL_AMOUNTS[idx] 12 | if idx == 2: 13 | crypto_zap.add_liquidity( 14 | amounts, 0, {"from": alice, "value": INITIAL_AMOUNTS[idx]} 15 | ) 16 | else: 17 | crypto_zap.add_liquidity(amounts, 0, {"from": alice}) 18 | assert crypto_lp_token.balanceOf(alice) > 0 19 | 20 | 21 | def test_add_multiple_coins(alice, crypto_zap, crypto_lp_token): 22 | crypto_zap.add_liquidity( 23 | INITIAL_AMOUNTS, 0, {"from": alice, "value": INITIAL_AMOUNTS[2]} 24 | ) 25 | 26 | assert crypto_lp_token.balanceOf(alice) > 0 27 | 28 | 29 | def test_add_no_coins(alice, crypto_zap): 30 | # No coins added, revert in crypto_swap 31 | with brownie.reverts(): 32 | crypto_zap.add_liquidity([0] * 3, 0, {"from": alice, "value": 0}) 33 | 34 | 35 | @pytest.mark.parametrize("idx", range(4)) 36 | def test_alternate_receiver(alice, bob, crypto_zap, idx, crypto_lp_token): 37 | if idx == 3: 38 | crypto_zap.add_liquidity( 39 | INITIAL_AMOUNTS, 0, bob, {"from": alice, "value": INITIAL_AMOUNTS[2]} 40 | ) 41 | assert crypto_lp_token.balanceOf(bob) > 0 42 | return 43 | 44 | amounts = [0, 0, 0] 45 | amounts[idx] = INITIAL_AMOUNTS[idx] 46 | if idx == 2: 47 | crypto_zap.add_liquidity( 48 | amounts, 0, bob, {"from": alice, "value": INITIAL_AMOUNTS[idx]} 49 | ) 50 | else: 51 | crypto_zap.add_liquidity(amounts, 0, bob, {"from": alice}) 52 | assert crypto_lp_token.balanceOf(bob) > 0 53 | 54 | 55 | @pytest.mark.parametrize("scale", [0.5, 0.75, 1.02, 1.25]) 56 | def test_invalid_eth_amount_specified_revert(alice, crypto_zap, scale): 57 | with brownie.reverts(): 58 | crypto_zap.add_liquidity( 59 | INITIAL_AMOUNTS, 0, {"from": alice, "value": INITIAL_AMOUNTS[2] * scale} 60 | ) 61 | 62 | 63 | def test_min_amount_revert(alice, crypto_zap): 64 | with brownie.reverts(): 65 | crypto_zap.add_liquidity( 66 | INITIAL_AMOUNTS, 2 ** 256 - 1, {"from": alice, "value": INITIAL_AMOUNTS[2]} 67 | ) 68 | 69 | -------------------------------------------------------------------------------- /tests/twocrypto/test_stateful_multiprecision.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from .test_stateful import NumbaGoUp 3 | from .conftest import _crypto_swap_with_deposit 4 | 5 | COINS = [ 6 | ('USDC', 6), 7 | ('EURX', 18)] 8 | 9 | INITIAL_PRICES = [int(1.2 * 10**18)] 10 | 11 | MAX_SAMPLES = 20 12 | MAX_COUNT = 20 13 | 14 | 15 | # Fixtures 16 | @pytest.fixture(scope="module", autouse=True) 17 | def coins_mp(ERC20Mock, accounts): 18 | yield [ERC20Mock.deploy(name, name, decimals, {"from": accounts[0]}) 19 | for name, decimals in COINS] 20 | 21 | 22 | @pytest.fixture(scope="module", autouse=True) 23 | def token_mp(CurveTokenV5, accounts): 24 | yield CurveTokenV5.deploy("Curve USD-EUR", "crvUSDEUR", {"from": accounts[0]}) 25 | 26 | 27 | @pytest.fixture(scope="module", autouse=True) 28 | def crypto_swap_mp(CurveCryptoSwap2, token_mp, coins_mp, accounts): 29 | swap = CurveCryptoSwap2.deploy( 30 | accounts[0], 31 | accounts[0], 32 | 90 * 2**2 * 10000, # A 33 | int(2.8e-4 * 1e18), # gamma 34 | int(8.5e-5 * 1e10), # mid_fee 35 | int(1.3e-3 * 1e10), # out_fee 36 | 10**10, # allowed_extra_profit 37 | int(0.012 * 1e18), # fee_gamma 38 | int(0.55e-5 * 1e18), # adjustment_step 39 | 0, # admin_fee 40 | 600, # ma_half_time 41 | INITIAL_PRICES[0], 42 | token_mp, 43 | coins_mp, 44 | {'from': accounts[0]}) 45 | token_mp.set_minter(swap, {"from": accounts[0]}) 46 | return swap 47 | 48 | 49 | @pytest.fixture(scope="module") 50 | def crypto_swap_with_deposit_mp(crypto_swap_mp, coins_mp, accounts): 51 | return _crypto_swap_with_deposit(crypto_swap_mp, coins_mp, accounts) 52 | 53 | 54 | class Multiprecision(NumbaGoUp): 55 | def rule_exchange(self, exchange_amount_in, exchange_i, user): 56 | exchange_amount_in = exchange_amount_in // 10**(18-self.decimals[exchange_i]) 57 | super().rule_exchange(exchange_amount_in, exchange_i, user) 58 | 59 | 60 | def test_multiprecision(crypto_swap_mp, token_mp, chain, accounts, coins_mp, state_machine): 61 | from hypothesis._settings import HealthCheck 62 | 63 | state_machine(Multiprecision, chain, accounts, coins_mp, crypto_swap_mp, token_mp, 64 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': MAX_COUNT, 'suppress_health_check': HealthCheck.all()}) 65 | -------------------------------------------------------------------------------- /tests/twocrypto/test_stateful_ramp.py: -------------------------------------------------------------------------------- 1 | from brownie.test import strategy 2 | from .test_stateful import NumbaGoUp 3 | 4 | MAX_SAMPLES = 20 5 | MAX_COUNT = 100 6 | MAX_D = 10**12 * 10**18 # $1T is hopefully a reasonable cap for tests 7 | ALLOWED_DIFFERENCE = 0.001 8 | 9 | 10 | class RampTest(NumbaGoUp): 11 | check_out_amount = strategy('bool') 12 | exchange_amount_in = strategy('uint256', min_value=10**18, max_value=50000 * 10**18) 13 | token_amount = strategy('uint256', min_value=10**18, max_value=10**12 * 10**18) 14 | deposit_amounts = strategy('uint256[3]', min_value=10**18, max_value=10**9 * 10**18) 15 | 16 | def setup(self, user_id=0): 17 | super().setup(user_id) 18 | new_A = self.swap.A() * 2 19 | new_gamma = self.swap.gamma() * 2 20 | self.swap.ramp_A_gamma(new_A, new_gamma, self.chain.time() + 14*86400, {'from': self.accounts[0]}) 21 | 22 | def rule_deposit(self, deposit_amounts, user): 23 | deposit_amounts[1:] = [deposit_amounts[0], deposit_amounts[1] * 10**18 // self.swap.price_oracle()] 24 | super().rule_deposit(deposit_amounts, user) 25 | 26 | def rule_exchange(self, exchange_amount_in, exchange_i, user, check_out_amount): 27 | if check_out_amount: 28 | self.swap.claim_admin_fees() 29 | if exchange_i > 0: 30 | exchange_amount_in = exchange_amount_in * 10**18 // self.swap.price_oracle() 31 | if exchange_amount_in < 1000: 32 | return 33 | super()._rule_exchange(exchange_amount_in, exchange_i, user, 34 | ALLOWED_DIFFERENCE if check_out_amount else False) 35 | 36 | def rule_remove_liquidity_one_coin(self, token_amount, exchange_i, user, check_out_amount): 37 | if check_out_amount: 38 | self.swap.claim_admin_fees() 39 | super().rule_remove_liquidity_one_coin(token_amount, exchange_i, user, ALLOWED_DIFFERENCE) 40 | else: 41 | super().rule_remove_liquidity_one_coin(token_amount, exchange_i, user, False) 42 | 43 | def invariant_virtual_price(self): 44 | # Invariant is not conserved here 45 | pass 46 | 47 | 48 | def test_ramp(crypto_swap, token, chain, accounts, coins, state_machine): 49 | from hypothesis._settings import HealthCheck 50 | 51 | state_machine(RampTest, chain, accounts, coins, crypto_swap, token, 52 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': MAX_COUNT, 'suppress_health_check': HealthCheck.all()}) 53 | -------------------------------------------------------------------------------- /tests/tricrypto/test_stateful_ramp.py: -------------------------------------------------------------------------------- 1 | from brownie.test import strategy 2 | from .test_stateful import NumbaGoUp 3 | 4 | MAX_SAMPLES = 20 5 | MAX_COUNT = 100 6 | MAX_D = 10**12 * 10**18 # $1T is hopefully a reasonable cap for tests 7 | ALLOWED_DIFFERENCE = 0.001 8 | 9 | 10 | class RampTest(NumbaGoUp): 11 | check_out_amount = strategy('bool') 12 | exchange_amount_in = strategy('uint256', min_value=10**18, max_value=50000 * 10**18) # XXX 13 | token_amount = strategy('uint256', min_value=10**18, max_value=10**12 * 10**18) 14 | deposit_amounts = strategy('uint256[3]', min_value=10**18, max_value=10**9 * 10**18) 15 | 16 | def setup(self, user_id=0): 17 | super().setup(user_id) 18 | new_A = self.swap.A() * 2 19 | new_gamma = self.swap.gamma() * 2 20 | self.swap.ramp_A_gamma(new_A, new_gamma, self.chain.time() + 14*86400, {'from': self.accounts[0]}) 21 | 22 | def rule_deposit(self, deposit_amounts, user): 23 | deposit_amounts[1:] = [deposit_amounts[0]] + [deposit_amounts[i] * 10**18 // self.swap.price_oracle(i-1) for i in [1, 2]] 24 | super().rule_deposit(deposit_amounts, user) 25 | 26 | def rule_exchange(self, exchange_amount_in, exchange_i, exchange_j, user, check_out_amount): 27 | if check_out_amount: 28 | self.swap.claim_admin_fees() 29 | if exchange_i > 0: 30 | exchange_amount_in = exchange_amount_in * 10**18 // self.swap.price_oracle(exchange_i - 1) 31 | if exchange_amount_in < 1000: 32 | return 33 | super()._rule_exchange(exchange_amount_in, exchange_i, exchange_j, user, 34 | ALLOWED_DIFFERENCE if check_out_amount else False) 35 | 36 | def rule_remove_liquidity_one_coin(self, token_amount, exchange_i, user, check_out_amount): 37 | if check_out_amount: 38 | self.swap.claim_admin_fees() 39 | super().rule_remove_liquidity_one_coin(token_amount, exchange_i, user, ALLOWED_DIFFERENCE) 40 | else: 41 | super().rule_remove_liquidity_one_coin(token_amount, exchange_i, user, False) 42 | 43 | def invariant_virtual_price(self): 44 | # Invariant is not conserved here 45 | pass 46 | 47 | 48 | def test_ramp(crypto_swap, token, chain, accounts, coins, state_machine): 49 | from hypothesis._settings import HealthCheck 50 | 51 | state_machine(RampTest, chain, accounts, coins, crypto_swap, token, 52 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': MAX_COUNT, 'suppress_health_check': HealthCheck.all()}) 53 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_mainnet/test_zap_remove_liquidity.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | 4 | INITIAL_AMOUNTS = [24000 * 10**6, 10000 * 10**18, 10000 * 10**6, 10000 * 10**6] 5 | 6 | 7 | @pytest.fixture(scope="module") 8 | def lp_token_amount(alice, crypto_zap, token): 9 | crypto_zap.add_liquidity(INITIAL_AMOUNTS, 0, {"from": alice}) 10 | token.approve(crypto_zap, 2 ** 256 - 1, {"from": alice}) 11 | return token.balanceOf(alice) 12 | 13 | 14 | def test_remove_all_coins(alice, crypto_zap, underlying_coins, lp_token_amount, base_decimals): 15 | balances = [c.balanceOf(alice) for c in underlying_coins] 16 | 17 | crypto_zap.remove_liquidity(lp_token_amount, [0] * 4, {"from": alice}) 18 | 19 | assert underlying_coins[0].balanceOf(alice) >= balances[0] + 0.97 * INITIAL_AMOUNTS[0] 20 | 21 | usd = sum((underlying_coins[i].balanceOf(alice) - balances[i]) * 10**(18 - base_decimals[i - 1]) for i in range(1, 4)) 22 | assert usd >= 0.97 * sum(INITIAL_AMOUNTS[i] * 10**(18 - base_decimals[i - 1]) for i in range(1, 4)) 23 | 24 | 25 | def test_remove_all_coins_min_amount( 26 | alice, crypto_zap, underlying_coins, lp_token_amount, base_decimals 27 | ): 28 | balances = [c.balanceOf(alice) for c in underlying_coins] 29 | 30 | amounts = [int(amt * 0.5) for amt in INITIAL_AMOUNTS] 31 | crypto_zap.remove_liquidity(lp_token_amount, amounts, {"from": alice}) 32 | 33 | assert underlying_coins[0].balanceOf(alice) >= balances[0] + 0.97 * INITIAL_AMOUNTS[0] 34 | 35 | usd = sum((underlying_coins[i].balanceOf(alice) - balances[i]) * 10**(18 - base_decimals[i - 1]) for i in range(1, 4)) 36 | assert usd >= 0.97 * sum(INITIAL_AMOUNTS[i] * 10**(18 - base_decimals[i - 1]) for i in range(1, 4)) 37 | 38 | 39 | def test_alternate_receiver(alice, bob, crypto_zap, underlying_coins, lp_token_amount, decimals): 40 | crypto_zap.remove_liquidity(lp_token_amount, [0] * 4, bob, {"from": alice}) 41 | 42 | for coin in underlying_coins: 43 | assert coin.balanceOf(bob) >= 0 44 | 45 | 46 | @pytest.mark.parametrize("scale", [0.2, 0.4, 0.6, 0.8]) 47 | def test_remove_percentage_of_coins_min_amount_revert( 48 | alice, crypto_zap, lp_token_amount, scale 49 | ): 50 | with brownie.reverts(): 51 | crypto_zap.remove_liquidity( 52 | lp_token_amount * scale, INITIAL_AMOUNTS, {"from": alice} 53 | ) 54 | 55 | 56 | def test_remove_all_coins_min_amount_revert(alice, crypto_zap, lp_token_amount): 57 | with brownie.reverts(): 58 | crypto_zap.remove_liquidity( 59 | lp_token_amount, [2 ** 256 - 1] * 4, {"from": alice} 60 | ) 61 | -------------------------------------------------------------------------------- /tests/twocrypto/test_stateful_admin_fee.py: -------------------------------------------------------------------------------- 1 | from .stateful_base import StatefulBase 2 | from math import log 3 | from brownie.test import strategy 4 | 5 | MAX_SAMPLES = 20 6 | STEP_COUNT = 100 7 | NO_CHANGE = 2**256-1 8 | 9 | 10 | def approx(x1, x2, precision): 11 | return abs(log(x1 / x2)) <= precision 12 | 13 | 14 | class StatefulAdmin(StatefulBase): 15 | exchange_amount_in = strategy('uint256', min_value=10**17, max_value=10**5 * 10**18) 16 | 17 | def setup(self): 18 | super().setup(user_id=1) 19 | admin = self.accounts[0] 20 | self.swap.commit_new_parameters( 21 | NO_CHANGE, 22 | NO_CHANGE, 23 | 5 * 10**9, # admin fee 24 | NO_CHANGE, 25 | NO_CHANGE, 26 | NO_CHANGE, 27 | NO_CHANGE, 28 | {'from': admin}) 29 | self.chain.sleep(3 * 86400 + 1) 30 | self.swap.apply_new_parameters({'from': admin}) 31 | assert self.swap.admin_fee() == 5 * 10**9 32 | self.mid_fee = self.swap.mid_fee() 33 | self.out_fee = self.swap.out_fee() 34 | self.admin_fee = 5 * 10**9 35 | 36 | def rule_exchange(self, exchange_amount_in, exchange_i, user): 37 | admin_balance = self.token.balanceOf(self.accounts[0]) 38 | if exchange_i == 1: 39 | exchange_amount_in_converted = exchange_amount_in * 10**18 // self.swap.price_oracle() 40 | else: 41 | exchange_amount_in_converted = exchange_amount_in 42 | super().rule_exchange(exchange_amount_in_converted, exchange_i, user) 43 | admin_balance = self.token.balanceOf(self.accounts[0]) - admin_balance 44 | self.total_supply += admin_balance 45 | 46 | def rule_claim_admin_fees(self): 47 | balance = self.token.balanceOf(self.accounts[0]) 48 | 49 | self.swap.claim_admin_fees() 50 | admin_balance = self.token.balanceOf(self.accounts[0]) 51 | balance = admin_balance - balance 52 | self.total_supply += balance 53 | 54 | if balance > 0: 55 | self.xcp_profit = self.swap.xcp_profit() 56 | measured_profit = admin_balance / self.total_supply 57 | assert approx(measured_profit, log(self.xcp_profit / 1e18) / 2, 0.1) 58 | 59 | 60 | def test_admin(crypto_swap, token, chain, accounts, coins, state_machine): 61 | from hypothesis._settings import HealthCheck 62 | 63 | state_machine(StatefulAdmin, chain, accounts, coins, crypto_swap, token, 64 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': STEP_COUNT, 'suppress_health_check': HealthCheck.all()}) 65 | -------------------------------------------------------------------------------- /scripts/deploy_mainnet_eurs_pool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | CurveTokenV4, 5 | CurveCryptoSwap2, 6 | compile_source, 7 | ) 8 | from brownie import interface 9 | import json 10 | 11 | VYPER_VERSION = "0.3.0" # Forced version, use None when brownie supports the new version 12 | 13 | # Addresses are taken for Polygon 14 | COINS = [ 15 | "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC 16 | "0xdB25f211AB05b1c97D595516F45794528a807ad8" # EURS 17 | ] 18 | FEE_RECEIVER = "0xeCb456EA5365865EbAb8a2661B0c503410e9B347" 19 | 20 | 21 | def main(): 22 | accounts.load('babe') 23 | 24 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=stasis-eurs&vs_currencies=usd").json() 25 | INITIAL_PRICE = int(p['stasis-eurs']['usd'] * 1e18) 26 | txparams = {"from": accounts[0], 'required_confs': 5, 'priority_fee': '2 gwei'} 27 | print('Euro price:', INITIAL_PRICE / 1e18, 'USDC') 28 | 29 | token = CurveTokenV4.deploy("Curve EURS-USDC", "crvEURSUSDC", txparams) 30 | coins = [interface.ERC20(addr) for addr in COINS] 31 | 32 | source = CurveCryptoSwap2._build["source"] 33 | source = source.replace("0x0000000000000000000000000000000000000001", token.address) 34 | source = source.replace("0x0000000000000000000000000000000000000010", coins[0].address) 35 | source = source.replace("0x0000000000000000000000000000000000000011", coins[1].address) 36 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 37 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 38 | with open("CryptoSwap.vy", "w") as f: 39 | f.write(source) 40 | deployer = compile_source(source, vyper_version=VYPER_VERSION).Vyper 41 | 42 | swap = deployer.deploy( 43 | accounts[0], 44 | FEE_RECEIVER, 45 | 5000 * 2**2 * 10000, # A 46 | int(1e-4 * 1e18), # gamma 47 | int(5e-4 * 1e10), # mid_fee 48 | int(45e-4 * 1e10), # out_fee 49 | 10**10, # allowed_extra_profit 50 | int(5e-3 * 1e18), # fee_gamma 51 | int(0.55e-5 * 1e18), # adjustment_step ? 52 | 5 * 10**9, # admin_fee 53 | 600, # ma_half_time 54 | INITIAL_PRICE, # price 55 | txparams 56 | ) 57 | token.set_minter(swap, txparams) 58 | 59 | print("Swap address:", swap.address) 60 | print("Token address:", token.address) 61 | 62 | with open("swap.json", "w") as f: 63 | json.dump(swap.abi, f) 64 | 65 | with open("token.json", "w") as f: 66 | json.dump(token.abi, f) 67 | 68 | return swap, token 69 | -------------------------------------------------------------------------------- /tests/tricrypto/forked/test_zap_remove_liquidity_one_coin.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | import itertools as it 4 | 5 | 6 | INITIAL_AMOUNTS = [40_000 * 10 ** 6, 1.1 * 10 ** 8, 15 * 10 ** 18] 7 | 8 | 9 | @pytest.fixture(scope="module") 10 | def lp_token_amount(alice, crypto_zap, crypto_lp_token): 11 | crypto_zap.add_liquidity( 12 | INITIAL_AMOUNTS, 0, {"from": alice, "value": INITIAL_AMOUNTS[2]} 13 | ) 14 | crypto_lp_token.approve(crypto_zap, 2 ** 256 - 1, {"from": alice}) 15 | return crypto_lp_token.balanceOf(alice) 16 | 17 | 18 | @pytest.mark.parametrize("idx", range(3)) 19 | def test_remove_one_coin(alice, crypto_zap, coins, lp_token_amount, idx): 20 | crypto_zap.remove_liquidity_one_coin(lp_token_amount, idx, 0, {"from": alice}) 21 | 22 | if idx == 2: 23 | assert alice.balance() / 10 ** 18 > 100 24 | return 25 | 26 | assert coins[idx].balanceOf(alice) > INITIAL_AMOUNTS[idx] 27 | 28 | 29 | @pytest.mark.parametrize("idx,scale", it.product(range(3), [0.3, 0.6, 0.9])) 30 | def test_remove_one_coin_percentage( 31 | alice, crypto_zap, coins, lp_token_amount, idx, scale 32 | ): 33 | crypto_zap.remove_liquidity_one_coin( 34 | lp_token_amount * scale, idx, 0, {"from": alice} 35 | ) 36 | 37 | if idx == 2: 38 | assert alice.balance() / 10 ** 18 > 85 + 15 * scale 39 | return 40 | 41 | assert coins[idx].balanceOf(alice) > INITIAL_AMOUNTS[idx] 42 | 43 | 44 | @pytest.mark.parametrize("idx", range(3)) 45 | def test_remove_one_coin_min_amount(alice, crypto_zap, coins, lp_token_amount, idx): 46 | crypto_zap.remove_liquidity_one_coin( 47 | lp_token_amount, idx, INITIAL_AMOUNTS[idx], {"from": alice} 48 | ) 49 | 50 | if idx == 2: 51 | assert alice.balance() / 10 ** 18 > 100 52 | return 53 | 54 | assert coins[idx].balanceOf(alice) > INITIAL_AMOUNTS[idx] 55 | 56 | 57 | @pytest.mark.parametrize("idx", range(3)) 58 | def test_remove_one_coin_alt_receiver( 59 | alice, bob, crypto_zap, coins, lp_token_amount, idx 60 | ): 61 | crypto_zap.remove_liquidity_one_coin(lp_token_amount, idx, 0, bob, {"from": alice}) 62 | 63 | if idx == 2: 64 | assert bob.balance() / 10 ** 18 > 100 65 | return 66 | 67 | assert coins[idx].balanceOf(bob) > INITIAL_AMOUNTS[idx] 68 | 69 | 70 | @pytest.mark.parametrize("idx", range(3)) 71 | def test_remove_one_coin_min_amount_revert(alice, crypto_zap, lp_token_amount, idx): 72 | with brownie.reverts(): 73 | crypto_zap.remove_liquidity_one_coin( 74 | lp_token_amount, idx, 2 ** 256 - 1, {"from": alice} 75 | ) 76 | 77 | -------------------------------------------------------------------------------- /tests/token/test_transfer.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | 4 | 5 | @pytest.fixture(scope="module", autouse=True) 6 | def setup(alice, token): 7 | token.mint(alice, 10 ** 21, {"from": alice}) 8 | 9 | 10 | def test_sender_balance_decreases(alice, bob, token): 11 | sender_balance = token.balanceOf(alice) 12 | amount = sender_balance // 4 13 | 14 | token.transfer(bob, amount, {"from": alice}) 15 | 16 | assert token.balanceOf(alice) == sender_balance - amount 17 | 18 | 19 | def test_receiver_balance_increases(alice, bob, token): 20 | receiver_balance = token.balanceOf(bob) 21 | amount = token.balanceOf(alice) // 4 22 | 23 | token.transfer(bob, amount, {"from": alice}) 24 | 25 | assert token.balanceOf(bob) == receiver_balance + amount 26 | 27 | 28 | def test_total_supply_not_affected(alice, bob, token): 29 | total_supply = token.totalSupply() 30 | amount = token.balanceOf(alice) 31 | 32 | token.transfer(bob, amount, {"from": alice}) 33 | 34 | assert token.totalSupply() == total_supply 35 | 36 | 37 | def test_returns_true(alice, bob, token): 38 | amount = token.balanceOf(alice) 39 | tx = token.transfer(bob, amount, {"from": alice}) 40 | 41 | assert tx.return_value is True 42 | 43 | 44 | def test_transfer_full_balance(alice, bob, token): 45 | amount = token.balanceOf(alice) 46 | receiver_balance = token.balanceOf(bob) 47 | 48 | token.transfer(bob, amount, {"from": alice}) 49 | 50 | assert token.balanceOf(alice) == 0 51 | assert token.balanceOf(bob) == receiver_balance + amount 52 | 53 | 54 | def test_transfer_zero_tokens(alice, bob, token): 55 | sender_balance = token.balanceOf(alice) 56 | receiver_balance = token.balanceOf(bob) 57 | 58 | token.transfer(bob, 0, {"from": alice}) 59 | 60 | assert token.balanceOf(alice) == sender_balance 61 | assert token.balanceOf(bob) == receiver_balance 62 | 63 | 64 | def test_transfer_to_self(alice, bob, token): 65 | sender_balance = token.balanceOf(alice) 66 | amount = sender_balance // 4 67 | 68 | token.transfer(alice, amount, {"from": alice}) 69 | 70 | assert token.balanceOf(alice) == sender_balance 71 | 72 | 73 | def test_insufficient_balance(alice, bob, token): 74 | balance = token.balanceOf(alice) 75 | 76 | with brownie.reverts(): 77 | token.transfer(bob, balance + 1, {"from": alice}) 78 | 79 | 80 | def test_transfer_event_fires(alice, bob, token): 81 | amount = token.balanceOf(alice) 82 | tx = token.transfer(bob, amount, {"from": alice}) 83 | 84 | assert len(tx.events) == 1 85 | assert tx.events["Transfer"].values() == [alice, bob, amount] 86 | -------------------------------------------------------------------------------- /tests/tricrypto/test_stateful_admin_fee.py: -------------------------------------------------------------------------------- 1 | from .stateful_base import StatefulBase 2 | from math import log 3 | from brownie.test import strategy 4 | 5 | MAX_SAMPLES = 20 6 | STEP_COUNT = 100 7 | NO_CHANGE = 2**256-1 8 | 9 | 10 | def approx(x1, x2, precision): 11 | return abs(log(x1 / x2)) <= precision 12 | 13 | 14 | class StatefulAdmin(StatefulBase): 15 | exchange_amount_in = strategy('uint256', min_value=10**17, max_value=10**5 * 10**18) 16 | 17 | def setup(self): 18 | super().setup(user_id=1) 19 | admin = self.accounts[0] 20 | self.swap.commit_new_parameters( 21 | NO_CHANGE, 22 | NO_CHANGE, 23 | 5 * 10**9, # admin fee 24 | NO_CHANGE, 25 | NO_CHANGE, 26 | NO_CHANGE, 27 | NO_CHANGE, 28 | {'from': admin}) 29 | self.chain.sleep(3 * 86400 + 1) 30 | self.swap.apply_new_parameters({'from': admin}) 31 | assert self.swap.admin_fee() == 5 * 10**9 32 | self.mid_fee = self.swap.mid_fee() 33 | self.out_fee = self.swap.out_fee() 34 | self.admin_fee = 5 * 10**9 35 | 36 | def rule_exchange(self, exchange_amount_in, exchange_i, exchange_j, user): 37 | admin_balance = self.token.balanceOf(self.accounts[0]) 38 | if exchange_i > 0: 39 | exchange_amount_in_converted = exchange_amount_in * 10**18 // self.swap.price_oracle(exchange_i - 1) 40 | else: 41 | exchange_amount_in_converted = exchange_amount_in 42 | super().rule_exchange(exchange_amount_in_converted, exchange_i, exchange_j, user) 43 | admin_balance = self.token.balanceOf(self.accounts[0]) - admin_balance 44 | self.total_supply += admin_balance 45 | 46 | def rule_claim_admin_fees(self): 47 | balance = self.token.balanceOf(self.accounts[0]) 48 | 49 | self.swap.claim_admin_fees() 50 | admin_balance = self.token.balanceOf(self.accounts[0]) 51 | balance = admin_balance - balance 52 | self.total_supply += balance 53 | 54 | if balance > 0: 55 | self.xcp_profit = self.swap.xcp_profit() 56 | measured_profit = admin_balance / self.total_supply 57 | assert approx(measured_profit, log(self.xcp_profit / 1e18) / 2, 0.1) 58 | 59 | 60 | def test_admin(crypto_swap, token, chain, accounts, coins, state_machine): 61 | from hypothesis._settings import HealthCheck 62 | 63 | state_machine(StatefulAdmin, chain, accounts, coins, crypto_swap, token, 64 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': STEP_COUNT, 'suppress_health_check': HealthCheck.all()}) 65 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_aave/test_zap_remove_liquidity_one_coin_a.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | import itertools as it 4 | 5 | INITIAL_AMOUNTS = [24000 * 10**6, 10000 * 10**18, 10000 * 10**6, 10000 * 10**6] 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def lp_token_amount(alice, crypto_zap, token): 10 | crypto_zap.add_liquidity(INITIAL_AMOUNTS, 0, {"from": alice}) 11 | token.approve(crypto_zap, 2 ** 256 - 1, {"from": alice}) 12 | return token.balanceOf(alice) 13 | 14 | 15 | @pytest.mark.parametrize("idx", range(4)) 16 | def test_remove_one_coin(alice, crypto_zap, underlying_coins, lp_token_amount, idx): 17 | calc_amount = crypto_zap.calc_withdraw_one_coin(lp_token_amount, idx) 18 | amount = underlying_coins[idx].balanceOf(alice) 19 | crypto_zap.remove_liquidity_one_coin(lp_token_amount, idx, 0, {"from": alice}) 20 | amount = underlying_coins[idx].balanceOf(alice) - amount 21 | 22 | assert amount > INITIAL_AMOUNTS[idx] 23 | assert abs(amount - calc_amount) / amount < 0.01 24 | 25 | 26 | @pytest.mark.parametrize("idx,scale", it.product(range(4), [0.3, 0.6, 0.9])) 27 | def test_remove_one_coin_percentage( 28 | alice, crypto_zap, underlying_coins, lp_token_amount, idx, scale 29 | ): 30 | calc_amount = crypto_zap.calc_withdraw_one_coin(int(lp_token_amount * scale), idx) 31 | amount = underlying_coins[idx].balanceOf(alice) 32 | crypto_zap.remove_liquidity_one_coin( 33 | int(lp_token_amount * scale), idx, 0, {"from": alice} 34 | ) 35 | amount = underlying_coins[idx].balanceOf(alice) - amount 36 | 37 | assert amount > INITIAL_AMOUNTS[idx] * scale / 5 38 | assert abs(amount - calc_amount) / amount < 0.01 39 | 40 | 41 | @pytest.mark.parametrize("idx", range(4)) 42 | def test_remove_one_coin_min_amount(alice, crypto_zap, underlying_coins, lp_token_amount, idx): 43 | crypto_zap.remove_liquidity_one_coin( 44 | lp_token_amount, idx, INITIAL_AMOUNTS[idx], {"from": alice} 45 | ) 46 | 47 | assert underlying_coins[idx].balanceOf(alice) > INITIAL_AMOUNTS[idx] 48 | 49 | 50 | @pytest.mark.parametrize("idx", range(4)) 51 | def test_remove_one_coin_alt_receiver( 52 | alice, bob, crypto_zap, underlying_coins, lp_token_amount, idx 53 | ): 54 | crypto_zap.remove_liquidity_one_coin(lp_token_amount, idx, 0, bob, {"from": alice}) 55 | 56 | assert underlying_coins[idx].balanceOf(bob) > INITIAL_AMOUNTS[idx] 57 | 58 | 59 | @pytest.mark.parametrize("idx", range(4)) 60 | def test_remove_one_coin_min_amount_revert(alice, crypto_zap, lp_token_amount, idx): 61 | with brownie.reverts(): 62 | crypto_zap.remove_liquidity_one_coin( 63 | lp_token_amount, idx, 2 ** 256 - 1, {"from": alice} 64 | ) 65 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_mainnet/test_zap_remove_liquidity_one_coin.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | import itertools as it 4 | 5 | INITIAL_AMOUNTS = [24000 * 10**6, 10000 * 10**18, 10000 * 10**6, 10000 * 10**6] 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def lp_token_amount(alice, crypto_zap, token): 10 | crypto_zap.add_liquidity(INITIAL_AMOUNTS, 0, {"from": alice}) 11 | token.approve(crypto_zap, 2 ** 256 - 1, {"from": alice}) 12 | return token.balanceOf(alice) 13 | 14 | 15 | @pytest.mark.parametrize("idx", range(4)) 16 | def test_remove_one_coin(alice, crypto_zap, underlying_coins, lp_token_amount, idx): 17 | calc_amount = crypto_zap.calc_withdraw_one_coin(lp_token_amount, idx) 18 | amount = underlying_coins[idx].balanceOf(alice) 19 | crypto_zap.remove_liquidity_one_coin(lp_token_amount, idx, 0, {"from": alice}) 20 | amount = underlying_coins[idx].balanceOf(alice) - amount 21 | 22 | assert amount > INITIAL_AMOUNTS[idx] 23 | assert abs(amount - calc_amount) / amount < 0.01 24 | 25 | 26 | @pytest.mark.parametrize("idx,scale", it.product(range(4), [0.3, 0.6, 0.9])) 27 | def test_remove_one_coin_percentage( 28 | alice, crypto_zap, underlying_coins, lp_token_amount, idx, scale 29 | ): 30 | calc_amount = crypto_zap.calc_withdraw_one_coin(int(lp_token_amount * scale), idx) 31 | amount = underlying_coins[idx].balanceOf(alice) 32 | crypto_zap.remove_liquidity_one_coin( 33 | int(lp_token_amount * scale), idx, 0, {"from": alice} 34 | ) 35 | amount = underlying_coins[idx].balanceOf(alice) - amount 36 | 37 | assert amount > INITIAL_AMOUNTS[idx] * scale / 5 38 | assert abs(amount - calc_amount) / amount < 0.01 39 | 40 | 41 | @pytest.mark.parametrize("idx", range(4)) 42 | def test_remove_one_coin_min_amount(alice, crypto_zap, underlying_coins, lp_token_amount, idx): 43 | crypto_zap.remove_liquidity_one_coin( 44 | lp_token_amount, idx, INITIAL_AMOUNTS[idx], {"from": alice} 45 | ) 46 | 47 | assert underlying_coins[idx].balanceOf(alice) > INITIAL_AMOUNTS[idx] 48 | 49 | 50 | @pytest.mark.parametrize("idx", range(4)) 51 | def test_remove_one_coin_alt_receiver( 52 | alice, bob, crypto_zap, underlying_coins, lp_token_amount, idx 53 | ): 54 | crypto_zap.remove_liquidity_one_coin(lp_token_amount, idx, 0, bob, {"from": alice}) 55 | 56 | assert underlying_coins[idx].balanceOf(bob) > INITIAL_AMOUNTS[idx] 57 | 58 | 59 | @pytest.mark.parametrize("idx", range(4)) 60 | def test_remove_one_coin_min_amount_revert(alice, crypto_zap, lp_token_amount, idx): 61 | with brownie.reverts(): 62 | crypto_zap.remove_liquidity_one_coin( 63 | lp_token_amount, idx, 2 ** 256 - 1, {"from": alice} 64 | ) 65 | -------------------------------------------------------------------------------- /scripts/deploy_mainnet_crv_pool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | network, 5 | CurveTokenV4, 6 | CurveCryptoSwap2ETH, 7 | compile_source, 8 | ) 9 | from brownie import interface 10 | import json 11 | 12 | VYPER_VERSION = "0.3.0" # Forced version, use None when brownie supports the new version 13 | 14 | # Addresses are taken for Polygon 15 | COINS = [ 16 | "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH 17 | "0xD533a949740bb3306d119CC777fa900bA034cd52" # CRV 18 | ] 19 | FEE_RECEIVER = "0xeCb456EA5365865EbAb8a2661B0c503410e9B347" 20 | 21 | 22 | def main(): 23 | accounts.load('babe') 24 | 25 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=eth").json() 26 | INITIAL_PRICE = int(p['curve-dao-token']['eth'] * 1e18) 27 | txparams = {"from": accounts[0]} 28 | if network.show_active() == 'mainnet': 29 | txparams.update({'required_confs': 5, 'priority_fee': '2 gwei'}) 30 | print('CRV price:', INITIAL_PRICE / 1e18, 'ETH') 31 | 32 | token = CurveTokenV4.deploy("Curve CRV-ETH", "crvCRVETH", txparams) 33 | coins = [interface.ERC20(addr) for addr in COINS] 34 | 35 | source = CurveCryptoSwap2ETH._build["source"] 36 | source = source.replace("0x0000000000000000000000000000000000000001", token.address) 37 | source = source.replace("0x0000000000000000000000000000000000000010", coins[0].address) 38 | source = source.replace("0x0000000000000000000000000000000000000011", coins[1].address) 39 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 40 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 41 | with open("CryptoSwap.vy", "w") as f: 42 | f.write(source) 43 | deployer = compile_source(source, vyper_version=VYPER_VERSION).Vyper 44 | 45 | swap = deployer.deploy( 46 | accounts[0], 47 | FEE_RECEIVER, 48 | 10 * 2**2 * 10000, # A 49 | int(1.45e-4 * 1e18), # gamma 50 | int(2.6e-3 * 1e10), # mid_fee 51 | int(4.5e-3 * 1e10), # out_fee 52 | 2 * 10**12, # allowed_extra_profit - same as tricrypto2 53 | int(2.3e-4 * 1e18), # fee_gamma 54 | int(1.46e-4 * 1e18), # adjustment_step 55 | 5 * 10**9, # admin_fee 56 | 600, # ma_half_time 57 | INITIAL_PRICE, # price 58 | txparams 59 | ) 60 | token.set_minter(swap, txparams) 61 | 62 | print("Swap address:", swap.address) 63 | print("Token address:", token.address) 64 | 65 | with open("swap.json", "w") as f: 66 | json.dump(swap.abi, f) 67 | 68 | with open("token.json", "w") as f: 69 | json.dump(token.abi, f) 70 | 71 | return swap, token 72 | -------------------------------------------------------------------------------- /tests/tricrypto/forked/test_zap_remove_liquidity.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | 4 | 5 | @pytest.fixture(scope="module") 6 | def initial_amounts(crypto_swap): 7 | usd = 40_000 8 | usdt = usd * 10**6 9 | wbtc = usd * 10**18 * 10**8 // crypto_swap.price_oracle(0) 10 | weth = usd * (10**18)**2 // crypto_swap.price_oracle(1) 11 | return [usdt, wbtc, weth] 12 | 13 | 14 | @pytest.fixture(scope="module") 15 | def lp_token_amount(alice, crypto_zap, crypto_lp_token, initial_amounts): 16 | crypto_zap.add_liquidity( 17 | initial_amounts, 0, {"from": alice, "value": initial_amounts[2]} 18 | ) 19 | crypto_lp_token.approve(crypto_zap, 2 ** 256 - 1, {"from": alice}) 20 | return crypto_lp_token.balanceOf(alice) 21 | 22 | 23 | def test_remove_all_coins(alice, crypto_zap, coins, lp_token_amount, initial_amounts): 24 | balances = [c.balanceOf(alice) for c in coins[:-1]] + [alice.balance()] 25 | 26 | crypto_zap.remove_liquidity(lp_token_amount, [0] * 3, {"from": alice}) 27 | 28 | for i in range(len(coins) - 1): 29 | assert coins[i].balanceOf(alice) >= balances[i] + 0.99 * initial_amounts[i] 30 | # balanced withdrawal 31 | assert alice.balance() > balances[-1] + 0.99 * initial_amounts[-1] 32 | 33 | 34 | def test_remove_all_coins_min_amount( 35 | alice, crypto_zap, coins, lp_token_amount, decimals, initial_amounts 36 | ): 37 | balances = [c.balanceOf(alice) for c in coins[:-1]] + [alice.balance()] 38 | 39 | amounts = [int(amt * 0.5) for amt in initial_amounts] 40 | crypto_zap.remove_liquidity(lp_token_amount, amounts, {"from": alice}) 41 | 42 | for i in range(len(coins) - 1): 43 | assert coins[i].balanceOf(alice) >= balances[i] + 0.99 * initial_amounts[i] 44 | # balanced withdrawal 45 | assert alice.balance() > balances[-1] + 0.99 * initial_amounts[-1] 46 | 47 | 48 | def test_alternate_receiver(alice, bob, crypto_zap, coins, lp_token_amount, decimals): 49 | crypto_zap.remove_liquidity(lp_token_amount, [0] * 3, bob, {"from": alice}) 50 | 51 | for coin in coins[:-1]: 52 | assert coin.balanceOf(bob) >= 0 53 | assert bob.balance() / 10 ** 18 >= 100 54 | 55 | 56 | @pytest.mark.parametrize("scale", [0.2, 0.4, 0.6, 0.8]) 57 | def test_remove_percentage_of_coins_min_amount_revert( 58 | alice, crypto_zap, lp_token_amount, scale, initial_amounts 59 | ): 60 | with brownie.reverts(): 61 | crypto_zap.remove_liquidity( 62 | lp_token_amount * scale, initial_amounts, {"from": alice} 63 | ) 64 | 65 | 66 | def test_remove_all_coins_min_amount_revert(alice, crypto_zap, lp_token_amount): 67 | with brownie.reverts(): 68 | crypto_zap.remove_liquidity( 69 | lp_token_amount, [2 ** 256 - 1] * 3, {"from": alice} 70 | ) 71 | -------------------------------------------------------------------------------- /scripts/deploy_poly_eurt_metapool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | CurveTokenV4, 5 | CurveCryptoSwap2, 6 | ZapTwoAave, 7 | compile_source, 8 | ) 9 | from brownie import interface 10 | import json 11 | 12 | # Addresses are taken for Polygon 13 | COINS = [ 14 | "0x7BDF330f423Ea880FF95fC41A280fD5eCFD3D09f", # EURT 15 | "0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171" # 3Crv 16 | ] 17 | SWAP = "0x445FE580eF8d70FF569aB36e80c647af338db351" 18 | FEE_RECEIVER = "0x0000000000000000000000000000000000000000" 19 | 20 | 21 | def main(): 22 | accounts.load('babe') 23 | 24 | virtual_price = interface.StableSwap2Pool(SWAP).get_virtual_price() 25 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=tether-eurt&vs_currencies=usd").json() 26 | INITIAL_PRICE = int(virtual_price / p['tether-eurt']['usd']) 27 | txparams = {"from": accounts[0], 'required_confs': 20, 'gasPrice': '30 gwei'} 28 | print('Euro price:', 1e18 / INITIAL_PRICE, '3crv') 29 | 30 | token = CurveTokenV4.deploy("Curve EURT-3Crv", "crvEURTUSD", txparams) 31 | coins = [interface.ERC20(addr) for addr in COINS] 32 | 33 | source = CurveCryptoSwap2._build["source"] 34 | source = source.replace("0x0000000000000000000000000000000000000001", token.address) 35 | source = source.replace("0x0000000000000000000000000000000000000010", coins[0].address) 36 | source = source.replace("0x0000000000000000000000000000000000000011", coins[1].address) 37 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 38 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 39 | with open("CryptoSwap.vy", "w") as f: 40 | f.write(source) 41 | deployer = compile_source(source, vyper_version="0.3.0").Vyper 42 | 43 | swap = deployer.deploy( 44 | accounts[0], 45 | FEE_RECEIVER, 46 | 5000 * 2**2 * 10000, # A 47 | int(1e-4 * 1e18), # gamma 48 | int(5e-4 * 1e10), # mid_fee 49 | int(45e-4 * 1e10), # out_fee 50 | 10**10, # allowed_extra_profit 51 | int(5e-3 * 1e18), # fee_gamma 52 | int(0.55e-5 * 1e18), # adjustment_step ? 53 | 5 * 10**9, # admin_fee 54 | 600, # ma_half_time 55 | INITIAL_PRICE, # price 56 | txparams 57 | ) 58 | token.set_minter(swap, txparams) 59 | 60 | zap = ZapTwoAave.deploy(swap.address, SWAP, txparams) 61 | 62 | print("Swap address:", swap.address) 63 | print("Token address:", token.address) 64 | print("Zap address:", zap.address) 65 | 66 | with open("swap.json", "w") as f: 67 | json.dump(swap.abi, f) 68 | 69 | with open("token.json", "w") as f: 70 | json.dump(token.abi, f) 71 | 72 | with open("zap.json", "w") as f: 73 | json.dump(zap.abi, f) 74 | 75 | return swap, token, zap 76 | -------------------------------------------------------------------------------- /scripts/deploy_arbi_eurs_metapool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | CurveTokenV4, 5 | CurveCryptoSwap2, 6 | ZapTwoArbiEurs, 7 | compile_source, 8 | ) 9 | from brownie import interface 10 | import json 11 | 12 | # Addresses are taken for Arbitrum 13 | COINS = [ 14 | "0xD22a58f79e9481D1a88e00c343885A588b34b68B", # EURS 15 | "0x7f90122BF0700F9E7e1F688fe926940E8839F353" # 2Crv 16 | ] 17 | SWAP = "0x7f90122BF0700F9E7e1F688fe926940E8839F353" 18 | FEE_RECEIVER = "0x0000000000000000000000000000000000000000" 19 | 20 | 21 | def main(): 22 | accounts.load('babe') 23 | 24 | virtual_price = interface.StableSwap2Pool(SWAP).get_virtual_price() 25 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=stasis-eurs&vs_currencies=usd").json() 26 | INITIAL_PRICE = int(virtual_price / p['stasis-eurs']['usd']) 27 | txparams = {"from": accounts[0], 'required_confs': 5, 'gasPrice': '2 gwei', 'gas': 200 * 10**6} 28 | print('Euro price:', 1e18 / INITIAL_PRICE, '2crv') 29 | 30 | token = CurveTokenV4.deploy("Curve EURS-2Crv", "crvEURSUSD", txparams) 31 | coins = [interface.ERC20(addr) for addr in COINS] 32 | 33 | source = CurveCryptoSwap2._build["source"] 34 | source = source.replace("0x0000000000000000000000000000000000000001", token.address) 35 | source = source.replace("0x0000000000000000000000000000000000000010", coins[0].address) 36 | source = source.replace("0x0000000000000000000000000000000000000011", coins[1].address) 37 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 38 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 39 | with open("CryptoSwap.vy", "w") as f: 40 | f.write(source) 41 | deployer = compile_source(source, vyper_version="0.3.0").Vyper 42 | 43 | swap = deployer.deploy( 44 | accounts[0], 45 | FEE_RECEIVER, 46 | 5000 * 2**2 * 10000, # A 47 | int(1e-4 * 1e18), # gamma 48 | int(5e-4 * 1e10), # mid_fee 49 | int(45e-4 * 1e10), # out_fee 50 | 10**10, # allowed_extra_profit 51 | int(5e-3 * 1e18), # fee_gamma 52 | int(0.55e-5 * 1e18), # adjustment_step ? 53 | 5 * 10**9, # admin_fee 54 | 600, # ma_half_time 55 | INITIAL_PRICE, # price 56 | txparams 57 | ) 58 | token.set_minter(swap, txparams) 59 | 60 | zap = ZapTwoArbiEurs.deploy(swap.address, SWAP, txparams) 61 | 62 | print("Swap address:", swap.address) 63 | print("Token address:", token.address) 64 | print("Zap address:", zap.address) 65 | 66 | with open("swap.json", "w") as f: 67 | json.dump(swap.abi, f) 68 | 69 | with open("token.json", "w") as f: 70 | json.dump(token.abi, f) 71 | 72 | with open("zap.json", "w") as f: 73 | json.dump(zap.abi, f) 74 | 75 | return swap, token, zap 76 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_aave/test_zap_remove_liquidity_a.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import brownie 3 | 4 | INITIAL_AMOUNTS = [24000 * 10**6, 10000 * 10**18, 10000 * 10**6, 10000 * 10**6] 5 | 6 | 7 | @pytest.fixture(scope="module") 8 | def lp_token_amount(alice, crypto_zap, token): 9 | crypto_zap.add_liquidity(INITIAL_AMOUNTS, 0, {"from": alice}) 10 | token.approve(crypto_zap, 2 ** 256 - 1, {"from": alice}) 11 | return token.balanceOf(alice) 12 | 13 | 14 | def test_remove_all_coins(alice, crypto_zap, underlying_coins, lp_token_amount, base_decimals): 15 | balances = [c.balanceOf(alice) for c in underlying_coins] 16 | 17 | crypto_zap.remove_liquidity(lp_token_amount, [0] * 4, {"from": alice}) 18 | 19 | assert underlying_coins[0].balanceOf(alice) >= balances[0] + 0.97 * INITIAL_AMOUNTS[0] 20 | 21 | usd = sum((underlying_coins[i].balanceOf(alice) - balances[i]) * 10**(18 - base_decimals[i - 1]) for i in range(1, 4)) 22 | assert usd >= 0.97 * sum(INITIAL_AMOUNTS[i] * 10**(18 - base_decimals[i - 1]) for i in range(1, 4)) 23 | 24 | 25 | def test_remove_all_coins_min_amount( 26 | alice, crypto_zap, underlying_coins, lp_token_amount, decimals, base_decimals, stable_swap 27 | ): 28 | balances = [c.balanceOf(alice) for c in underlying_coins] 29 | 30 | stable_precisions = [1, 10**12, 10**12] 31 | total = sum(stable_swap.balances(i) * stable_precisions[i] for i in range(3)) 32 | frac = [stable_swap.balances(i) * stable_precisions[i] / total for i in range(3)] 33 | total = sum(INITIAL_AMOUNTS[i+1] * stable_precisions[i] for i in range(3)) 34 | 35 | amounts = [int(0.9 * INITIAL_AMOUNTS[0])] + [int(0.9 * frac[i] * total / stable_precisions[i]) for i in range(3)] 36 | crypto_zap.remove_liquidity(lp_token_amount, amounts, {"from": alice}) 37 | 38 | assert underlying_coins[0].balanceOf(alice) >= balances[0] + 0.97 * INITIAL_AMOUNTS[0] 39 | 40 | usd = sum((underlying_coins[i].balanceOf(alice) - balances[i]) * 10**(18 - base_decimals[i - 1]) for i in range(1, 4)) 41 | assert usd >= 0.97 * sum(INITIAL_AMOUNTS[i] * 10**(18 - base_decimals[i - 1]) for i in range(1, 4)) 42 | 43 | 44 | def test_alternate_receiver(alice, bob, crypto_zap, underlying_coins, lp_token_amount, decimals): 45 | crypto_zap.remove_liquidity(lp_token_amount, [0] * 4, bob, {"from": alice}) 46 | 47 | for coin in underlying_coins: 48 | assert coin.balanceOf(bob) >= 0 49 | 50 | 51 | @pytest.mark.parametrize("scale", [0.2, 0.4, 0.6, 0.8]) 52 | def test_remove_percentage_of_coins_min_amount_revert( 53 | alice, crypto_zap, lp_token_amount, scale 54 | ): 55 | with brownie.reverts(): 56 | crypto_zap.remove_liquidity( 57 | lp_token_amount * scale, INITIAL_AMOUNTS, {"from": alice} 58 | ) 59 | 60 | 61 | def test_remove_all_coins_min_amount_revert(alice, crypto_zap, lp_token_amount): 62 | with brownie.reverts(): 63 | crypto_zap.remove_liquidity( 64 | lp_token_amount, [2 ** 256 - 1] * 4, {"from": alice} 65 | ) 66 | -------------------------------------------------------------------------------- /tests/twocrypto/test_simulate.py: -------------------------------------------------------------------------------- 1 | from math import log 2 | from brownie.test import strategy 3 | from .stateful_base import StatefulBase 4 | from . import simulation_int_many as sim 5 | 6 | MAX_SAMPLES = 20 7 | STEP_COUNT = 100 8 | 9 | 10 | def approx(x1, x2, precision): 11 | return abs(log(x1 / x2)) <= precision 12 | 13 | 14 | class StatefulSimulation(StatefulBase): 15 | exchange_amount_in = strategy('uint256', min_value=10**17, max_value=10**5 * 10**18) 16 | 17 | def setup(self): 18 | super().setup() 19 | 20 | for u in self.accounts[1:]: 21 | for coin, q in zip(self.coins, self.initial_deposit): 22 | coin._mint_for_testing(u, q) 23 | for i in range(2): 24 | self.balances[i] += self.initial_deposit[i] 25 | self.swap.add_liquidity(self.initial_deposit, 0, {'from': u}) 26 | self.total_supply += self.token.balanceOf(u) 27 | 28 | self.virtual_price = self.swap.get_virtual_price() 29 | 30 | self.trader = sim.Trader( 31 | self.swap.A(), 32 | self.swap.gamma(), 33 | self.swap.D(), 34 | 2, 35 | [10**18, self.swap.price_scale()], 36 | self.swap.mid_fee() / 1e10, 37 | self.swap.out_fee() / 1e10, 38 | self.swap.allowed_extra_profit(), 39 | self.swap.fee_gamma(), 40 | self.swap.adjustment_step() / 1e18, 41 | self.swap.ma_half_time() 42 | ) 43 | for i in range(2): 44 | self.trader.curve.x[i] = self.swap.balances(i) 45 | 46 | # Adjust virtual prices 47 | self.trader.xcp_profit = self.swap.xcp_profit() 48 | self.trader.xcp_profit_real = self.swap.virtual_price() 49 | self.trader.t = self.chain[-1].timestamp 50 | 51 | def rule_exchange(self, exchange_amount_in, exchange_i, user): 52 | exchange_j = 1 - exchange_i 53 | exchange_amount_in = exchange_amount_in * 10**18 // self.trader.price_oracle[exchange_i] 54 | 55 | if super().rule_exchange(exchange_amount_in, exchange_i, user): 56 | dy = self.trader.buy(exchange_amount_in, exchange_i, exchange_j) 57 | price = exchange_amount_in * 10**18 // dy 58 | self.trader.tweak_price(self.chain[-1].timestamp, exchange_i, exchange_j, price) 59 | 60 | def invariant_simulator(self): 61 | if self.trader.xcp_profit / 1e18 - 1 > 1e-8: 62 | assert abs(self.trader.xcp_profit - self.swap.xcp_profit()) / (self.trader.xcp_profit - 10**18) < 0.05 63 | assert approx(self.trader.curve.p[1], self.swap.price_scale(), 1e-4) # adjustment_step * 2 64 | 65 | 66 | def test_sim(crypto_swap, token, chain, accounts, coins, state_machine): 67 | from hypothesis._settings import HealthCheck 68 | 69 | state_machine(StatefulSimulation, chain, accounts, coins, crypto_swap, token, 70 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': STEP_COUNT, 'suppress_health_check': HealthCheck.all()}) 71 | -------------------------------------------------------------------------------- /tests/tricrypto/test_simulate.py: -------------------------------------------------------------------------------- 1 | from math import log 2 | from brownie.test import strategy 3 | from .stateful_base import StatefulBase 4 | from . import simulation_int_many as sim 5 | 6 | MAX_SAMPLES = 20 7 | STEP_COUNT = 100 8 | 9 | 10 | def approx(x1, x2, precision): 11 | return abs(log(x1 / x2)) <= precision 12 | 13 | 14 | class StatefulSimulation(StatefulBase): 15 | exchange_amount_in = strategy('uint256', min_value=10**17, max_value=10**5 * 10**18) 16 | 17 | def setup(self): 18 | super().setup() 19 | 20 | for u in self.accounts[1:]: 21 | for coin, q in zip(self.coins, self.initial_deposit): 22 | coin._mint_for_testing(u, q) 23 | for i in range(3): 24 | self.balances[i] += self.initial_deposit[i] 25 | self.swap.add_liquidity(self.initial_deposit, 0, {'from': u}) 26 | self.total_supply += self.token.balanceOf(u) 27 | 28 | self.virtual_price = self.swap.get_virtual_price() 29 | 30 | self.trader = sim.Trader( 31 | self.swap.A(), 32 | self.swap.gamma(), 33 | self.swap.D(), 34 | 3, 35 | [10**18] + [self.swap.price_scale(i) for i in range(2)], 36 | self.swap.mid_fee() / 1e10, 37 | self.swap.out_fee() / 1e10, 38 | self.swap.allowed_extra_profit(), 39 | self.swap.fee_gamma(), 40 | self.swap.adjustment_step() / 1e18, 41 | self.swap.ma_half_time() 42 | ) 43 | for i in range(3): 44 | self.trader.curve.x[i] = self.swap.balances(i) 45 | 46 | # Adjust virtual prices 47 | self.trader.xcp_profit = self.swap.xcp_profit() 48 | self.trader.xcp_profit_real = self.swap.virtual_price() 49 | self.trader.t = self.chain[-1].timestamp 50 | 51 | def rule_exchange(self, exchange_amount_in, exchange_i, exchange_j, user): 52 | exchange_amount_in = exchange_amount_in * 10**18 // self.trader.price_oracle[exchange_i] 53 | 54 | if super().rule_exchange(exchange_amount_in, exchange_i, exchange_j, user): 55 | dy = self.trader.buy(exchange_amount_in, exchange_i, exchange_j) 56 | price = exchange_amount_in * 10**18 // dy 57 | self.trader.tweak_price(self.chain[-1].timestamp, exchange_i, exchange_j, price) 58 | 59 | def invariant_simulator(self): 60 | if self.trader.xcp_profit / 1e18 - 1 > 1e-8: 61 | assert abs(self.trader.xcp_profit - self.swap.xcp_profit()) / (self.trader.xcp_profit - 10**18) < 0.05 62 | for i in range(2): 63 | assert approx(self.trader.curve.p[i+1], self.swap.price_scale(i), 1e-4) # adjustment_step * 2 64 | 65 | 66 | def test_sim(crypto_swap, token, chain, accounts, coins, state_machine): 67 | from hypothesis._settings import HealthCheck 68 | 69 | state_machine(StatefulSimulation, chain, accounts, coins, crypto_swap, token, 70 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': STEP_COUNT, 'suppress_health_check': HealthCheck.all()}) 71 | -------------------------------------------------------------------------------- /scripts/deploy_plain_polygon.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | CurveCryptoMath3, 5 | CurveTokenV4, 6 | CurveCryptoViews3, 7 | CurveCryptoSwap, 8 | ERC20Mock, 9 | compile_source, 10 | ) 11 | from brownie import interface 12 | 13 | COINS = [ 14 | "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", # USDC 15 | "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", # WBTC 16 | "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619" # WETH 17 | ] 18 | 19 | 20 | def main(): 21 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd").json() 22 | INITIAL_PRICES = [int(p[cur]['usd'] * 1e18) for cur in ['bitcoin', 'ethereum']] 23 | 24 | crypto_math = CurveCryptoMath3.deploy({"from": accounts[0]}) 25 | token = CurveTokenV4.deploy("Curve.fi USD-BTC-ETH", "crvUSDBTCETH", {"from": accounts[0]}) 26 | 27 | if COINS: 28 | coins = [interface.ERC20(addr) for addr in COINS] 29 | else: 30 | coins = [ 31 | ERC20Mock.deploy(name, name, 18, {"from": accounts[0]}) for name in ["USD", "BTC", "ETH"] 32 | ] 33 | 34 | source = CurveCryptoViews3._build["source"] 35 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 36 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 37 | source = source.replace("1,#2", str(10 ** (18 - coins[2].decimals())) + ',') 38 | deployer = compile_source(source, vyper_version="0.2.12").Vyper 39 | crypto_views = deployer.deploy(crypto_math, {"from": accounts[0]}) 40 | 41 | source = CurveCryptoSwap._build["source"] 42 | source = source.replace("0x0000000000000000000000000000000000000000", crypto_math.address) 43 | source = source.replace("0x0000000000000000000000000000000000000001", token.address) 44 | source = source.replace("0x0000000000000000000000000000000000000002", crypto_views.address) 45 | source = source.replace("0x0000000000000000000000000000000000000010", coins[0].address) 46 | source = source.replace("0x0000000000000000000000000000000000000011", coins[1].address) 47 | source = source.replace("0x0000000000000000000000000000000000000012", coins[2].address) 48 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 49 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 50 | source = source.replace("1,#2", str(10 ** (18 - coins[2].decimals())) + ',') 51 | deployer = compile_source(source, vyper_version="0.2.12").Vyper 52 | 53 | swap = deployer.deploy( 54 | accounts[0], 55 | 135 * 3 ** 3, # A 56 | int(7e-5 * 1e18), # gamma 57 | int(4e-4 * 1e10), # mid_fee 58 | int(4e-3 * 1e10), # out_fee 59 | int(0.0028 * 1e18), # price_threshold 60 | int(0.01 * 1e18), # fee_gamma 61 | int(0.0015 * 1e18), # adjustment_step 62 | 0, # admin_fee 63 | 600, # ma_half_time 64 | INITIAL_PRICES, 65 | {"from": accounts[0]}, 66 | ) 67 | token.set_minter(swap, {"from": accounts[0]}) 68 | 69 | print("Deployed at:") 70 | print("Swap:", swap.address) 71 | print("Token:", token.address) 72 | 73 | return swap, token 74 | -------------------------------------------------------------------------------- /scripts/deploy_mainnet_eurt_metapool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | CurveTokenV4, 5 | CurveCryptoSwap2, 6 | ZapTwoEthEurt, 7 | compile_source, 8 | ) 9 | from brownie import interface 10 | import json 11 | 12 | VYPER_VERSION = "0.3.0" # Forced version, use None when brownie supports the new version 13 | 14 | # Addresses are taken for Polygon 15 | COINS = [ 16 | "0xC581b735A1688071A1746c968e0798D642EDE491", # EURT 17 | "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" # 3crv 18 | ] 19 | SWAP = "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7" 20 | FEE_RECEIVER = "0xeCb456EA5365865EbAb8a2661B0c503410e9B347" 21 | 22 | 23 | def main(): 24 | accounts.load('babe') 25 | 26 | virtual_price = interface.StableSwap3Pool(SWAP).get_virtual_price() 27 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=tether-eurt&vs_currencies=usd").json() 28 | INITIAL_PRICE = int(virtual_price / p['tether-eurt']['usd']) 29 | txparams = {"from": accounts[0], 'required_confs': 5, 'priority_fee': '2 gwei'} 30 | print('Euro price:', 1e18 / INITIAL_PRICE, '3crv') 31 | 32 | token = CurveTokenV4.deploy("Curve EURT-3Crv", "crvEURTUSD", txparams) 33 | coins = [interface.ERC20(addr) for addr in COINS] 34 | 35 | source = CurveCryptoSwap2._build["source"] 36 | source = source.replace("0x0000000000000000000000000000000000000001", token.address) 37 | source = source.replace("0x0000000000000000000000000000000000000010", coins[0].address) 38 | source = source.replace("0x0000000000000000000000000000000000000011", coins[1].address) 39 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 40 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 41 | with open("CryptoSwap.vy", "w") as f: 42 | f.write(source) 43 | deployer = compile_source(source, vyper_version=VYPER_VERSION).Vyper 44 | 45 | swap = deployer.deploy( 46 | accounts[0], 47 | FEE_RECEIVER, 48 | 5000 * 2**2 * 10000, # A 49 | int(1e-4 * 1e18), # gamma 50 | int(5e-4 * 1e10), # mid_fee 51 | int(45e-4 * 1e10), # out_fee 52 | 10**10, # allowed_extra_profit 53 | int(5e-3 * 1e18), # fee_gamma 54 | int(0.55e-5 * 1e18), # adjustment_step ? 55 | 5 * 10**9, # admin_fee 56 | 600, # ma_half_time 57 | INITIAL_PRICE, # price 58 | txparams 59 | ) 60 | token.set_minter(swap, txparams) 61 | 62 | path = ZapTwoEthEurt._sources.get_source_path('ZapTwoEthEurt') 63 | with open(path, 'r') as f: 64 | source = f.read() 65 | source = source.replace("0x0000000000000000000000000000000000000001", token.address) 66 | source = source.replace("0x0000000000000000000000000000000000000000", swap.address) 67 | with open("EuroZap.vy", "w") as f: 68 | f.write(source) 69 | deployer = compile_source(source, vyper_version=VYPER_VERSION).Vyper 70 | zap = deployer.deploy(txparams) 71 | 72 | print("Swap address:", swap.address) 73 | print("Token address:", token.address) 74 | print("Zap address:", zap.address) 75 | 76 | with open("swap.json", "w") as f: 77 | json.dump(swap.abi, f) 78 | 79 | with open("token.json", "w") as f: 80 | json.dump(token.abi, f) 81 | 82 | with open("zap.json", "w") as f: 83 | json.dump(zap.abi, f) 84 | 85 | return swap, token, zap 86 | -------------------------------------------------------------------------------- /tests/twocrypto/forked_mainnet/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie_tokens import MintableForkToken 3 | 4 | VYPER_VERSION = "0.3.1" # Forced version, use None when brownie supports the new version 5 | 6 | 7 | BASE_COINS = [ 8 | "0x6B175474E89094C44Da98b954EedeAC495271d0F", # DAI 9 | "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC 10 | "0xdAC17F958D2ee523a2206206994597C13D831ec7" # USDT 11 | ] 12 | COINS = [ 13 | "0xC581b735A1688071A1746c968e0798D642EDE491", # EUR 14 | "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" # 3crv* 15 | ] 16 | BASE_SWAP = "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7" 17 | 18 | 19 | @pytest.fixture(scope="session") 20 | def alice(accounts): 21 | return accounts[0] 22 | 23 | 24 | @pytest.fixture(scope="session") 25 | def bob(accounts): 26 | return accounts[1] 27 | 28 | 29 | @pytest.fixture(scope="session") 30 | def charlie(accounts): 31 | return accounts[2] 32 | 33 | 34 | @pytest.fixture(scope="module") 35 | def coins(): 36 | return [MintableForkToken(addr) for addr in COINS] 37 | 38 | 39 | @pytest.fixture(scope="module") 40 | def base_coins(): 41 | return [MintableForkToken(addr) for addr in BASE_COINS] 42 | 43 | 44 | @pytest.fixture(scope="module") 45 | def underlying_coins(coins, base_coins): 46 | return [coins[0]] + base_coins 47 | 48 | 49 | @pytest.fixture(scope="module") 50 | def decimals(): 51 | return [6, 18] 52 | 53 | 54 | @pytest.fixture(scope="module") 55 | def base_decimals(): 56 | return [18, 6, 6] 57 | 58 | 59 | @pytest.fixture(scope="module", autouse=True) 60 | def crypto_swap(CurveCryptoSwap2, token, coins, alice): 61 | swap = CurveCryptoSwap2.deploy( 62 | alice, 63 | alice, 64 | 90 * 2**2 * 10000, # A 65 | int(2.8e-4 * 1e18), # gamma 66 | int(8.5e-5 * 1e10), # mid_fee 67 | int(1.3e-3 * 1e10), # out_fee 68 | 10**10, # allowed_extra_profit 69 | int(0.012 * 1e18), # fee_gamma 70 | int(0.55e-5 * 1e18), # adjustment_step 71 | 0, # admin_fee 72 | 600, # ma_half_time 73 | int(0.8 * 1e18), # price 74 | token, 75 | coins, 76 | {'from': alice}) 77 | token.set_minter(swap, {"from": alice}) 78 | 79 | return swap 80 | 81 | 82 | @pytest.fixture(scope="module") 83 | def crypto_zap(alice, ZapTwo, crypto_swap): 84 | return ZapTwo.deploy(crypto_swap, BASE_SWAP, {'from': alice}) 85 | 86 | 87 | @pytest.fixture(scope="module", autouse=True) 88 | def pre_mining(alice, crypto_zap, crypto_swap, coins, decimals, base_coins, base_decimals, charlie): 89 | """Mint a bunch of test tokens""" 90 | for c, d in zip(base_coins, base_decimals): 91 | c._mint_for_testing(alice, 100_000 * 10**d) 92 | c.approve(crypto_zap, 2**256 - 1, {'from': alice}) 93 | c._mint_for_testing(charlie, 100_000 * 10**d) 94 | c.approve(crypto_zap, 2**256 - 1, {'from': charlie}) 95 | 96 | coins[0]._mint_for_testing(alice, 300_000 * 10**decimals[0]) 97 | coins[0].approve(crypto_zap, 2**256 - 1, {'from': alice}) 98 | 99 | coins[0]._mint_for_testing(charlie, 300_000 * 10**decimals[0]) 100 | coins[0].approve(crypto_zap, 2**256 - 1, {'from': charlie}) 101 | 102 | crypto_zap.add_liquidity([ 103 | 240000 * 10**6, 100000 * 10**18, 100000 * 10**6, 100000 * 10**6 104 | ], 0, {'from': charlie}) 105 | -------------------------------------------------------------------------------- /deployment-logs/2021-05-24/zap.json: -------------------------------------------------------------------------------- 1 | [{"stateMutability": "nonpayable", "type": "constructor", "inputs": [{"name": "_pool", "type": "address"}, {"name": "_base_pool", "type": "address"}], "outputs": [], "name": "constructor"}, {"stateMutability": "nonpayable", "type": "function", "name": "add_liquidity", "inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "add_liquidity", "inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "exchange_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "exchange_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity", "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity", "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}, {"name": "_receiver", "type": "address"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity_one_coin", "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity_one_coin", "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": []}, {"stateMutability": "view", "type": "function", "name": "get_dy_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 8403}, {"stateMutability": "view", "type": "function", "name": "calc_token_amount", "inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_is_deposit", "type": "bool"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 4305}, {"stateMutability": "view", "type": "function", "name": "calc_withdraw_one_coin", "inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 6199}, {"stateMutability": "view", "type": "function", "name": "coins", "inputs": [{"name": "arg0", "type": "uint256"}], "outputs": [{"name": "", "type": "address"}], "gas": 1407}, {"stateMutability": "view", "type": "function", "name": "underlying_coins", "inputs": [{"name": "arg0", "type": "uint256"}], "outputs": [{"name": "", "type": "address"}], "gas": 1437}, {"stateMutability": "view", "type": "function", "name": "pool", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 1358}, {"stateMutability": "view", "type": "function", "name": "base_pool", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 1388}, {"stateMutability": "view", "type": "function", "name": "token", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 1418}] -------------------------------------------------------------------------------- /deployment-logs/2021-10-04. Avax/zap.json: -------------------------------------------------------------------------------- 1 | [{"inputs": [{"name": "_pool", "type": "address"}, {"name": "_base_pool", "type": "address"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "name": "exchange_underlying", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "exchange_underlying", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 8403, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "name": "get_dy_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 4305, "inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_is_deposit", "type": "bool"}], "name": "calc_token_amount", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 6199, "inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "name": "calc_withdraw_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1343, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1373, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "underlying_coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1358, "inputs": [], "name": "pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1388, "inputs": [], "name": "base_pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1418, "inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2021-08-14. polygon/zap.json: -------------------------------------------------------------------------------- 1 | [{"inputs": [{"name": "_pool", "type": "address"}, {"name": "_base_pool", "type": "address"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "name": "exchange_underlying", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "exchange_underlying", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 8403, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "name": "get_dy_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 4305, "inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_is_deposit", "type": "bool"}], "name": "calc_token_amount", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 6199, "inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "name": "calc_withdraw_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1343, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1373, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "underlying_coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1358, "inputs": [], "name": "pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1388, "inputs": [], "name": "base_pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1418, "inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2021-08-27. Polygon redeployment/zap.json: -------------------------------------------------------------------------------- 1 | [{"inputs": [{"name": "_pool", "type": "address"}, {"name": "_base_pool", "type": "address"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "name": "exchange_underlying", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "exchange_underlying", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 8403, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "name": "get_dy_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 4305, "inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_is_deposit", "type": "bool"}], "name": "calc_token_amount", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 6199, "inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "name": "calc_withdraw_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1343, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1373, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "underlying_coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1358, "inputs": [], "name": "pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1388, "inputs": [], "name": "base_pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1418, "inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2021-10-17. Hamony/zap.json: -------------------------------------------------------------------------------- 1 | [{"stateMutability": "nonpayable", "type": "constructor", "inputs": [{"name": "_pool", "type": "address"}, {"name": "_base_pool", "type": "address"}], "outputs": [], "name": "constructor"}, {"stateMutability": "nonpayable", "type": "function", "name": "add_liquidity", "inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}], "outputs": [], "gas": 32993}, {"stateMutability": "nonpayable", "type": "function", "name": "add_liquidity", "inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": [], "gas": 32993}, {"stateMutability": "nonpayable", "type": "function", "name": "exchange_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "outputs": [], "gas": 20027}, {"stateMutability": "nonpayable", "type": "function", "name": "exchange_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": [], "gas": 20027}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity", "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}], "outputs": [], "gas": 43921}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity", "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}, {"name": "_receiver", "type": "address"}], "outputs": [], "gas": 43921}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity_one_coin", "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "outputs": [], "gas": 14775}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity_one_coin", "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": [], "gas": 14775}, {"stateMutability": "view", "type": "function", "name": "get_dy_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 8787}, {"stateMutability": "view", "type": "function", "name": "calc_token_amount", "inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_is_deposit", "type": "bool"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 4660}, {"stateMutability": "view", "type": "function", "name": "calc_withdraw_one_coin", "inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 6532}, {"stateMutability": "view", "type": "function", "name": "coins", "inputs": [{"name": "arg0", "type": "uint256"}], "outputs": [{"name": "", "type": "address"}], "gas": 1475}, {"stateMutability": "view", "type": "function", "name": "underlying_coins", "inputs": [{"name": "arg0", "type": "uint256"}], "outputs": [{"name": "", "type": "address"}], "gas": 1511}, {"stateMutability": "view", "type": "function", "name": "pool", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 1496}, {"stateMutability": "view", "type": "function", "name": "base_pool", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 1526}, {"stateMutability": "view", "type": "function", "name": "token", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 1556}] -------------------------------------------------------------------------------- /tests/twocrypto/forked_aave/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface 3 | from brownie_tokens import MintableForkToken 4 | 5 | VYPER_VERSION = "0.3.1" # Forced version, use None when brownie supports the new version 6 | 7 | 8 | BASE_COINS = [ 9 | "0x6B175474E89094C44Da98b954EedeAC495271d0F", # DAI 10 | "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC 11 | "0xdAC17F958D2ee523a2206206994597C13D831ec7" # USDT 12 | ] 13 | COINS = [ 14 | "0xC581b735A1688071A1746c968e0798D642EDE491", # EUR 15 | "0xFd2a8fA60Abd58Efe3EeE34dd494cD491dC14900" # a3crv* 16 | ] 17 | BASE_SWAP = "0xDeBF20617708857ebe4F679508E7b7863a8A8EeE" 18 | 19 | 20 | @pytest.fixture(scope="session") 21 | def alice(accounts): 22 | return accounts[0] 23 | 24 | 25 | @pytest.fixture(scope="session") 26 | def bob(accounts): 27 | return accounts[1] 28 | 29 | 30 | @pytest.fixture(scope="session") 31 | def charlie(accounts): 32 | return accounts[2] 33 | 34 | 35 | @pytest.fixture(scope="module") 36 | def coins(): 37 | return [MintableForkToken(addr) for addr in COINS] 38 | 39 | 40 | @pytest.fixture(scope="module") 41 | def base_coins(): 42 | return [MintableForkToken(addr) for addr in BASE_COINS] 43 | 44 | 45 | @pytest.fixture(scope="module") 46 | def underlying_coins(coins, base_coins): 47 | return [coins[0]] + base_coins 48 | 49 | 50 | @pytest.fixture(scope="module") 51 | def decimals(): 52 | return [6, 18] 53 | 54 | 55 | @pytest.fixture(scope="module") 56 | def base_decimals(): 57 | return [18, 6, 6] 58 | 59 | 60 | @pytest.fixture(scope="module", autouse=True) 61 | def crypto_swap(CurveCryptoSwap2, token, coins, alice): 62 | swap = CurveCryptoSwap2.deploy( 63 | alice, 64 | alice, 65 | 90 * 2**2 * 10000, # A 66 | int(2.8e-4 * 1e18), # gamma 67 | int(8.5e-5 * 1e10), # mid_fee 68 | int(1.3e-3 * 1e10), # out_fee 69 | 10**10, # allowed_extra_profit 70 | int(0.012 * 1e18), # fee_gamma 71 | int(0.55e-5 * 1e18), # adjustment_step 72 | 0, # admin_fee 73 | 600, # ma_half_time 74 | int(0.8 * 1e18), # price 75 | token, 76 | coins, 77 | {'from': alice}) 78 | token.set_minter(swap, {"from": alice}) 79 | 80 | return swap 81 | 82 | 83 | @pytest.fixture(scope="module", autouse=True) 84 | def stable_swap(): 85 | return interface.StableSwap3Pool(BASE_SWAP) 86 | 87 | 88 | @pytest.fixture(scope="module") 89 | def crypto_zap(alice, ZapTwoAave, crypto_swap): 90 | return ZapTwoAave.deploy( 91 | crypto_swap, BASE_SWAP, {"from": alice} 92 | ) 93 | 94 | 95 | @pytest.fixture(scope="module", autouse=True) 96 | def pre_mining(alice, crypto_zap, crypto_swap, coins, decimals, base_coins, base_decimals, charlie): 97 | """Mint a bunch of test tokens""" 98 | for c, d in zip(base_coins, base_decimals): 99 | c._mint_for_testing(alice, 100_000 * 10**d) 100 | c.approve(crypto_zap, 2**256 - 1, {'from': alice}) 101 | c._mint_for_testing(charlie, 100_000 * 10**d) 102 | c.approve(crypto_zap, 2**256 - 1, {'from': charlie}) 103 | 104 | coins[0]._mint_for_testing(alice, 300_000 * 10**decimals[0]) 105 | coins[0].approve(crypto_zap, 2**256 - 1, {'from': alice}) 106 | 107 | coins[0]._mint_for_testing(charlie, 300_000 * 10**decimals[0]) 108 | coins[0].approve(crypto_zap, 2**256 - 1, {'from': charlie}) 109 | 110 | crypto_zap.add_liquidity([ 111 | 240000 * 10**6, 100000 * 10**18, 100000 * 10**6, 100000 * 10**6 112 | ], 0, {'from': charlie}) 113 | -------------------------------------------------------------------------------- /tests/twocrypto/test_gas_realistic.py: -------------------------------------------------------------------------------- 1 | import pytest # noqa 2 | from .stateful_base import StatefulBase 3 | from brownie.test import strategy 4 | 5 | MAX_SAMPLES = 60 6 | STEP_COUNT = 30 7 | 8 | 9 | class StatefulGas(StatefulBase): 10 | exchange_amount_in = strategy('uint256', min_value=10 * 10**18, max_value=100 * 10**18) 11 | deposit_amount = strategy('uint256', min_value=10 * 10**18, max_value=100 * 10**18) 12 | token_fraction = strategy('uint256', min_value=10**14, max_value=5 * 10**16) 13 | sleep_time = strategy('uint256', max_value=100) 14 | update_D = strategy('bool') 15 | 16 | def rule_exchange(self, exchange_amount_in, exchange_i, user): 17 | if exchange_i > 0: 18 | exchange_amount_in = exchange_amount_in * 10**18 // self.swap.price_oracle() 19 | super().rule_exchange(exchange_amount_in, exchange_i, user) 20 | 21 | def rule_deposit(self, deposit_amount, exchange_i, user): 22 | amounts = [0] * 2 23 | if exchange_i > 0: 24 | amounts[exchange_i] = deposit_amount * 10**18 // self.swap.price_oracle() 25 | else: 26 | amounts[exchange_i] = deposit_amount 27 | new_balances = [x + y for x, y in zip(self.balances, amounts)] 28 | 29 | self.coins[exchange_i]._mint_for_testing(user, deposit_amount) 30 | 31 | try: 32 | tokens = self.token.balanceOf(user) 33 | self.swap.add_liquidity(amounts, 0, {'from': user}) 34 | tokens = self.token.balanceOf(user) - tokens 35 | self.total_supply += tokens 36 | self.balances = new_balances 37 | except Exception: 38 | if self.check_limits(amounts): 39 | raise 40 | 41 | def rule_remove_liquidity_one_coin(self, token_fraction, exchange_i, user, update_D): 42 | if update_D: 43 | self.swap.claim_admin_fees() 44 | 45 | token_amount = token_fraction * self.total_supply // 10**18 46 | d_token = self.token.balanceOf(user) 47 | if token_amount == 0 or token_amount > d_token: 48 | return 49 | 50 | try: 51 | calc_out_amount = self.swap.calc_withdraw_one_coin(token_amount, exchange_i) 52 | except Exception: 53 | if self.check_limits([0] * 2) and not (token_amount > self.total_supply): 54 | raise 55 | return 56 | 57 | d_balance = self.coins[exchange_i].balanceOf(user) 58 | try: 59 | self.swap.remove_liquidity_one_coin(token_amount, exchange_i, 0, {'from': user}) 60 | except Exception: 61 | # Small amounts may fail with rounding errors 62 | if calc_out_amount > 100 and\ 63 | token_amount / self.total_supply > 1e-10 and\ 64 | calc_out_amount / self.swap.balances(exchange_i) > 1e-10: 65 | raise 66 | return 67 | 68 | d_balance = self.coins[exchange_i].balanceOf(user) - d_balance 69 | d_token = d_token - self.token.balanceOf(user) 70 | 71 | if update_D: 72 | assert calc_out_amount == d_balance, f"{calc_out_amount} vs {d_balance} for {token_amount}" 73 | 74 | self.balances[exchange_i] -= d_balance 75 | self.total_supply -= d_token 76 | 77 | # Virtual price resets if everything is withdrawn 78 | if self.total_supply == 0: 79 | self.virtual_price = 10**18 80 | 81 | 82 | @pytest.mark.skip() 83 | def test_gas(crypto_swap, token, chain, accounts, coins, state_machine): 84 | from hypothesis._settings import HealthCheck 85 | 86 | state_machine(StatefulGas, chain, accounts, coins, crypto_swap, token, 87 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': STEP_COUNT, 'suppress_health_check': HealthCheck.all()}) 88 | -------------------------------------------------------------------------------- /tests/tricrypto/test_gas_realistic.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from .stateful_base import StatefulBase 3 | from brownie.test import strategy 4 | 5 | MAX_SAMPLES = 60 6 | STEP_COUNT = 100 7 | 8 | 9 | class StatefulGas(StatefulBase): 10 | exchange_amount_in = strategy('uint256', min_value=10 * 10**18, max_value=100 * 10**18) 11 | deposit_amount = strategy('uint256', min_value=10 * 10**18, max_value=100 * 10**18) 12 | token_fraction = strategy('uint256', min_value=10**14, max_value=5 * 10**16) 13 | sleep_time = strategy('uint256', max_value=100) 14 | update_D = strategy('bool') 15 | 16 | def rule_exchange(self, exchange_amount_in, exchange_i, exchange_j, user): 17 | if exchange_i > 0: 18 | exchange_amount_in = exchange_amount_in * 10**18 // self.swap.price_oracle(exchange_i-1) 19 | super().rule_exchange(exchange_amount_in, exchange_i, exchange_j, user) 20 | 21 | def rule_deposit(self, deposit_amount, exchange_i, user): 22 | amounts = [0] * 3 23 | if exchange_i > 0: 24 | amounts[exchange_i] = deposit_amount * 10**18 // self.swap.price_oracle(exchange_i - 1) 25 | else: 26 | amounts[exchange_i] = deposit_amount 27 | new_balances = [x + y for x, y in zip(self.balances, amounts)] 28 | 29 | self.coins[exchange_i]._mint_for_testing(user, deposit_amount) 30 | 31 | try: 32 | tokens = self.token.balanceOf(user) 33 | self.swap.add_liquidity(amounts, 0, {'from': user}) 34 | tokens = self.token.balanceOf(user) - tokens 35 | self.total_supply += tokens 36 | self.balances = new_balances 37 | except Exception: 38 | if self.check_limits(amounts): 39 | raise 40 | 41 | def rule_remove_liquidity_one_coin(self, token_fraction, exchange_i, user, update_D): 42 | if update_D: 43 | self.swap.claim_admin_fees() 44 | 45 | token_amount = token_fraction * self.total_supply // 10**18 46 | d_token = self.token.balanceOf(user) 47 | if token_amount == 0 or token_amount > d_token: 48 | return 49 | 50 | try: 51 | calc_out_amount = self.swap.calc_withdraw_one_coin(token_amount, exchange_i) 52 | except Exception: 53 | if self.check_limits([0] * 3) and not (token_amount > self.total_supply): 54 | raise 55 | return 56 | 57 | d_balance = self.coins[exchange_i].balanceOf(user) 58 | try: 59 | self.swap.remove_liquidity_one_coin(token_amount, exchange_i, 0, {'from': user}) 60 | except Exception: 61 | # Small amounts may fail with rounding errors 62 | if calc_out_amount > 100 and\ 63 | token_amount / self.total_supply > 1e-10 and\ 64 | calc_out_amount / self.swap.balances(exchange_i) > 1e-10: 65 | raise 66 | return 67 | 68 | d_balance = self.coins[exchange_i].balanceOf(user) - d_balance 69 | d_token = d_token - self.token.balanceOf(user) 70 | 71 | if update_D: 72 | assert calc_out_amount == d_balance, f"{calc_out_amount} vs {d_balance} for {token_amount}" 73 | 74 | self.balances[exchange_i] -= d_balance 75 | self.total_supply -= d_token 76 | 77 | # Virtual price resets if everything is withdrawn 78 | if self.total_supply == 0: 79 | self.virtual_price = 10**18 80 | 81 | 82 | @pytest.mark.skip() 83 | def test_gas(crypto_swap, token, chain, accounts, coins, state_machine): 84 | from hypothesis._settings import HealthCheck 85 | 86 | state_machine(StatefulGas, chain, accounts, coins, crypto_swap, token, 87 | settings={'max_examples': MAX_SAMPLES, 'stateful_step_count': STEP_COUNT, 'suppress_health_check': HealthCheck.all()}) 88 | -------------------------------------------------------------------------------- /deployment-logs/2021-12-22. XAUT/zap.json: -------------------------------------------------------------------------------- 1 | [{"inputs": [{"name": "pool", "type": "address"}, {"name": "base_pool", "type": "address"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"inputs": [{"name": "i", "type": "uint256"}], "name": "coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}], "name": "underlying_coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "base_pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "price_oracle", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "price_scale", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "lp_price", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[4]"}, {"name": "_min_mint_amount", "type": "uint256"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[4]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "name": "exchange_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "exchange_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[4]"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[4]"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "name": "get_dy_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[4]"}], "name": "calc_token_amount", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "name": "calc_withdraw_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2022-06-29. EUROC/zap.json: -------------------------------------------------------------------------------- 1 | [{"inputs": [{"name": "pool", "type": "address"}, {"name": "base_pool", "type": "address"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"inputs": [{"name": "i", "type": "uint256"}], "name": "coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}], "name": "underlying_coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "base_pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "price_oracle", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "price_scale", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "lp_price", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[4]"}, {"name": "_min_mint_amount", "type": "uint256"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[4]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "name": "exchange_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "exchange_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[4]"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[4]"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "name": "get_dy_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[4]"}], "name": "calc_token_amount", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "name": "calc_withdraw_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2021-10-31. EURT on mainnet/zap.json: -------------------------------------------------------------------------------- 1 | [{"stateMutability": "nonpayable", "type": "constructor", "inputs": [], "outputs": [], "name": "constructor"}, {"stateMutability": "view", "type": "function", "name": "coins", "inputs": [{"name": "i", "type": "uint256"}], "outputs": [{"name": "", "type": "address"}], "gas": 528}, {"stateMutability": "view", "type": "function", "name": "underlying_coins", "inputs": [{"name": "i", "type": "uint256"}], "outputs": [{"name": "", "type": "address"}], "gas": 582}, {"stateMutability": "view", "type": "function", "name": "pool", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 486}, {"stateMutability": "view", "type": "function", "name": "base_pool", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 516}, {"stateMutability": "view", "type": "function", "name": "token", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 546}, {"stateMutability": "view", "type": "function", "name": "price_oracle", "inputs": [], "outputs": [{"name": "", "type": "uint256"}], "gas": 2794}, {"stateMutability": "view", "type": "function", "name": "price_scale", "inputs": [], "outputs": [{"name": "", "type": "uint256"}], "gas": 2824}, {"stateMutability": "nonpayable", "type": "function", "name": "add_liquidity", "inputs": [{"name": "_amounts", "type": "uint256[4]"}, {"name": "_min_mint_amount", "type": "uint256"}], "outputs": [], "gas": 21671}, {"stateMutability": "nonpayable", "type": "function", "name": "add_liquidity", "inputs": [{"name": "_amounts", "type": "uint256[4]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": [], "gas": 21671}, {"stateMutability": "nonpayable", "type": "function", "name": "exchange_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 16932}, {"stateMutability": "nonpayable", "type": "function", "name": "exchange_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 16932}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity", "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[4]"}], "outputs": [], "gas": 27650}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity", "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[4]"}, {"name": "_receiver", "type": "address"}], "outputs": [], "gas": 27650}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity_one_coin", "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "outputs": [], "gas": 10245}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity_one_coin", "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": [], "gas": 10245}, {"stateMutability": "view", "type": "function", "name": "get_dy_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 6427}, {"stateMutability": "view", "type": "function", "name": "calc_token_amount", "inputs": [{"name": "_amounts", "type": "uint256[4]"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 3547}, {"stateMutability": "view", "type": "function", "name": "calc_withdraw_one_coin", "inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 4335}] -------------------------------------------------------------------------------- /scripts/deploy_fantom.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | CurveCryptoMath3, 5 | CurveTokenV4, 6 | CurveCryptoViews3, 7 | CurveCryptoSwapC, 8 | ERC20Mock, 9 | compile_source, 10 | ) 11 | from brownie import interface, network 12 | import json 13 | 14 | 15 | COINS = [ 16 | "0x049d68029688eAbF473097a2fC38ef61633A3C7A", # fUSDT 17 | "0x321162Cd933E2Be498Cd2267a90534A804051b11", # WBTC 18 | "0x74b23882a30290451A17c44f4F05243b6b58C76d" # WETH 19 | ] 20 | FEE_RECEIVER = "0x0000000000000000000000000000000000000000" 21 | 22 | if network.show_active() == 'ftm-main': 23 | print('Deploying on mainnet') 24 | accounts.load('babe') 25 | txparams = {"from": accounts[0], 'required_confs': 5} 26 | 27 | else: 28 | txparams = {"from": accounts[0]} 29 | 30 | 31 | def main(): 32 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd").json() 33 | INITIAL_PRICES = [int(p[cur]['usd'] * 1e18) for cur in ['bitcoin', 'ethereum']] 34 | 35 | crypto_math = CurveCryptoMath3.deploy(txparams) 36 | token = CurveTokenV4.deploy("Curve.fi USD-BTC-ETH", "crv3crypto", txparams) 37 | 38 | if COINS: 39 | coins = [interface.ERC20(addr) for addr in COINS] 40 | else: 41 | coins = [ 42 | ERC20Mock.deploy(name, name, 18, {"from": accounts[0]}) for name in ["USD", "BTC", "ETH"] 43 | ] 44 | 45 | source = CurveCryptoViews3._build["source"] 46 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 47 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 48 | source = source.replace("1,#2", str(10 ** (18 - coins[2].decimals())) + ',') 49 | with open("CryptoViews.vy", "w") as f: 50 | f.write(source) 51 | deployer = compile_source(source, vyper_version="0.2.15").Vyper 52 | crypto_views = deployer.deploy(crypto_math, txparams) 53 | 54 | source = CurveCryptoSwapC._build["source"] 55 | source = source.replace("0x0000000000000000000000000000000000000000", crypto_math.address) 56 | source = source.replace("0x0000000000000000000000000000000000000001", token.address) 57 | source = source.replace("0x0000000000000000000000000000000000000002", crypto_views.address) 58 | source = source.replace("0x0000000000000000000000000000000000000010", coins[0].address) 59 | source = source.replace("0x0000000000000000000000000000000000000011", coins[1].address) 60 | source = source.replace("0x0000000000000000000000000000000000000012", coins[2].address) 61 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 62 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 63 | source = source.replace("1,#2", str(10 ** (18 - coins[2].decimals())) + ',') 64 | with open("CryptoSwap.vy", "w") as f: 65 | f.write(source) 66 | deployer = compile_source(source, vyper_version="0.2.15").Vyper 67 | 68 | swap = deployer.deploy( 69 | accounts[0], 70 | FEE_RECEIVER, 71 | int(2 * 3 ** 3 * 10000), # A 72 | int(2.1e-5 * 1e18), # gamma 73 | int(1.1e-3 * 1e10), # mid_fee 74 | int(4.5e-3 * 1e10), # out_fee 75 | 2 * 10**12, # allowed_extra_profit 76 | int(5e-4 * 1e18), # fee_gamma 77 | int(0.00049 * 1e18), # adjustment_step 78 | 5 * 10**9, # admin_fee 79 | 600, # ma_half_time 80 | INITIAL_PRICES, 81 | txparams, 82 | ) 83 | token.set_minter(swap, txparams) 84 | 85 | print("Deployed at:") 86 | print("Swap:", swap.address) 87 | print("Token:", token.address) 88 | print("Math:", crypto_math.address) 89 | print("Views:", crypto_views.address) 90 | 91 | with open("swap.json", "w") as f: 92 | json.dump(swap.abi, f) 93 | 94 | with open("token.json", "w") as f: 95 | json.dump(token.abi, f) 96 | 97 | return swap, token 98 | -------------------------------------------------------------------------------- /deployment-logs/2021-07-13/CryptoViews.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.12 2 | # (c) Curve.Fi, 2021 3 | 4 | # This contract contains view-only external methods which can be gas-inefficient 5 | # when called from smart contracts but ok to use from frontend 6 | # Called only from Curve contract as it uses msg.sender as the contract address 7 | from vyper.interfaces import ERC20 8 | 9 | interface Curve: 10 | def A() -> uint256: view 11 | def gamma() -> uint256: view 12 | def price_scale(i: uint256) -> uint256: view 13 | def balances(i: uint256) -> uint256: view 14 | def D() -> uint256: view 15 | def fee_calc(xp: uint256[N_COINS]) -> uint256: view 16 | def calc_token_fee(amounts: uint256[N_COINS], xp: uint256[N_COINS]) -> uint256: view 17 | def token() -> address: view 18 | 19 | interface Math: 20 | def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS]) -> uint256: view 21 | def newton_y(ANN: uint256, gamma: uint256, x: uint256[N_COINS], D: uint256, i: uint256) -> uint256: view 22 | 23 | N_COINS: constant(int128) = 3 # <- change 24 | PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to 25 | PRECISIONS: constant(uint256[N_COINS]) = [ 26 | 1000000000000, 27 | 10000000000, 28 | 1, 29 | ] 30 | 31 | math: address 32 | 33 | 34 | @external 35 | def __init__(math: address): 36 | self.math = math 37 | 38 | 39 | @external 40 | @view 41 | def get_dy(i: uint256, j: uint256, dx: uint256) -> uint256: 42 | assert i != j and i < N_COINS and j < N_COINS, "coin index out of range" 43 | assert dx > 0, "do not exchange 0 coins" 44 | 45 | precisions: uint256[N_COINS] = PRECISIONS 46 | 47 | price_scale: uint256[N_COINS-1] = empty(uint256[N_COINS-1]) 48 | for k in range(N_COINS-1): 49 | price_scale[k] = Curve(msg.sender).price_scale(k) 50 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 51 | for k in range(N_COINS): 52 | xp[k] = Curve(msg.sender).balances(k) 53 | xp[i] += dx 54 | xp[0] *= precisions[0] 55 | for k in range(N_COINS-1): 56 | xp[k+1] = xp[k+1] * price_scale[k] * precisions[k+1] / PRECISION 57 | 58 | A: uint256 = Curve(msg.sender).A() 59 | gamma: uint256 = Curve(msg.sender).gamma() 60 | 61 | y: uint256 = Math(self.math).newton_y(A, gamma, xp, Curve(msg.sender).D(), j) 62 | dy: uint256 = xp[j] - y - 1 63 | xp[j] = y 64 | if j > 0: 65 | dy = dy * PRECISION / price_scale[j-1] 66 | dy /= precisions[j] 67 | dy -= Curve(msg.sender).fee_calc(xp) * dy / 10**10 68 | 69 | return dy 70 | 71 | 72 | @view 73 | @external 74 | def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256: 75 | precisions: uint256[N_COINS] = PRECISIONS 76 | token_supply: uint256 = ERC20(Curve(msg.sender).token()).totalSupply() 77 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 78 | for k in range(N_COINS): 79 | xp[k] = Curve(msg.sender).balances(k) 80 | amountsp: uint256[N_COINS] = amounts 81 | if deposit: 82 | for k in range(N_COINS): 83 | xp[k] += amounts[k] 84 | else: 85 | for k in range(N_COINS): 86 | xp[k] -= amounts[k] 87 | xp[0] *= precisions[0] 88 | amountsp[0] *= precisions[0] 89 | for k in range(N_COINS-1): 90 | p: uint256 = Curve(msg.sender).price_scale(k) * precisions[k+1] 91 | xp[k+1] = xp[k+1] * p / PRECISION 92 | amountsp[k+1] = amountsp[k+1] * p / PRECISION 93 | A: uint256 = Curve(msg.sender).A() 94 | gamma: uint256 = Curve(msg.sender).gamma() 95 | D: uint256 = Math(self.math).newton_D(A, gamma, xp) 96 | d_token: uint256 = token_supply * D / Curve(msg.sender).D() 97 | if deposit: 98 | d_token -= token_supply 99 | else: 100 | d_token = token_supply - d_token 101 | d_token -= Curve(msg.sender).calc_token_fee(amountsp, xp) * d_token / 10**10 + 1 102 | return d_token 103 | -------------------------------------------------------------------------------- /deployment-logs/2021-08-14. polygon/CryptoViews.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.15 2 | # (c) Curve.Fi, 2021 3 | 4 | # This contract contains view-only external methods which can be gas-inefficient 5 | # when called from smart contracts but ok to use from frontend 6 | # Called only from Curve contract as it uses msg.sender as the contract address 7 | from vyper.interfaces import ERC20 8 | 9 | interface Curve: 10 | def A() -> uint256: view 11 | def gamma() -> uint256: view 12 | def price_scale(i: uint256) -> uint256: view 13 | def balances(i: uint256) -> uint256: view 14 | def D() -> uint256: view 15 | def fee_calc(xp: uint256[N_COINS]) -> uint256: view 16 | def calc_token_fee(amounts: uint256[N_COINS], xp: uint256[N_COINS]) -> uint256: view 17 | def token() -> address: view 18 | 19 | interface Math: 20 | def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS]) -> uint256: view 21 | def newton_y(ANN: uint256, gamma: uint256, x: uint256[N_COINS], D: uint256, i: uint256) -> uint256: view 22 | 23 | N_COINS: constant(int128) = 3 # <- change 24 | PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to 25 | PRECISIONS: constant(uint256[N_COINS]) = [ 26 | 1, 27 | 10000000000, 28 | 1, 29 | ] 30 | 31 | math: address 32 | 33 | 34 | @external 35 | def __init__(math: address): 36 | self.math = math 37 | 38 | 39 | @external 40 | @view 41 | def get_dy(i: uint256, j: uint256, dx: uint256) -> uint256: 42 | assert i != j and i < N_COINS and j < N_COINS, "coin index out of range" 43 | assert dx > 0, "do not exchange 0 coins" 44 | 45 | precisions: uint256[N_COINS] = PRECISIONS 46 | 47 | price_scale: uint256[N_COINS-1] = empty(uint256[N_COINS-1]) 48 | for k in range(N_COINS-1): 49 | price_scale[k] = Curve(msg.sender).price_scale(k) 50 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 51 | for k in range(N_COINS): 52 | xp[k] = Curve(msg.sender).balances(k) 53 | xp[i] += dx 54 | xp[0] *= precisions[0] 55 | for k in range(N_COINS-1): 56 | xp[k+1] = xp[k+1] * price_scale[k] * precisions[k+1] / PRECISION 57 | 58 | A: uint256 = Curve(msg.sender).A() 59 | gamma: uint256 = Curve(msg.sender).gamma() 60 | 61 | y: uint256 = Math(self.math).newton_y(A, gamma, xp, Curve(msg.sender).D(), j) 62 | dy: uint256 = xp[j] - y - 1 63 | xp[j] = y 64 | if j > 0: 65 | dy = dy * PRECISION / price_scale[j-1] 66 | dy /= precisions[j] 67 | dy -= Curve(msg.sender).fee_calc(xp) * dy / 10**10 68 | 69 | return dy 70 | 71 | 72 | @view 73 | @external 74 | def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256: 75 | precisions: uint256[N_COINS] = PRECISIONS 76 | token_supply: uint256 = ERC20(Curve(msg.sender).token()).totalSupply() 77 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 78 | for k in range(N_COINS): 79 | xp[k] = Curve(msg.sender).balances(k) 80 | amountsp: uint256[N_COINS] = amounts 81 | if deposit: 82 | for k in range(N_COINS): 83 | xp[k] += amounts[k] 84 | else: 85 | for k in range(N_COINS): 86 | xp[k] -= amounts[k] 87 | xp[0] *= precisions[0] 88 | amountsp[0] *= precisions[0] 89 | for k in range(N_COINS-1): 90 | p: uint256 = Curve(msg.sender).price_scale(k) * precisions[k+1] 91 | xp[k+1] = xp[k+1] * p / PRECISION 92 | amountsp[k+1] = amountsp[k+1] * p / PRECISION 93 | A: uint256 = Curve(msg.sender).A() 94 | gamma: uint256 = Curve(msg.sender).gamma() 95 | D: uint256 = Math(self.math).newton_D(A, gamma, xp) 96 | d_token: uint256 = token_supply * D / Curve(msg.sender).D() 97 | if deposit: 98 | d_token -= token_supply 99 | else: 100 | d_token = token_supply - d_token 101 | d_token -= Curve(msg.sender).calc_token_fee(amountsp, xp) * d_token / 10**10 + 1 102 | return d_token 103 | -------------------------------------------------------------------------------- /deployment-logs/2021-08-27. Polygon redeployment/CryptoViews.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.15 2 | # (c) Curve.Fi, 2021 3 | 4 | # This contract contains view-only external methods which can be gas-inefficient 5 | # when called from smart contracts but ok to use from frontend 6 | # Called only from Curve contract as it uses msg.sender as the contract address 7 | from vyper.interfaces import ERC20 8 | 9 | interface Curve: 10 | def A() -> uint256: view 11 | def gamma() -> uint256: view 12 | def price_scale(i: uint256) -> uint256: view 13 | def balances(i: uint256) -> uint256: view 14 | def D() -> uint256: view 15 | def fee_calc(xp: uint256[N_COINS]) -> uint256: view 16 | def calc_token_fee(amounts: uint256[N_COINS], xp: uint256[N_COINS]) -> uint256: view 17 | def token() -> address: view 18 | 19 | interface Math: 20 | def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS]) -> uint256: view 21 | def newton_y(ANN: uint256, gamma: uint256, x: uint256[N_COINS], D: uint256, i: uint256) -> uint256: view 22 | 23 | N_COINS: constant(int128) = 3 # <- change 24 | PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to 25 | PRECISIONS: constant(uint256[N_COINS]) = [ 26 | 1, 27 | 10000000000, 28 | 1, 29 | ] 30 | 31 | math: address 32 | 33 | 34 | @external 35 | def __init__(math: address): 36 | self.math = math 37 | 38 | 39 | @external 40 | @view 41 | def get_dy(i: uint256, j: uint256, dx: uint256) -> uint256: 42 | assert i != j and i < N_COINS and j < N_COINS, "coin index out of range" 43 | assert dx > 0, "do not exchange 0 coins" 44 | 45 | precisions: uint256[N_COINS] = PRECISIONS 46 | 47 | price_scale: uint256[N_COINS-1] = empty(uint256[N_COINS-1]) 48 | for k in range(N_COINS-1): 49 | price_scale[k] = Curve(msg.sender).price_scale(k) 50 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 51 | for k in range(N_COINS): 52 | xp[k] = Curve(msg.sender).balances(k) 53 | xp[i] += dx 54 | xp[0] *= precisions[0] 55 | for k in range(N_COINS-1): 56 | xp[k+1] = xp[k+1] * price_scale[k] * precisions[k+1] / PRECISION 57 | 58 | A: uint256 = Curve(msg.sender).A() 59 | gamma: uint256 = Curve(msg.sender).gamma() 60 | 61 | y: uint256 = Math(self.math).newton_y(A, gamma, xp, Curve(msg.sender).D(), j) 62 | dy: uint256 = xp[j] - y - 1 63 | xp[j] = y 64 | if j > 0: 65 | dy = dy * PRECISION / price_scale[j-1] 66 | dy /= precisions[j] 67 | dy -= Curve(msg.sender).fee_calc(xp) * dy / 10**10 68 | 69 | return dy 70 | 71 | 72 | @view 73 | @external 74 | def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256: 75 | precisions: uint256[N_COINS] = PRECISIONS 76 | token_supply: uint256 = ERC20(Curve(msg.sender).token()).totalSupply() 77 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 78 | for k in range(N_COINS): 79 | xp[k] = Curve(msg.sender).balances(k) 80 | amountsp: uint256[N_COINS] = amounts 81 | if deposit: 82 | for k in range(N_COINS): 83 | xp[k] += amounts[k] 84 | else: 85 | for k in range(N_COINS): 86 | xp[k] -= amounts[k] 87 | xp[0] *= precisions[0] 88 | amountsp[0] *= precisions[0] 89 | for k in range(N_COINS-1): 90 | p: uint256 = Curve(msg.sender).price_scale(k) * precisions[k+1] 91 | xp[k+1] = xp[k+1] * p / PRECISION 92 | amountsp[k+1] = amountsp[k+1] * p / PRECISION 93 | A: uint256 = Curve(msg.sender).A() 94 | gamma: uint256 = Curve(msg.sender).gamma() 95 | D: uint256 = Math(self.math).newton_D(A, gamma, xp) 96 | d_token: uint256 = token_supply * D / Curve(msg.sender).D() 97 | if deposit: 98 | d_token -= token_supply 99 | else: 100 | d_token = token_supply - d_token 101 | d_token -= Curve(msg.sender).calc_token_fee(amountsp, xp) * d_token / 10**10 + 1 102 | return d_token 103 | -------------------------------------------------------------------------------- /deployment-logs/2021-10-30. EURS/zap.json: -------------------------------------------------------------------------------- 1 | [{"inputs": [{"name": "_pool", "type": "address"}, {"name": "_base_pool", "type": "address"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"gas": 4244, "inputs": [], "name": "price_oracle", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 4274, "inputs": [], "name": "price_scale", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 21320, "inputs": [{"name": "_amounts", "type": "uint256[3]"}, {"name": "_min_mint_amount", "type": "uint256"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 21320, "inputs": [{"name": "_amounts", "type": "uint256[3]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 23822, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "name": "exchange_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 23822, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "exchange_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 31724, "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[3]"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 31724, "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[3]"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 14811, "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 14811, "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 9456, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "name": "get_dy_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 4764, "inputs": [{"name": "_amounts", "type": "uint256[3]"}], "name": "calc_token_amount", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 6585, "inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "name": "calc_withdraw_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1535, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1571, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "underlying_coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1556, "inputs": [], "name": "pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1586, "inputs": [], "name": "base_pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1616, "inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2021-10-30. EURT/zap.json: -------------------------------------------------------------------------------- 1 | [{"stateMutability": "nonpayable", "type": "constructor", "inputs": [{"name": "_pool", "type": "address"}, {"name": "_base_pool", "type": "address"}], "outputs": [], "name": "constructor"}, {"stateMutability": "view", "type": "function", "name": "price_oracle", "inputs": [], "outputs": [{"name": "", "type": "uint256"}], "gas": 4244}, {"stateMutability": "view", "type": "function", "name": "price_scale", "inputs": [], "outputs": [{"name": "", "type": "uint256"}], "gas": 4274}, {"stateMutability": "nonpayable", "type": "function", "name": "add_liquidity", "inputs": [{"name": "_amounts", "type": "uint256[4]"}, {"name": "_min_mint_amount", "type": "uint256"}], "outputs": [], "gas": 26295}, {"stateMutability": "nonpayable", "type": "function", "name": "add_liquidity", "inputs": [{"name": "_amounts", "type": "uint256[4]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": [], "gas": 26295}, {"stateMutability": "nonpayable", "type": "function", "name": "exchange_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 20875}, {"stateMutability": "nonpayable", "type": "function", "name": "exchange_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 20875}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity", "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[4]"}], "outputs": [], "gas": 37116}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity", "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[4]"}, {"name": "_receiver", "type": "address"}], "outputs": [], "gas": 37116}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity_one_coin", "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "outputs": [], "gas": 13330}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_liquidity_one_coin", "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "outputs": [], "gas": 13330}, {"stateMutability": "view", "type": "function", "name": "get_dy_underlying", "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 9477}, {"stateMutability": "view", "type": "function", "name": "calc_token_amount", "inputs": [{"name": "_amounts", "type": "uint256[4]"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 4997}, {"stateMutability": "view", "type": "function", "name": "calc_withdraw_one_coin", "inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 6585}, {"stateMutability": "view", "type": "function", "name": "coins", "inputs": [{"name": "arg0", "type": "uint256"}], "outputs": [{"name": "", "type": "address"}], "gas": 1535}, {"stateMutability": "view", "type": "function", "name": "underlying_coins", "inputs": [{"name": "arg0", "type": "uint256"}], "outputs": [{"name": "", "type": "address"}], "gas": 1571}, {"stateMutability": "view", "type": "function", "name": "pool", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 1556}, {"stateMutability": "view", "type": "function", "name": "base_pool", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 1586}, {"stateMutability": "view", "type": "function", "name": "token", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 1616}] -------------------------------------------------------------------------------- /deployment-logs/2022-03-09. EURS-polygon/zap.json: -------------------------------------------------------------------------------- 1 | [{"inputs": [{"name": "_pool", "type": "address"}, {"name": "_base_pool", "type": "address"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"gas": 4056, "inputs": [], "name": "price_oracle", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 4086, "inputs": [], "name": "price_scale", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 22848, "inputs": [{"name": "_amounts", "type": "uint256[4]"}, {"name": "_min_mint_amount", "type": "uint256"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 22848, "inputs": [{"name": "_amounts", "type": "uint256[4]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 18583, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}], "name": "exchange_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 18583, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "exchange_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 32679, "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[4]"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 32679, "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[4]"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 12201, "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 12201, "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 9428, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "name": "get_dy_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 4809, "inputs": [{"name": "_amounts", "type": "uint256[4]"}], "name": "calc_token_amount", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 6494, "inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "name": "calc_withdraw_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1469, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1505, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "underlying_coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1490, "inputs": [], "name": "pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1520, "inputs": [], "name": "base_pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1550, "inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] -------------------------------------------------------------------------------- /scripts/deploy_harmony_metapool.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie import ( 3 | accounts, 4 | CurveCryptoMath3, 5 | CurveTokenV4, 6 | CurveCryptoViews3, 7 | CurveCryptoSwapHarmony, 8 | ZapHarmony, 9 | compile_source, 10 | ) 11 | from brownie import interface 12 | import json 13 | 14 | COINS = [ 15 | "0xC5cfaDA84E902aD92DD40194f0883ad49639b023", # h3Crv 16 | "0x3095c7557bcb296ccc6e363de01b760ba031f2d9", # hWBTC 17 | "0x6983d1e6def3690c4d616b13597a09e6193ea013" # hWETH 18 | ] 19 | SWAP = "0xC5cfaDA84E902aD92DD40194f0883ad49639b023" 20 | FEE_RECEIVER = "0x0000000000000000000000000000000000000000" 21 | 22 | 23 | def main(): 24 | accounts.load('babe') 25 | 26 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd").json() 27 | INITIAL_PRICES = [int(p[cur]['usd'] * 1e18) for cur in ['bitcoin', 'ethereum']] 28 | txparams = {"from": accounts[0], 'required_confs': 10} 29 | 30 | crypto_math = CurveCryptoMath3.deploy(txparams) 31 | token = CurveTokenV4.deploy("Curve USD-BTC-ETH", "crvUSDBTCETH", txparams) 32 | 33 | coins = [interface.ERC20(addr) for addr in COINS] 34 | vprice = interface.Swap(SWAP).get_virtual_price() 35 | INITIAL_PRICES = [p * 10**18 // vprice for p in INITIAL_PRICES] 36 | 37 | source = CurveCryptoViews3._build["source"] 38 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 39 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 40 | source = source.replace("1,#2", str(10 ** (18 - coins[2].decimals())) + ',') 41 | with open("CryptoViews.vy", "w") as f: 42 | f.write(source) 43 | deployer = compile_source(source, vyper_version="0.3.0").Vyper 44 | crypto_views = deployer.deploy(crypto_math, txparams) 45 | 46 | source = CurveCryptoSwapHarmony._build["source"] 47 | source = source.replace("0x0000000000000000000000000000000000000000", crypto_math.address) 48 | source = source.replace("0x0000000000000000000000000000000000000001", token.address) 49 | source = source.replace("0x0000000000000000000000000000000000000002", crypto_views.address) 50 | source = source.replace("0x0000000000000000000000000000000000000010", coins[0].address) 51 | source = source.replace("0x0000000000000000000000000000000000000011", coins[1].address) 52 | source = source.replace("0x0000000000000000000000000000000000000012", coins[2].address) 53 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 54 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 55 | source = source.replace("1,#2", str(10 ** (18 - coins[2].decimals())) + ',') 56 | with open("CryptoSwap.vy", "w") as f: 57 | f.write(source) 58 | deployer = compile_source(source, vyper_version="0.3.0").Vyper 59 | 60 | swap = deployer.deploy( 61 | accounts[0], 62 | FEE_RECEIVER, 63 | int(6.32 * 3 ** 3 * 10000), # A 64 | int(1.18e-5 * 1e18), # gamma 65 | int(0.5e-3 * 1e10), # mid_fee 66 | int(3e-3 * 1e10), # out_fee 67 | 2 * 10**12, # allowed_extra_profit 68 | int(5e-4 * 1e18), # fee_gamma 69 | int(0.001 * 1e18), # adjustment_step 70 | 5 * 10**9, # admin_fee 71 | 600, # ma_half_time 72 | INITIAL_PRICES, 73 | txparams, 74 | ) 75 | token.set_minter(swap, txparams) 76 | 77 | zap = ZapHarmony.deploy(swap.address, SWAP, txparams) 78 | 79 | print("Math address:", crypto_math.address) 80 | print("Views address:", crypto_views.address) 81 | 82 | print("Swap address:", swap.address) 83 | print("Token address:", token.address) 84 | print("Zap address:", zap.address) 85 | 86 | with open("swap.json", "w") as f: 87 | json.dump(swap.abi, f) 88 | 89 | with open("token.json", "w") as f: 90 | json.dump(token.abi, f) 91 | 92 | with open("zap.json", "w") as f: 93 | json.dump(zap.abi, f) 94 | 95 | return swap, token, zap 96 | -------------------------------------------------------------------------------- /deployment-logs/2021-05-24/CurveCryptoViews3.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.12 2 | # (c) Curve.Fi, 2021 3 | 4 | # This contract contains view-only external methods which can be gas-inefficient 5 | # when called from smart contracts but ok to use from frontend 6 | # Called only from Curve contract as it uses msg.sender as the contract address 7 | from vyper.interfaces import ERC20 8 | 9 | interface Curve: 10 | def A_precise() -> uint256: view 11 | def gamma() -> uint256: view 12 | def price_scale(i: uint256) -> uint256: view 13 | def balances(i: uint256) -> uint256: view 14 | def D() -> uint256: view 15 | def fee_calc(xp: uint256[N_COINS]) -> uint256: view 16 | def calc_token_fee(amounts: uint256[N_COINS], xp: uint256[N_COINS]) -> uint256: view 17 | def token() -> address: view 18 | 19 | interface Math: 20 | def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS]) -> uint256: view 21 | def newton_y(ANN: uint256, gamma: uint256, x: uint256[N_COINS], D: uint256, i: uint256) -> uint256: view 22 | 23 | N_COINS: constant(int128) = 3 # <- change 24 | PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to 25 | PRECISIONS: constant(uint256[N_COINS]) = [ 26 | 1, # a3crv 27 | 10**10, # WBTC 28 | 1, # WETH 29 | ] 30 | 31 | math: address 32 | 33 | 34 | @external 35 | def __init__(math: address): 36 | self.math = math 37 | 38 | 39 | @external 40 | @view 41 | def get_dy(i: uint256, j: uint256, dx: uint256) -> uint256: 42 | assert i != j and i < N_COINS and j < N_COINS, "coin index out of range" 43 | assert dx > 0, "do not exchange 0 coins" 44 | 45 | precisions: uint256[N_COINS] = PRECISIONS 46 | 47 | price_scale: uint256[N_COINS-1] = empty(uint256[N_COINS-1]) 48 | for k in range(N_COINS-1): 49 | price_scale[k] = Curve(msg.sender).price_scale(k) 50 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 51 | for k in range(N_COINS): 52 | xp[k] = Curve(msg.sender).balances(k) 53 | y0: uint256 = xp[j] 54 | xp[i] += dx 55 | xp[0] *= precisions[0] 56 | for k in range(N_COINS-1): 57 | xp[k+1] = xp[k+1] * price_scale[k] * precisions[k+1] / PRECISION 58 | 59 | A: uint256 = Curve(msg.sender).A_precise() 60 | gamma: uint256 = Curve(msg.sender).gamma() 61 | 62 | y: uint256 = Math(self.math).newton_y(A, gamma, xp, Curve(msg.sender).D(), j) 63 | dy: uint256 = xp[j] - y - 1 64 | xp[j] = y 65 | if j > 0: 66 | dy = dy * PRECISION / price_scale[j-1] 67 | dy /= precisions[j] 68 | dy -= Curve(msg.sender).fee_calc(xp) * dy / 10**10 69 | 70 | return dy 71 | 72 | 73 | @view 74 | @external 75 | def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256: 76 | precisions: uint256[N_COINS] = PRECISIONS 77 | token_supply: uint256 = ERC20(Curve(msg.sender).token()).totalSupply() 78 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 79 | for k in range(N_COINS): 80 | xp[k] = Curve(msg.sender).balances(k) 81 | amountsp: uint256[N_COINS] = amounts 82 | if deposit: 83 | for k in range(N_COINS): 84 | xp[k] += amounts[k] 85 | else: 86 | for k in range(N_COINS): 87 | xp[k] -= amounts[k] 88 | xp[0] *= precisions[0] 89 | amountsp[0] *= precisions[0] 90 | for k in range(N_COINS-1): 91 | p: uint256 = Curve(msg.sender).price_scale(k) * precisions[k+1] 92 | xp[k+1] = xp[k+1] * p / PRECISION 93 | amountsp[k+1] = amountsp[k+1] * p / PRECISION 94 | A: uint256 = Curve(msg.sender).A_precise() 95 | gamma: uint256 = Curve(msg.sender).gamma() 96 | D: uint256 = Math(self.math).newton_D(A, gamma, xp) 97 | d_token: uint256 = token_supply * D / Curve(msg.sender).D() 98 | if deposit: 99 | d_token -= token_supply 100 | else: 101 | d_token = token_supply - d_token 102 | d_token -= Curve(msg.sender).calc_token_fee(amountsp, xp) * d_token / 10**10 + 1 103 | return d_token 104 | -------------------------------------------------------------------------------- /deployment-logs/2021-05-28/CurveCryptoViews3.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.12 2 | # (c) Curve.Fi, 2021 3 | 4 | # This contract contains view-only external methods which can be gas-inefficient 5 | # when called from smart contracts but ok to use from frontend 6 | # Called only from Curve contract as it uses msg.sender as the contract address 7 | from vyper.interfaces import ERC20 8 | 9 | interface Curve: 10 | def A_precise() -> uint256: view 11 | def gamma() -> uint256: view 12 | def price_scale(i: uint256) -> uint256: view 13 | def balances(i: uint256) -> uint256: view 14 | def D() -> uint256: view 15 | def fee_calc(xp: uint256[N_COINS]) -> uint256: view 16 | def calc_token_fee(amounts: uint256[N_COINS], xp: uint256[N_COINS]) -> uint256: view 17 | def token() -> address: view 18 | 19 | interface Math: 20 | def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS]) -> uint256: view 21 | def newton_y(ANN: uint256, gamma: uint256, x: uint256[N_COINS], D: uint256, i: uint256) -> uint256: view 22 | 23 | N_COINS: constant(int128) = 3 # <- change 24 | PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to 25 | PRECISIONS: constant(uint256[N_COINS]) = [ 26 | 10**12, # USDT 27 | 10**10, # WBTC 28 | 1, # WETH 29 | ] 30 | 31 | math: address 32 | 33 | 34 | @external 35 | def __init__(math: address): 36 | self.math = math 37 | 38 | 39 | @external 40 | @view 41 | def get_dy(i: uint256, j: uint256, dx: uint256) -> uint256: 42 | assert i != j and i < N_COINS and j < N_COINS, "coin index out of range" 43 | assert dx > 0, "do not exchange 0 coins" 44 | 45 | precisions: uint256[N_COINS] = PRECISIONS 46 | 47 | price_scale: uint256[N_COINS-1] = empty(uint256[N_COINS-1]) 48 | for k in range(N_COINS-1): 49 | price_scale[k] = Curve(msg.sender).price_scale(k) 50 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 51 | for k in range(N_COINS): 52 | xp[k] = Curve(msg.sender).balances(k) 53 | y0: uint256 = xp[j] 54 | xp[i] += dx 55 | xp[0] *= precisions[0] 56 | for k in range(N_COINS-1): 57 | xp[k+1] = xp[k+1] * price_scale[k] * precisions[k+1] / PRECISION 58 | 59 | A: uint256 = Curve(msg.sender).A_precise() 60 | gamma: uint256 = Curve(msg.sender).gamma() 61 | 62 | y: uint256 = Math(self.math).newton_y(A, gamma, xp, Curve(msg.sender).D(), j) 63 | dy: uint256 = xp[j] - y - 1 64 | xp[j] = y 65 | if j > 0: 66 | dy = dy * PRECISION / price_scale[j-1] 67 | dy /= precisions[j] 68 | dy -= Curve(msg.sender).fee_calc(xp) * dy / 10**10 69 | 70 | return dy 71 | 72 | 73 | @view 74 | @external 75 | def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256: 76 | precisions: uint256[N_COINS] = PRECISIONS 77 | token_supply: uint256 = ERC20(Curve(msg.sender).token()).totalSupply() 78 | xp: uint256[N_COINS] = empty(uint256[N_COINS]) 79 | for k in range(N_COINS): 80 | xp[k] = Curve(msg.sender).balances(k) 81 | amountsp: uint256[N_COINS] = amounts 82 | if deposit: 83 | for k in range(N_COINS): 84 | xp[k] += amounts[k] 85 | else: 86 | for k in range(N_COINS): 87 | xp[k] -= amounts[k] 88 | xp[0] *= precisions[0] 89 | amountsp[0] *= precisions[0] 90 | for k in range(N_COINS-1): 91 | p: uint256 = Curve(msg.sender).price_scale(k) * precisions[k+1] 92 | xp[k+1] = xp[k+1] * p / PRECISION 93 | amountsp[k+1] = amountsp[k+1] * p / PRECISION 94 | A: uint256 = Curve(msg.sender).A_precise() 95 | gamma: uint256 = Curve(msg.sender).gamma() 96 | D: uint256 = Math(self.math).newton_D(A, gamma, xp) 97 | d_token: uint256 = token_supply * D / Curve(msg.sender).D() 98 | if deposit: 99 | d_token -= token_supply 100 | else: 101 | d_token = token_supply - d_token 102 | d_token -= Curve(msg.sender).calc_token_fee(amountsp, xp) * d_token / 10**10 + 1 103 | return d_token 104 | -------------------------------------------------------------------------------- /scripts/deploy.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from brownie.network.gas.strategies import GasNowScalingStrategy 3 | from brownie import ( 4 | accounts, 5 | CurveCryptoMath3, 6 | CurveTokenV4, 7 | CurveCryptoViews3, 8 | CurveCryptoSwap, 9 | ERC20Mock, 10 | compile_source, 11 | ) 12 | from brownie import interface, network 13 | import json 14 | 15 | 16 | COINS = [ 17 | "0xdAC17F958D2ee523a2206206994597C13D831ec7", # USDT 18 | "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", # WBTC 19 | "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" # WETH 20 | ] 21 | FEE_RECEIVER = "0xeCb456EA5365865EbAb8a2661B0c503410e9B347" 22 | 23 | if network.show_active() == 'mainnet': 24 | print('Deploying on mainnet') 25 | accounts.load('babe') 26 | txparams = {"from": accounts[0], 'required_confs': 5} 27 | try: 28 | network.gas_price(GasNowScalingStrategy("slow", "fast")) 29 | except ConnectionError: 30 | pass 31 | 32 | else: 33 | txparams = {"from": accounts[0]} 34 | 35 | 36 | def main(): 37 | p = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd").json() 38 | INITIAL_PRICES = [int(p[cur]['usd'] * 1e18) for cur in ['bitcoin', 'ethereum']] 39 | 40 | crypto_math = CurveCryptoMath3.deploy(txparams) 41 | token = CurveTokenV4.deploy("Curve.fi USD-BTC-ETH", "crv3crypto", txparams) 42 | 43 | if COINS: 44 | coins = [interface.ERC20(addr) for addr in COINS] 45 | else: 46 | coins = [ 47 | ERC20Mock.deploy(name, name, 18, {"from": accounts[0]}) for name in ["USD", "BTC", "ETH"] 48 | ] 49 | 50 | source = CurveCryptoViews3._build["source"] 51 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 52 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 53 | source = source.replace("1,#2", str(10 ** (18 - coins[2].decimals())) + ',') 54 | with open("CryptoViews.vy", "w") as f: 55 | f.write(source) 56 | deployer = compile_source(source, vyper_version="0.2.12").Vyper 57 | crypto_views = deployer.deploy(crypto_math, txparams) 58 | 59 | source = CurveCryptoSwap._build["source"] 60 | source = source.replace("0x0000000000000000000000000000000000000000", crypto_math.address) 61 | source = source.replace("0x0000000000000000000000000000000000000001", token.address) 62 | source = source.replace("0x0000000000000000000000000000000000000002", crypto_views.address) 63 | source = source.replace("0x0000000000000000000000000000000000000010", coins[0].address) 64 | source = source.replace("0x0000000000000000000000000000000000000011", coins[1].address) 65 | source = source.replace("0x0000000000000000000000000000000000000012", coins[2].address) 66 | source = source.replace("1,#0", str(10 ** (18 - coins[0].decimals())) + ',') 67 | source = source.replace("1,#1", str(10 ** (18 - coins[1].decimals())) + ',') 68 | source = source.replace("1,#2", str(10 ** (18 - coins[2].decimals())) + ',') 69 | with open("CryptoSwap.vy", "w") as f: 70 | f.write(source) 71 | deployer = compile_source(source, vyper_version="0.2.12").Vyper 72 | 73 | swap = deployer.deploy( 74 | accounts[0], 75 | FEE_RECEIVER, 76 | int(0.2 * 3 ** 3 * 10000), # A 77 | int(3.5e-3 * 1e18), # gamma 78 | int(1.1e-3 * 1e10), # mid_fee 79 | int(4.5e-3 * 1e10), # out_fee 80 | 2 * 10**12, # allowed_extra_profit 81 | int(5e-4 * 1e18), # fee_gamma 82 | int(0.00049 * 1e18), # adjustment_step 83 | 5 * 10**9, # admin_fee 84 | 600, # ma_half_time 85 | INITIAL_PRICES, 86 | txparams, 87 | ) 88 | token.set_minter(swap, txparams) 89 | 90 | print("Deployed at:") 91 | print("Swap:", swap.address) 92 | print("Token:", token.address) 93 | print("Math:", crypto_math.address) 94 | print("Views:", crypto_views.address) 95 | 96 | with open("swap.json", "w") as f: 97 | json.dump(swap.abi, f) 98 | 99 | with open("token.json", "w") as f: 100 | json.dump(token.abi, f) 101 | 102 | return swap, token 103 | -------------------------------------------------------------------------------- /deployment-logs/2021-12-11. CVX/CurveTokenV4.abi: -------------------------------------------------------------------------------- 1 | [{"name": "Transfer", "inputs": [{"name": "_from", "type": "address", "indexed": true}, {"name": "_to", "type": "address", "indexed": true}, {"name": "_value", "type": "uint256", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "Approval", "inputs": [{"name": "_owner", "type": "address", "indexed": true}, {"name": "_spender", "type": "address", "indexed": true}, {"name": "_value", "type": "uint256", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "SetName", "inputs": [{"name": "old_name", "type": "string", "indexed": false}, {"name": "old_symbol", "type": "string", "indexed": false}, {"name": "name", "type": "string", "indexed": false}, {"name": "symbol", "type": "string", "indexed": false}, {"name": "owner", "type": "address", "indexed": false}, {"name": "time", "type": "uint256", "indexed": false}], "anonymous": false, "type": "event"}, {"stateMutability": "nonpayable", "type": "constructor", "inputs": [{"name": "_name", "type": "string"}, {"name": "_symbol", "type": "string"}], "outputs": []}, {"stateMutability": "view", "type": "function", "name": "decimals", "inputs": [], "outputs": [{"name": "", "type": "uint256"}], "gas": 360}, {"stateMutability": "nonpayable", "type": "function", "name": "transfer", "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "outputs": [{"name": "", "type": "bool"}], "gas": 78662}, {"stateMutability": "nonpayable", "type": "function", "name": "transferFrom", "inputs": [{"name": "_from", "type": "address"}, {"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "outputs": [{"name": "", "type": "bool"}], "gas": 116646}, {"stateMutability": "nonpayable", "type": "function", "name": "approve", "inputs": [{"name": "_spender", "type": "address"}, {"name": "_value", "type": "uint256"}], "outputs": [{"name": "", "type": "bool"}], "gas": 39181}, {"stateMutability": "nonpayable", "type": "function", "name": "increaseAllowance", "inputs": [{"name": "_spender", "type": "address"}, {"name": "_added_value", "type": "uint256"}], "outputs": [{"name": "", "type": "bool"}], "gas": 41711}, {"stateMutability": "nonpayable", "type": "function", "name": "decreaseAllowance", "inputs": [{"name": "_spender", "type": "address"}, {"name": "_subtracted_value", "type": "uint256"}], "outputs": [{"name": "", "type": "bool"}], "gas": 41737}, {"stateMutability": "nonpayable", "type": "function", "name": "mint", "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "outputs": [{"name": "", "type": "bool"}], "gas": 80902}, {"stateMutability": "nonpayable", "type": "function", "name": "mint_relative", "inputs": [{"name": "_to", "type": "address"}, {"name": "frac", "type": "uint256"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 81224}, {"stateMutability": "nonpayable", "type": "function", "name": "burnFrom", "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "outputs": [{"name": "", "type": "bool"}], "gas": 80954}, {"stateMutability": "nonpayable", "type": "function", "name": "set_minter", "inputs": [{"name": "_minter", "type": "address"}], "outputs": [], "gas": 37875}, {"stateMutability": "nonpayable", "type": "function", "name": "set_name", "inputs": [{"name": "_name", "type": "string"}, {"name": "_symbol", "type": "string"}], "outputs": [], "gas": 231779}, {"stateMutability": "view", "type": "function", "name": "name", "inputs": [], "outputs": [{"name": "", "type": "string"}], "gas": 13079}, {"stateMutability": "view", "type": "function", "name": "symbol", "inputs": [], "outputs": [{"name": "", "type": "string"}], "gas": 10838}, {"stateMutability": "view", "type": "function", "name": "balanceOf", "inputs": [{"name": "arg0", "type": "address"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 3116}, {"stateMutability": "view", "type": "function", "name": "allowance", "inputs": [{"name": "arg0", "type": "address"}, {"name": "arg1", "type": "address"}], "outputs": [{"name": "", "type": "uint256"}], "gas": 3412}, {"stateMutability": "view", "type": "function", "name": "totalSupply", "inputs": [], "outputs": [{"name": "", "type": "uint256"}], "gas": 2910}, {"stateMutability": "view", "type": "function", "name": "minter", "inputs": [], "outputs": [{"name": "", "type": "address"}], "gas": 2940}] 2 | -------------------------------------------------------------------------------- /deployment-logs/2021-05-24/token.json: -------------------------------------------------------------------------------- 1 | [{"anonymous": false, "inputs": [{"indexed": true, "name": "_from", "type": "address"}, {"indexed": true, "name": "_to", "type": "address"}, {"indexed": false, "name": "_value", "type": "uint256"}], "name": "Transfer", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "_owner", "type": "address"}, {"indexed": true, "name": "_spender", "type": "address"}, {"indexed": false, "name": "_value", "type": "uint256"}], "name": "Approval", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "name": "old_name", "type": "string"}, {"indexed": false, "name": "old_symbol", "type": "string"}, {"indexed": false, "name": "name", "type": "string"}, {"indexed": false, "name": "symbol", "type": "string"}, {"indexed": false, "name": "owner", "type": "address"}, {"indexed": false, "name": "time", "type": "uint256"}], "name": "SetName", "type": "event"}, {"inputs": [{"name": "_name", "type": "string"}, {"name": "_symbol", "type": "string"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"gas": 288, "inputs": [], "name": "decimals", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 74740, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transfer", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 111382, "inputs": [{"name": "_from", "type": "address"}, {"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transferFrom", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 37821, "inputs": [{"name": "_spender", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "approve", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 39065, "inputs": [{"name": "_spender", "type": "address"}, {"name": "_added_value", "type": "uint256"}], "name": "increaseAllowance", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 39089, "inputs": [{"name": "_spender", "type": "address"}, {"name": "_subtracted_value", "type": "uint256"}], "name": "decreaseAllowance", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 75679, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "mint", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 76083, "inputs": [{"name": "_to", "type": "address"}, {"name": "frac", "type": "uint256"}], "name": "mint_relative", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 75727, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "burnFrom", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 36515, "inputs": [{"name": "_minter", "type": "address"}], "name": "set_minter", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 208602, "inputs": [{"name": "_name", "type": "string"}, {"name": "_symbol", "type": "string"}], "name": "set_name", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 7820, "inputs": [], "name": "name", "outputs": [{"name": "", "type": "string"}], "stateMutability": "view", "type": "function"}, {"gas": 6873, "inputs": [], "name": "symbol", "outputs": [{"name": "", "type": "string"}], "stateMutability": "view", "type": "function"}, {"gas": 1693, "inputs": [{"name": "arg0", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1938, "inputs": [{"name": "arg0", "type": "address"}, {"name": "arg1", "type": "address"}], "name": "allowance", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1538, "inputs": [], "name": "totalSupply", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1568, "inputs": [], "name": "minter", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] -------------------------------------------------------------------------------- /deployment-logs/2021-05-28/token.json: -------------------------------------------------------------------------------- 1 | [{"anonymous": false, "inputs": [{"indexed": true, "name": "_from", "type": "address"}, {"indexed": true, "name": "_to", "type": "address"}, {"indexed": false, "name": "_value", "type": "uint256"}], "name": "Transfer", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "_owner", "type": "address"}, {"indexed": true, "name": "_spender", "type": "address"}, {"indexed": false, "name": "_value", "type": "uint256"}], "name": "Approval", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "name": "old_name", "type": "string"}, {"indexed": false, "name": "old_symbol", "type": "string"}, {"indexed": false, "name": "name", "type": "string"}, {"indexed": false, "name": "symbol", "type": "string"}, {"indexed": false, "name": "owner", "type": "address"}, {"indexed": false, "name": "time", "type": "uint256"}], "name": "SetName", "type": "event"}, {"inputs": [{"name": "_name", "type": "string"}, {"name": "_symbol", "type": "string"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"gas": 288, "inputs": [], "name": "decimals", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 74740, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transfer", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 111382, "inputs": [{"name": "_from", "type": "address"}, {"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transferFrom", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 37821, "inputs": [{"name": "_spender", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "approve", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 39065, "inputs": [{"name": "_spender", "type": "address"}, {"name": "_added_value", "type": "uint256"}], "name": "increaseAllowance", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 39089, "inputs": [{"name": "_spender", "type": "address"}, {"name": "_subtracted_value", "type": "uint256"}], "name": "decreaseAllowance", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 75679, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "mint", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 76083, "inputs": [{"name": "_to", "type": "address"}, {"name": "frac", "type": "uint256"}], "name": "mint_relative", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 75727, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "burnFrom", "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"gas": 36515, "inputs": [{"name": "_minter", "type": "address"}], "name": "set_minter", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 208602, "inputs": [{"name": "_name", "type": "string"}, {"name": "_symbol", "type": "string"}], "name": "set_name", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 7820, "inputs": [], "name": "name", "outputs": [{"name": "", "type": "string"}], "stateMutability": "view", "type": "function"}, {"gas": 6873, "inputs": [], "name": "symbol", "outputs": [{"name": "", "type": "string"}], "stateMutability": "view", "type": "function"}, {"gas": 1693, "inputs": [{"name": "arg0", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1938, "inputs": [{"name": "arg0", "type": "address"}, {"name": "arg1", "type": "address"}], "name": "allowance", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1538, "inputs": [], "name": "totalSupply", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1568, "inputs": [], "name": "minter", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] --------------------------------------------------------------------------------