diff --git a/.gitignore b/.gitignore index dd5f792..d7ae498 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules/ coverage/ coverage.json build/ +.vscode +.idea diff --git a/OneSplit.full.abi b/OneSplit.full.abi index 7309c9e..4d8e3b8 100644 --- a/OneSplit.full.abi +++ b/OneSplit.full.abi @@ -1 +1 @@ -[{"inputs":[{"internalType":"contract IOneSplitView","name":"_oneSplitView","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"constant":true,"inputs":[],"name":"DEXES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ETH_ADDRESS","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_AAVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_ALL_SPLIT_SOURCES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_ALL_WRAP_SOURCES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_BANCOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_BDAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CHAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_COMPOUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_BINANCE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_COMPOUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_PAX","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_SYNTHETIX","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_USDT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_Y","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_FULCRUM","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_IDLE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_IEARN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_KYBER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_MOONISWAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_OASIS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_SMART_TOKEN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2_ALL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2_DAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2_ETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2_USDC","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_WETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_KYBER_BANCOR_RESERVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_KYBER_OASIS_RESERVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_KYBER_UNISWAP_RESERVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_MULTI_PATH_DAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_MULTI_PATH_ETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_MULTI_PATH_USDC","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_UNISWAP_AAVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_UNISWAP_CHAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_UNISWAP_COMPOUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"aave","outputs":[{"internalType":"contract IAaveLendingPool","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"bancorContractRegistry","outputs":[{"internalType":"contract IBancorContractRegistry","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"bancorConverterRegistry","outputs":[{"internalType":"contract IBancorConverterRegistry","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"bancorEtherToken","outputs":[{"internalType":"contract IBancorEtherToken","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"bnt","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"busd","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cETH","outputs":[{"internalType":"contract ICompoundEther","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"chai","outputs":[{"internalType":"contract IChai","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"compound","outputs":[{"internalType":"contract ICompound","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curveBinance","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curveCompound","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curvePax","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curveSynthetix","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curveUsdt","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curveY","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"dai","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"contract IERC20","name":"fromToken","type":"address"},{"internalType":"contract IERC20","name":"toToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"parts","type":"uint256"},{"internalType":"uint256","name":"flags","type":"uint256"}],"name":"getExpectedReturn","outputs":[{"internalType":"uint256","name":"returnAmount","type":"uint256"},{"internalType":"uint256[]","name":"distribution","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kyberNetworkProxy","outputs":[{"internalType":"contract IKyberNetworkProxy","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"mooniswapRegistry","outputs":[{"internalType":"contract IMooniswapRegistry","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"oasisExchange","outputs":[{"internalType":"contract IOasisExchange","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"oneSplitView","outputs":[{"internalType":"contract IOneSplitView","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pax","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"susd","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"contract IERC20","name":"fromToken","type":"address"},{"internalType":"contract IERC20","name":"toToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256[]","name":"distribution","type":"uint256[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"swap","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"tusd","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"uniswapFactory","outputs":[{"internalType":"contract IUniswapFactory","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"uniswapV2","outputs":[{"internalType":"contract IUniswapV2Factory","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"usdc","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"usdt","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"weth","outputs":[{"internalType":"contract IWETH","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"contract IOneSplitView","name":"_oneSplitView","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"constant":true,"inputs":[],"name":"DEXES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ETH_ADDRESS","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_AAVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_ALL_SPLIT_SOURCES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_ALL_WRAP_SOURCES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_BALANCER_POOL_TOKEN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_BANCOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_BDAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CHAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_COMPOUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_BINANCE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_COMPOUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_PAX","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_SYNTHETIX","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_USDT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_Y","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_ZAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_FULCRUM","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_IDLE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_IEARN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_KYBER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_MOONISWAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_OASIS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_SMART_TOKEN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_POOL_TOKEN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2_ALL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2_DAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2_ETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP_V2_USDC","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_WETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_KYBER_BANCOR_RESERVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_KYBER_OASIS_RESERVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_KYBER_UNISWAP_RESERVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_MULTI_PATH_DAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_MULTI_PATH_ETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_MULTI_PATH_USDC","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_UNISWAP_AAVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_UNISWAP_CHAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_UNISWAP_COMPOUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"aave","outputs":[{"internalType":"contract IAaveLendingPool","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"bancorContractRegistry","outputs":[{"internalType":"contract IBancorContractRegistry","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"bancorConverterRegistry","outputs":[{"internalType":"contract IBancorConverterRegistry","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"bancorEtherToken","outputs":[{"internalType":"contract IBancorEtherToken","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"bnt","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"busd","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cETH","outputs":[{"internalType":"contract ICompoundEther","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"chai","outputs":[{"internalType":"contract IChai","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"compound","outputs":[{"internalType":"contract ICompound","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curveBinance","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curveCompound","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curvePax","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curveSynthetix","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curveUsdt","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"curveY","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"dai","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"contract IERC20","name":"fromToken","type":"address"},{"internalType":"contract IERC20","name":"toToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"parts","type":"uint256"},{"internalType":"uint256","name":"flags","type":"uint256"}],"name":"getExpectedReturn","outputs":[{"internalType":"uint256","name":"returnAmount","type":"uint256"},{"internalType":"uint256[]","name":"distribution","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kyberNetworkProxy","outputs":[{"internalType":"contract IKyberNetworkProxy","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"mooniswapRegistry","outputs":[{"internalType":"contract IMooniswapRegistry","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"oasisExchange","outputs":[{"internalType":"contract IOasisExchange","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"oneSplitView","outputs":[{"internalType":"contract IOneSplitView","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pax","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"susd","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"contract IERC20","name":"fromToken","type":"address"},{"internalType":"contract IERC20","name":"toToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256[]","name":"distribution","type":"uint256[]"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"swap","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"tusd","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"uniswapFactory","outputs":[{"internalType":"contract IUniswapFactory","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"uniswapV2","outputs":[{"internalType":"contract IUniswapV2Factory","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"usdc","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"usdt","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"weth","outputs":[{"internalType":"contract IWETH","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/OneSplit.full.bin b/OneSplit.full.bin index 4b02744..b21ac04 100644 --- a/OneSplit.full.bin +++ b/OneSplit.full.bin @@ -1 +1 @@ -608060405234801561001057600080fd5b5060405162004a2f38038062004a2f8339818101604052602081101561003557600080fd5b5051600080546001600160a01b039092166001600160a01b03199092169190911790556149c780620000686000396000f3fe6080604052600436106103ef5760003560e01c80637e09b9c211610208578063c989b66711610118578063dc1536b2116100ab578063f4b9fa751161007a578063f4b9fa7514610aa1578063f56e281f14610ab6578063f69e204614610acb578063fa3f110b14610ae0578063fbe4ed9514610af5576103ef565b8063dc1536b214610998578063e2a7515e146109ad578063e355812314610a77578063e44987b414610a8c576103ef565b8063cede5f6a116100e7578063cede5f6a14610944578063d393c3e914610959578063d70a2d1f1461096e578063d77366a414610983576103ef565b8063c989b667146108f0578063c9b42c6714610905578063cc26e9fc1461091a578063ce74b7ac1461092f576103ef565b8063b0a7ef291161019b578063bf2c5a071161016a578063bf2c5a0714610887578063c762a46c1461089c578063c77b9de6146108b1578063c7f112e4146108c6578063c9257775146108db576103ef565b8063b0a7ef2914610833578063b184a3ae14610848578063b3bc78441461085d578063b69d045614610872576103ef565b8063a1b4d011116101d7578063a1b4d011146107df578063a2878cb1146107f4578063a4792ab314610809578063a734f06e1461081e576103ef565b80637e09b9c21461078b578063819faf7b146107a0578063851954fa146107b55780638bdb2afa146107ca576103ef565b806340ab7b8c116103035780635aa8fb481161029657806368e2a0141161026557806368e2a014146107225780636cbc4a6e1461073757806375a8b0121461074c57806375b5be2d146107615780637a88bdbd14610776576103ef565b80635aa8fb48146106ce5780635ae51b82146106e35780635c0cb479146106f857806364ec4e5c1461070d576103ef565b80634a7101d5116102d25780634a7101d51461067a5780635187c0911461068f57806351f1985c146106a457806352a701b4146106b9576103ef565b806340ab7b8c14610626578063423d03f91461063b57806344211d62146106505780634752c68014610665576103ef565b806322320c981161038657806334b4dabb1161035557806334b4dabb146105bd578063372a26cb146105d25780633ca5b234146105e75780633e413bee146105fc5780633fc8cef314610611576103ef565b806322320c98146105695780632d3b52071461057e5780632e707bd2146105935780632f48ab7d146105a8576103ef565b806313989140116103c257806313989140146105155780631d209b651461052a5780632113240d1461053f57806321a360f514610554576103ef565b806305d8aa0a146103fe578063085e2c5b1461042557806312dea160146104cf5780631388b42014610500575b333214156103fc57600080fd5b005b34801561040a57600080fd5b50610413610b0a565b60408051918252519081900360200190f35b34801561043157600080fd5b50610474600480360360a081101561044857600080fd5b506001600160a01b03813581169160208101359091169060408101359060608101359060800135610b11565b6040518083815260200180602001828103825283818151815260200191508051906020019060200280838360005b838110156104ba5781810151838201526020016104a2565b50505050905001935050505060405180910390f35b3480156104db57600080fd5b506104e4610c5d565b604080516001600160a01b039092168252519081900360200190f35b34801561050c57600080fd5b506104e4610c75565b34801561052157600080fd5b50610413610c8d565b34801561053657600080fd5b50610413610c93565b34801561054b57600080fd5b50610413610c9b565b34801561056057600080fd5b50610413610ca1565b34801561057557600080fd5b506104e4610caa565b34801561058a57600080fd5b50610413610cc2565b34801561059f57600080fd5b50610413610ccb565b3480156105b457600080fd5b506104e4610cd0565b3480156105c957600080fd5b50610413610ce2565b3480156105de57600080fd5b506104e4610ce7565b3480156105f357600080fd5b506104e4610cff565b34801561060857600080fd5b506104e4610d17565b34801561061d57600080fd5b506104e4610d29565b34801561063257600080fd5b506104e4610d41565b34801561064757600080fd5b506104e4610d59565b34801561065c57600080fd5b50610413610d71565b34801561067157600080fd5b50610413610d76565b34801561068657600080fd5b50610413610d7e565b34801561069b57600080fd5b506104e4610d83565b3480156106b057600080fd5b506104e4610d9b565b3480156106c557600080fd5b506104e4610db3565b3480156106da57600080fd5b50610413610dcb565b3480156106ef57600080fd5b50610413610dd1565b34801561070457600080fd5b50610413610dd7565b34801561071957600080fd5b50610413610ddc565b34801561072e57600080fd5b50610413610de3565b34801561074357600080fd5b50610413610dea565b34801561075857600080fd5b50610413610df1565b34801561076d57600080fd5b506104e4610df7565b34801561078257600080fd5b50610413610e0a565b34801561079757600080fd5b50610413610e0f565b3480156107ac57600080fd5b506104e4610e16565b3480156107c157600080fd5b506104e4610e2e565b3480156107d657600080fd5b506104e4610e46565b3480156107eb57600080fd5b506104e4610e5e565b34801561080057600080fd5b50610413610e76565b34801561081557600080fd5b506104e4610e7e565b34801561082a57600080fd5b506104e4610e96565b34801561083f57600080fd5b50610413610eae565b34801561085457600080fd5b506104e4610eb4565b34801561086957600080fd5b50610413610ecc565b34801561087e57600080fd5b506104e4610ed5565b34801561089357600080fd5b50610413610eed565b3480156108a857600080fd5b50610413610ef5565b3480156108bd57600080fd5b50610413610efa565b3480156108d257600080fd5b50610413610f00565b3480156108e757600080fd5b506104e4610f08565b3480156108fc57600080fd5b50610413610f20565b34801561091157600080fd5b50610413610f27565b34801561092657600080fd5b50610413610f2e565b34801561093b57600080fd5b50610413610f33565b34801561095057600080fd5b506104e4610f3b565b34801561096557600080fd5b50610413610f53565b34801561097a57600080fd5b506104e4610f5a565b34801561098f57600080fd5b506104e4610f72565b3480156109a457600080fd5b50610413610f8a565b6103fc600480360360c08110156109c357600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135640100000000811115610a0357600080fd5b820183602082011115610a1557600080fd5b80359060200191846020830284011164010000000083111715610a3757600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505091359250610f90915050565b348015610a8357600080fd5b506104136111e2565b348015610a9857600080fd5b506104136111ea565b348015610aad57600080fd5b506104e46111f2565b348015610ac257600080fd5b50610413611204565b348015610ad757600080fd5b506104e4611209565b348015610aec57600080fd5b50610413611221565b348015610b0157600080fd5b506104e4611229565b6220000081565b600080546040805163085e2c5b60e01b81526001600160a01b03898116600483015288811660248301526044820188905260648201879052608482018690529151606093929092169163085e2c5b9160a4808201928792909190829003018186803b158015610b7f57600080fd5b505afa158015610b93573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040908152811015610bbc57600080fd5b815160208301805160405192949293830192919084640100000000821115610be357600080fd5b908301906020820185811115610bf857600080fd5b8251866020820283011164010000000082111715610c1557600080fd5b82525081516020918201928201910280838360005b83811015610c42578181015183820152602001610c2a565b50505050905001604052505050915091509550959350505050565b7352ae12abe5d8bd778bd5397f99ca900624cfadd481565b73794e6e91555438afc3ccf1c5076a74f42133d08d81565b61200081565b630400000081565b61800081565b64020000000081565b73a5407eae9ba41422680e2e00537571bcc53efbfd81565b64010000000081565b608081565b6000805160206148d183398151915281565b604081565b7379a8c46dea5ada233abaffd40f3a0a2b1e5a4f2781565b734fabb145d64652a948d72533023f6e7a623c7c5381565b6000805160206148b183398151915281565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c81565b7345f783cce6b7ff23b2ab2d70e416cdb7d6055f5181565b601081565b631e00000081565b602081565b735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f81565b73a2b47e3d5c44877cca798226b7b8118f9bfb7a5681565b738e870d67f660d95d5be530380d0ec0bd388289e181565b61400081565b61080081565b600881565b6202000081565b6210000081565b6208000081565b61040081565b6e085d4780b73119b644ae5ecd22b37681565b600281565b6240000081565b73398ec7346dcd622edc5ae82352f02be94c62d11981565b73c0829421c1d260bd3cb3e0f06cfe2d52db2ce31581565b73c0a47dfe034b400b47bdad5fecda2621de6c4d9581565b734ddc2d193948926d02f9b1fe9e1daa0718270ed581565b634000000081565b737079e8517594e5b21d2b9a0d17cb33f5fe2bca7081565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81565b61100081565b7306364f10b501e868329afbc005b3492902d6c76381565b64040000000081565b7306af07097c9eeb7fd685c692751d5c66db49c21581565b630800000081565b600181565b61020081565b638000000081565b7357ab1ec28d129707052df4df418d58a2d46d5f5181565b6280000081565b6204000081565b601281565b630200000081565b7352ea46506b9cc5ef470c5bf89f17dc28bb35d85c81565b6201000081565b73f6e2d7f616b67e46d708e4410746e9aab3a4c51881565b73818e6fecd516ecc3849daf6845e3ec868087b75581565b61010081565b846001600160a01b0316866001600160a01b03161415610faf576111da565b610fb7614815565b60405180610240016040528061123881526020016114b9815260200161164481526020016119698152602001611c428152602001611dcd8152602001611f9281526020016121ab81526020016123ce81526020016125f1815260200161278f815260200161293b8152602001612aa78152602001612bef8152602001612bfc8152602001612c1e8152602001612c3a8152602001612c5681525090506012835111156110945760405162461bcd60e51b81526004018080602001828103825260428152602001806149516042913960600191505060405180910390fd5b600080805b85518110156110f25760008682815181106110b057fe5b602002602001015111156110ea576110e48682815181106110cd57fe5b602002602001015184612e7990919063ffffffff16565b92508091505b600101611099565b50600082116111325760405162461bcd60e51b815260040180806020018281038252602f815260200180614861602f913960400191505060405180910390fd5b8660005b86518110156111d45786818151811061114b57fe5b602002602001015160001415611160576111cc565b60006111988561118c8a858151811061117557fe5b60200260200101518d612edc90919063ffffffff16565b9063ffffffff612f3516565b9050838214156111a55750815b80830392506111c98c8c838986601281106111bc57fe5b602002015163ffffffff16565b50505b600101611136565b50505050505b505050505050565b631000000081565b632000000081565b60008051602061484183398151915281565b600481565b733d9819210a31b4961b30ef54be2aed79b9c9cd3b81565b630100000081565b6000546001600160a01b031681565b60008161124d6001600160a01b038616612f77565b61137d57604080516303795fb160e11b81526001600160a01b0387166004820152905160009173c0a47dfe034b400b47bdad5fecda2621de6c4d95916306f2bf6291602480820192602092909190829003018186803b1580156112af57600080fd5b505afa1580156112c3573d6000803e3d6000fd5b505050506040513d60208110156112d957600080fd5b505190506001600160a01b0381161561137b576112f68682612fb3565b604080516395e3c50b60e01b8152600481018490526001602482015242604482015290516001600160a01b038316916395e3c50b9160648083019260209291908290030181600087803b15801561134c57600080fd5b505af1158015611360573d6000803e3d6000fd5b505050506040513d602081101561137657600080fd5b505191505b505b61138f846001600160a01b0316612f77565b6114af57604080516303795fb160e11b81526001600160a01b0386166004820152905160009173c0a47dfe034b400b47bdad5fecda2621de6c4d95916306f2bf6291602480820192602092909190829003018186803b1580156113f157600080fd5b505afa158015611405573d6000803e3d6000fd5b505050506040513d602081101561141b57600080fd5b505190506001600160a01b038116156114ad57806001600160a01b031663f39b5b9b836001426040518463ffffffff1660e01b815260040180838152602001828152602001925050506020604051808303818588803b15801561147d57600080fd5b505af1158015611491573d6000803e3d6000fd5b50505050506040513d60208110156114a857600080fd5b505191505b505b90505b9392505050565b60006114d98473818e6fecd516ecc3849daf6845e3ec868087b755612fb3565b73818e6fecd516ecc3849daf6845e3ec868087b7556329589f616115056001600160a01b038716612f77565b611510576000611512565b835b611524876001600160a01b0316612f77565b61152e5786611544565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b85611557886001600160a01b0316612f77565b6115615787611577565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b604080516001600160e01b031960e088901b1681526001600160a01b039485166004820152602481019390935292166044820152306064820152600160ff1b6084820152600060a48201819052734d37f28d2db99e8d35a6c725a5f1749a085850a360c483015261010060e4830152610104820152905161014480830192602092919082900301818588803b15801561160f57600080fd5b505af1158015611623573d6000803e3d6000fd5b50505050506040513d602081101561163a57600080fd5b5051949350505050565b6000611658846001600160a01b0316612f77565b156116c65773c0829421c1d260bd3cb3e0f06cfe2d52db2ce3156001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b1580156116ac57600080fd5b505af11580156116c0573d6000803e3d6000fd5b50505050505b60007352ae12abe5d8bd778bd5397f99ca900624cfadd46001600160a01b031663bb34534c6040518163ffffffff1660e01b815260040180806c42616e636f724e6574776f726b60981b815250602001905060206040518083038186803b15801561173057600080fd5b505afa158015611744573d6000803e3d6000fd5b505050506040513d602081101561175a57600080fd5b50519050606061176a868661306c565b90506117a7611781876001600160a01b0316612f77565b61178b57866117a1565b73c0829421c1d260bd3cb3e0f06cfe2d52db2ce3155b83612fb3565b6000826001600160a01b031663c7ba24bc838760016040518463ffffffff1660e01b81526004018080602001848152602001838152602001828103825285818151815260200191508051906020019060200280838360005b838110156118175781810151838201526020016117ff565b50505050905001945050505050602060405180830381600087803b15801561183e57600080fd5b505af1158015611852573d6000803e3d6000fd5b505050506040513d602081101561186857600080fd5b5051905061187e6001600160a01b038716612f77565b1561195f57604080516370a0823160e01b8152306004820152905173c0829421c1d260bd3cb3e0f06cfe2d52db2ce31591632e1a7d4d9183916370a08231916024808301926020929190829003018186803b1580156118dc57600080fd5b505afa1580156118f0573d6000803e3d6000fd5b505050506040513d602081101561190657600080fd5b5051604080516001600160e01b031960e085901b168152600481019290925251602480830192600092919082900301818387803b15801561194657600080fd5b505af115801561195a573d6000803e3d6000fd5b505050505b9695505050505050565b600061197d846001600160a01b0316612f77565b156119eb5773c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b1580156119d157600080fd5b505af11580156119e5573d6000803e3d6000fd5b50505050505b611a3a611a00856001600160a01b0316612f77565b611a0a5784611a20565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b73794e6e91555438afc3ccf1c5076a74f42133d08d612fb3565b600073794e6e91555438afc3ccf1c5076a74f42133d08d630621b4f6611a686001600160a01b038816612f77565b611a725786611a88565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b85611a9b886001600160a01b0316612f77565b611aa55787611abb565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b604080516001600160e01b031960e087901b1681526001600160a01b03948516600482015260248101939093529216604482015260016064820152905160848083019260209291908290030181600087803b158015611b1957600080fd5b505af1158015611b2d573d6000803e3d6000fd5b505050506040513d6020811015611b4357600080fd5b50519050611b596001600160a01b038516612f77565b156114af57604080516370a0823160e01b8152306004820152905173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc291632e1a7d4d9183916370a08231916024808301926020929190829003018186803b158015611bb757600080fd5b505afa158015611bcb573d6000803e3d6000fd5b505050506040513d6020811015611be157600080fd5b5051604080516001600160e01b031960e085901b168152600481019290925251602480830192600092919082900301818387803b158015611c2157600080fd5b505af1158015611c35573d6000803e3d6000fd5b5050505090509392505050565b6000806001600160a01b0385166000805160206148b183398151915214611c6a576000611c6d565b60025b6001600160a01b03861660008051602061484183398151915214611c92576000611c95565b60015b0160ff16905060006000805160206148b18339815191526001600160a01b03861614611cc2576000611cc5565b60025b6001600160a01b03861660008051602061484183398151915214611cea576000611ced565b60015b0160ff16905081600f0b60001480611d08575080600f0b6000145b15611d18576000925050506114b2565b611d368673a2b47e3d5c44877cca798226b7b8118f9bfb7a56612fb3565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b602482015260448101869052600060648201819052915173a2b47e3d5c44877cca798226b7b8118f9bfb7a569263a6417ed6926084808201939182900301818387803b158015611dac57600080fd5b505af1158015611dc0573d6000803e3d6000fd5b5050505050509392505050565b6000806001600160a01b0385166000805160206148d183398151915214611df5576000611df8565b60035b6001600160a01b0386166000805160206148b183398151915214611e1d576000611e20565b60025b6001600160a01b03871660008051602061484183398151915214611e45576000611e48565b60015b010160ff16905060006000805160206148d18339815191526001600160a01b0316856001600160a01b031614611e7f576000611e82565b60035b6001600160a01b0386166000805160206148b183398151915214611ea7576000611eaa565b60025b6001600160a01b03871660008051602061484183398151915214611ecf576000611ed2565b60015b010160ff16905081600f0b60001480611eee575080600f0b6000145b15611efe576000925050506114b2565b611f1c867352ea46506b9cc5ef470c5bf89f17dc28bb35d85c612fb3565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517352ea46506b9cc5ef470c5bf89f17dc28bb35d85c9263a6417ed6926084808201939182900301818387803b158015611dac57600080fd5b6000806001600160a01b0385166e085d4780b73119b644ae5ecd22b37614611fbb576000611fbe565b60045b6001600160a01b0386166000805160206148d183398151915214611fe3576000611fe6565b60035b6001600160a01b0387166000805160206148b18339815191521461200b57600061200e565b60025b6001600160a01b03881660008051602061484183398151915214612033576000612036565b60015b01010160ff16905060006e085d4780b73119b644ae5ecd22b3766001600160a01b0316856001600160a01b03161461206f576000612072565b60045b6001600160a01b0386166000805160206148d18339815191521461209757600061209a565b60035b6001600160a01b0387166000805160206148b1833981519152146120bf5760006120c2565b60025b6001600160a01b038816600080516020614841833981519152146120e75760006120ea565b60015b01010160ff16905081600f0b60001480612107575080600f0b6000145b15612117576000925050506114b2565b612135867345f783cce6b7ff23b2ab2d70e416cdb7d6055f51612fb3565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517345f783cce6b7ff23b2ab2d70e416cdb7d6055f519263a6417ed6926084808201939182900301818387803b158015611dac57600080fd5b6000806001600160a01b038516734fabb145d64652a948d72533023f6e7a623c7c53146121d95760006121dc565b60045b6001600160a01b0386166000805160206148d183398151915214612201576000612204565b60035b6001600160a01b0387166000805160206148b18339815191521461222957600061222c565b60025b6001600160a01b03881660008051602061484183398151915214612251576000612254565b60015b01010160ff1690506000734fabb145d64652a948d72533023f6e7a623c7c536001600160a01b0316856001600160a01b031614612292576000612295565b60045b6001600160a01b0386166000805160206148d1833981519152146122ba5760006122bd565b60035b6001600160a01b0387166000805160206148b1833981519152146122e25760006122e5565b60025b6001600160a01b0388166000805160206148418339815191521461230a57600061230d565b60015b01010160ff16905081600f0b6000148061232a575080600f0b6000145b1561233a576000925050506114b2565b612358867379a8c46dea5ada233abaffd40f3a0a2b1e5a4f27612fb3565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517379a8c46dea5ada233abaffd40f3a0a2b1e5a4f279263a6417ed6926084808201939182900301818387803b158015611dac57600080fd5b6000806001600160a01b0385167357ab1ec28d129707052df4df418d58a2d46d5f51146123fc5760006123ff565b60045b6001600160a01b0386166000805160206148d183398151915214612424576000612427565b60035b6001600160a01b0387166000805160206148b18339815191521461244c57600061244f565b60025b6001600160a01b03881660008051602061484183398151915214612474576000612477565b60015b01010160ff16905060007357ab1ec28d129707052df4df418d58a2d46d5f516001600160a01b0316856001600160a01b0316146124b55760006124b8565b60045b6001600160a01b0386166000805160206148d1833981519152146124dd5760006124e0565b60035b6001600160a01b0387166000805160206148b183398151915214612505576000612508565b60025b6001600160a01b0388166000805160206148418339815191521461252d576000612530565b60015b01010160ff16905081600f0b6000148061254d575080600f0b6000145b1561255d576000925050506114b2565b61257b8673a5407eae9ba41422680e2e00537571bcc53efbfd612fb3565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b602482015260448101869052600060648201819052915173a5407eae9ba41422680e2e00537571bcc53efbfd9263a6417ed6926084808201939182900301818387803b158015611dac57600080fd5b6000612605846001600160a01b0316612f77565b6126be57600061261485613776565b90506126208582612fb3565b806001600160a01b031663a0712d68846040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b15801561266657600080fd5b505af115801561267a573d6000803e3d6000fd5b505050506040513d602081101561269057600080fd5b506126b6905081856126b16001600160a01b0383163063ffffffff61396016565b611238565b9150506114b2565b6126d0836001600160a01b0316612f77565b6127855760006126df84613776565b905060006126ee868386611238565b9050816001600160a01b031663db006a75826040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b15801561273657600080fd5b505af115801561274a573d6000803e3d6000fd5b505050506040513d602081101561276057600080fd5b5061277c90506001600160a01b0386163063ffffffff61396016565b925050506114b2565b5060009392505050565b60006001600160a01b0384166000805160206148418339815191521415612872576127ce847306af07097c9eeb7fd685c692751d5c66db49c215612fb3565b60408051633b4da69f60e01b81523060048201526024810184905290517306af07097c9eeb7fd685c692751d5c66db49c21591633b4da69f91604480830192600092919082900301818387803b15801561282757600080fd5b505af115801561283b573d6000803e3d6000fd5b5061286b92507306af07097c9eeb7fd685c692751d5c66db49c21591508590506126b1823063ffffffff61396016565b90506114b2565b6001600160a01b03831660008051602061484183398151915214156127855760006128b2857306af07097c9eeb7fd685c692751d5c66db49c21585611238565b6040805163ef693bed60e01b81523060048201526024810183905290519192507306af07097c9eeb7fd685c692751d5c66db49c2159163ef693bed9160448082019260009290919082900301818387803b15801561290f57600080fd5b505af1158015612923573d6000803e3d6000fd5b506126b6925050506001600160a01b03851630613960565b600061294f846001600160a01b0316612f77565b612a0d57600061295e85613a0a565b905061296a8582612fb3565b60408051636968703360e11b81526001600160a01b03871660048201526024810185905261044d6044820152905173398ec7346dcd622edc5ae82352f02be94c62d1199163d2d0e06691606480830192600092919082900301818387803b1580156129d457600080fd5b505af11580156129e8573d6000803e3d6000fd5b505050506126b681856126b130856001600160a01b031661396090919063ffffffff16565b612a1f836001600160a01b0316612f77565b612785576000612a2e84613a0a565b90506000612a3d868386611238565b9050816001600160a01b031663db006a75826040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b158015612a8557600080fd5b505af1158015612a99573d6000803e3d6000fd5b5050505080925050506114b2565b600080737079e8517594e5b21d2b9a0d17cb33f5fe2bca706001600160a01b031663d4b839926040518163ffffffff1660e01b815260040160206040518083038186803b158015612af757600080fd5b505afa158015612b0b573d6000803e3d6000fd5b505050506040513d6020811015612b2157600080fd5b50519050612b2f8582612fb3565b806001600160a01b031663fe029156612b50876001600160a01b0316612f77565b612b5b576000612b5d565b845b604080516001600160e01b031960e085901b1681526001600160a01b03808b1660048301528916602482015260448101889052600060648201529051608480830192602092919082900301818588803b158015612bb957600080fd5b505af1158015612bcd573d6000803e3d6000fd5b50505050506040513d6020811015612be457600080fd5b505195945050505050565b60006114af848484613e27565b60006114af8473c02aaa39b223fe8d0a0e5c4f27ead9083c756cc285856141f0565b60006114af8460008051602061484183398151915285856141f0565b60006114af846000805160206148b183398151915285856141f0565b6000806001600160a01b038516738e870d67f660d95d5be530380d0ec0bd388289e114612c84576000612c87565b60045b6001600160a01b0386166000805160206148d183398151915214612cac576000612caf565b60035b6001600160a01b0387166000805160206148b183398151915214612cd4576000612cd7565b60025b6001600160a01b03881660008051602061484183398151915214612cfc576000612cff565b60015b01010160ff1690506000738e870d67f660d95d5be530380d0ec0bd388289e16001600160a01b0316856001600160a01b031614612d3d576000612d40565b60045b6001600160a01b0386166000805160206148d183398151915214612d65576000612d68565b60035b6001600160a01b0387166000805160206148b183398151915214612d8d576000612d90565b60025b6001600160a01b03881660008051602061484183398151915214612db5576000612db8565b60015b01010160ff16905081600f0b60001480612dd5575080600f0b6000145b15612de5576000925050506114b2565b612e03867306364f10b501e868329afbc005b3492902d6c763612fb3565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517306364f10b501e868329afbc005b3492902d6c7639263a6417ed6926084808201939182900301818387803b158015611dac57600080fd5b600082820183811015612ed3576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b90505b92915050565b600082612eeb57506000612ed6565b82820282848281612ef857fe5b0414612ed35760405162461bcd60e51b81526004018080602001828103825260218152602001806148906021913960400191505060405180910390fd5b6000612ed383836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250614210565b60006001600160a01b0382161580612fab57506001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee145b90505b919050565b612fc5826001600160a01b0316612f77565b6130685760408051636eb1769f60e11b81523060048201526001600160a01b038381166024830152915160ff9285169163dd62ed3e916044808301926020929190829003018186803b15801561301a57600080fd5b505afa15801561302e573d6000803e3d6000fd5b505050506040513d602081101561304457600080fd5b5051901c613068576130686001600160a01b0383168260001963ffffffff6142b216565b5050565b6060816001600160a01b0316836001600160a01b0316141561309d5750604080516000815260208101909152612ed6565b6130af836001600160a01b0316612f77565b156130cc5773c0829421c1d260bd3cb3e0f06cfe2d52db2ce31592505b6130de826001600160a01b0316612f77565b156130fb5773c0829421c1d260bd3cb3e0f06cfe2d52db2ce31591505b6001600160a01b038316731f573d6fb3f13d689ff844b4ce37794d79a7ff1c148061314257506001600160a01b038216731f573d6fb3f13d689ff844b4ce37794d79a7ff1c145b1561316d5760408051600380825260808201909252906020820160608038833901905050905061318f565b60408051600580825260c08201909252906020820160a0803883390190505090505b6000806001600160a01b038516731f573d6fb3f13d689ff844b4ce37794d79a7ff1c14613358576000606073f6e2d7f616b67e46d708e4410746e9aab3a4c518612710636b625ad960e11b6131ec6001600160a01b038b16612f77565b6131f6578961320c565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c5b604080516001600160a01b039092166024830152600060448084019190915281518084039091018152606490920181526020820180516001600160e01b03166001600160e01b0319909416939093178352518151919290918291908083835b6020831061328a5780518252601f19909201916020918201910161326b565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303818686fa925050503d80600081146132eb576040519150601f19603f3d011682016040523d82523d6000602084013e6132f0565b606091505b5091509150816133185760408051600080825260208201909252905b50945050505050612ed6565b80806020019051602081101561332d57600080fd5b505193506001600160a01b03841661335557604080516000808252602082019092529061330c565b50505b6001600160a01b038416731f573d6fb3f13d689ff844b4ce37794d79a7ff1c14613516576000606073f6e2d7f616b67e46d708e4410746e9aab3a4c518612710636b625ad960e11b6133b26001600160a01b038a16612f77565b6133bc57886133d2565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c5b604080516001600160a01b039092166024830152600060448084019190915281518084039091018152606490920181526020820180516001600160e01b03166001600160e01b0319909416939093178352518151919290918291908083835b602083106134505780518252601f199092019160209182019101613431565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303818686fa925050503d80600081146134b1576040519150601f19603f3d011682016040523d82523d6000602084013e6134b6565b606091505b5091509150816134d657604080516000808252602082019092529061330c565b8080602001905160208110156134eb57600080fd5b505192506001600160a01b03831661351357604080516000808252602082019092529061330c565b50505b6001600160a01b038416731f573d6fb3f13d689ff844b4ce37794d79a7ff1c14156135d957848360008151811061354957fe5b60200260200101906001600160a01b031690816001600160a01b031681525050818360018151811061357757fe5b60200260200101906001600160a01b031690816001600160a01b031681525050731f573d6fb3f13d689ff844b4ce37794d79a7ff1c836002815181106135b957fe5b6001600160a01b039092166020928302919091019091015250612ed69050565b6001600160a01b038516731f573d6fb3f13d689ff844b4ce37794d79a7ff1c141561367c57731f573d6fb3f13d689ff844b4ce37794d79a7ff1c8360008151811061362057fe5b60200260200101906001600160a01b031690816001600160a01b031681525050808360018151811061364e57fe5b60200260200101906001600160a01b031690816001600160a01b03168152505083836002815181106135b957fe5b848360008151811061368a57fe5b60200260200101906001600160a01b031690816001600160a01b03168152505081836001815181106136b857fe5b60200260200101906001600160a01b031690816001600160a01b031681525050731f573d6fb3f13d689ff844b4ce37794d79a7ff1c836002815181106136fa57fe5b60200260200101906001600160a01b031690816001600160a01b031681525050808360038151811061372857fe5b60200260200101906001600160a01b031690816001600160a01b031681525050838360048151811061375657fe5b6001600160a01b0390921660209283029190910190910152505092915050565b600061378a826001600160a01b0316612f77565b156137aa5750734ddc2d193948926d02f9b1fe9e1daa0718270ed5612fae565b6001600160a01b03821660008051602061484183398151915214156137e45750735d3a536e4d6dbd6114cc1ead35777bab948e3643612fae565b6001600160a01b038216730d8775f648430679a709e98d2b0cb6250d2887ef14156138245750736c8c6b02e7b2be14d4fa6022dfd6d75921d90e4e612fae565b6001600160a01b038216731985365e9f78359a9b6ad760e32412f4a445e8621415613864575073158079ee67fce2f58472a96584a73c7ab9ac95c1612fae565b6001600160a01b0382166000805160206148b1833981519152141561389e57507339aa39c021dfbae8fac545936693ac917d5e7563612fae565b6001600160a01b038216732260fac5e5542a773aa44fbcfedf7c193bc2c59914156138de575073c11b1268c1a384e55c48c2391d8d480264a3a7f4612fae565b6001600160a01b03821673e41d2489571d322189246dafa5ebde1f4699f498141561391e575073b3319f5d18bc0d84dd1b4825dcde5d5f7266d407612fae565b6001600160a01b0382166000805160206148d18339815191521415613958575073f650c3d88d12db855b8bf7d11be6c55a4e07dcc9612fae565b506000919050565b600061396b83612f77565b1561398157506001600160a01b03811631612ed6565b826001600160a01b03166370a08231836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156139d757600080fd5b505afa1580156139eb573d6000803e3d6000fd5b505050506040513d6020811015613a0157600080fd5b50519050612ed6565b6000613a1e826001600160a01b0316612f77565b15613a3e5750733a3a65aab0dd2a17e3f1947ba16138cd37d08c04612fae565b6001600160a01b0382166000805160206148418339815191521415613a78575073fc1e690f61efd961294b3e1ce3313fbd8aa4f85d612fae565b6001600160a01b0382166000805160206148b18339815191521415613ab25750739ba00d6856a4edf4665bca2c2309936572473b7e612fae565b6001600160a01b0382167357ab1ec28d129707052df4df418d58a2d46d5f511415613af2575073625ae63000f46200499120b906716420bd059240612fae565b6001600160a01b038216734fabb145d64652a948d72533023f6e7a623c7c531415613b325750736ee0f7bb50a54ab5253da0667b0dc2ee526c30a8612fae565b6001600160a01b0382166e085d4780b73119b644ae5ecd22b3761415613b6d5750734da9b813057d04baef4e5800e36083717b4a0341612fae565b6001600160a01b0382166000805160206148d18339815191521415613ba757507371fc860f7d3a592a4a98740e39db31d25db65ae8612fae565b6001600160a01b038216730d8775f648430679a709e98d2b0cb6250d2887ef1415613be7575073e1ba0fb44ccb0d11b80f92f4f8ed94ca3ff51d00612fae565b6001600160a01b03821673dd974d5c2e2928dea5f71b9825b8b646686bd2001415613c275750739d91be44c06d373a8a226e1f3b146956083803eb612fae565b6001600160a01b0382167380fb784b7ed66730e8b1dbd9820afd29931aab031415613c675750737d2d3688df45ce7c552e19c27e007673da9204b8612fae565b6001600160a01b03821673514910771af9ca656af840dff83e8264ecf986ca1415613ca7575073a64bd6c70cb9051f6a9ba1f163fdc07e0dfb5f84612fae565b6001600160a01b038216730f5d2fb29fb7d3cfee444a200298f468908cc9421415613ce75750736fce4a401b6b80ace52baaefe4421bd188e76f6f612fae565b6001600160a01b038216739f8f72aa9304c8b593d555f12ef6589cc3a579a21415613d275750737deb5e830be29f91e298ba5ff1356bb7f8146998612fae565b6001600160a01b038216731985365e9f78359a9b6ad760e32412f4a445e8621415613d6757507371010a9d003445ac60c4e6a7017c1e89a477b438612fae565b6001600160a01b03821673c011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f1415613da7575073328c4c80bc7aca0834db37e6600a6c49e12da4de612fae565b6001600160a01b038216732260fac5e5542a773aa44fbcfedf7c193bc2c5991415613de7575073fc4b8ed459e00e5400be803a9bb3954234fd50e3612fae565b6001600160a01b03821673e41d2489571d322189246dafa5ebde1f4699f49814156139585750736fb0855c404e09c47c3fbca25f08d4e41f9f062f612fae565b6000613e3b846001600160a01b0316612f77565b15613ea95773c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b158015613e8f57600080fd5b505af1158015613ea3573d6000803e3d6000fd5b50505050505b6000613ebd856001600160a01b0316612f77565b613ec75784613edd565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b90506000613ef3856001600160a01b0316612f77565b613efd5784613f13565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b6040805163e6a4390560e01b81526001600160a01b038581166004830152831660248201529051919250600091735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f9163e6a43905916044808301926020929190829003018186803b158015613f7b57600080fd5b505afa158015613f8f573d6000803e3d6000fd5b505050506040513d6020811015613fa557600080fd5b50519050613fc46001600160a01b03821684848863ffffffff61438816565b9350613fe06001600160a01b038416828763ffffffff61443f16565b50816001600160a01b0316836001600160a01b03161015614079576040805163022c0d9f60e01b815260006004820181905260248201879052306044830152608060648301526084820181905291516001600160a01b0384169263022c0d9f9260c4808201939182900301818387803b15801561405c57600080fd5b505af1158015614070573d6000803e3d6000fd5b505050506140f3565b6040805163022c0d9f60e01b815260048101869052600060248201819052306044830152608060648301526084820181905291516001600160a01b0384169263022c0d9f9260c4808201939182900301818387803b1580156140da57600080fd5b505af11580156140ee573d6000803e3d6000fd5b505050505b614105866001600160a01b0316612f77565b156141e657604080516370a0823160e01b8152306004820152905173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc291632e1a7d4d9183916370a08231916024808301926020929190829003018186803b15801561416357600080fd5b505afa158015614177573d6000803e3d6000fd5b505050506040513d602081101561418d57600080fd5b5051604080516001600160e01b031960e085901b168152600481019290925251602480830192600092919082900301818387803b1580156141cd57600080fd5b505af11580156141e1573d6000803e3d6000fd5b505050505b5050509392505050565b60006142078484614202888887613e27565b613e27565b95945050505050565b6000818361429c5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015614261578181015183820152602001614249565b50505050905090810190601f16801561428e5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385816142a857fe5b0495945050505050565b6142bb83612f77565b61438357600081118015614349575060408051636eb1769f60e11b81523060048201526001600160a01b038481166024830152915160009286169163dd62ed3e916044808301926020929190829003018186803b15801561431b57600080fd5b505afa15801561432f573d6000803e3d6000fd5b505050506040513d602081101561434557600080fd5b5051115b15614369576143696001600160a01b03841683600063ffffffff6144ba16565b6143836001600160a01b038416838363ffffffff6144ba16565b505050565b6000806143a46001600160a01b0386168763ffffffff61396016565b905060006143c16001600160a01b0386168863ffffffff61396016565b905060006143d7856103e563ffffffff612edc16565b905060006143eb828463ffffffff612edc16565b9050600061441183614405876103e863ffffffff612edc16565b9063ffffffff612e7916565b9050801561442e57614429828263ffffffff612f3516565b614431565b60005b9a9950505050505050505050565b60008161444e575060016114b2565b61445784612f77565b15614498576040516001600160a01b0384169083156108fc029084906000818181858888f19350505050158015614492573d6000803e3d6000fd5b506114b2565b6144b26001600160a01b038516848463ffffffff6145cd16565b5060016114b2565b801580614540575060408051636eb1769f60e11b81523060048201526001600160a01b03848116602483015291519185169163dd62ed3e91604480820192602092909190829003018186803b15801561451257600080fd5b505afa158015614526573d6000803e3d6000fd5b505050506040513d602081101561453c57600080fd5b5051155b61457b5760405162461bcd60e51b815260040180806020018281038252603681526020018061491b6036913960400191505060405180910390fd5b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b17905261438390849061461b565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526143839084905b61462d826001600160a01b03166147d9565b61467e576040805162461bcd60e51b815260206004820152601f60248201527f5361666545524332303a2063616c6c20746f206e6f6e2d636f6e747261637400604482015290519081900360640190fd5b60006060836001600160a01b0316836040518082805190602001908083835b602083106146bc5780518252601f19909201916020918201910161469d565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d806000811461471e576040519150601f19603f3d011682016040523d82523d6000602084013e614723565b606091505b50915091508161477a576040805162461bcd60e51b815260206004820181905260248201527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564604482015290519081900360640190fd5b8051156147d35780806020019051602081101561479657600080fd5b50516147d35760405162461bcd60e51b815260040180806020018281038252602a8152602001806148f1602a913960400191505060405180910390fd5b50505050565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47081811480159061480d57508115155b949350505050565b6040518061024001604052806012905b61483e8152602001906001900390816148255790505090565bfefe0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f4f6e6553706c69743a20646973747269627574696f6e2073686f756c6420636f6e7461696e206e6f6e2d7a65726f73536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec75361666545524332303a204552433230206f7065726174696f6e20646964206e6f7420737563636565645361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f20746f206e6f6e2d7a65726f20616c6c6f77616e63654f6e6553706c69743a20446973747269627574696f6e2061727261792073686f756c64206e6f74206578636565642072657365727665732061727261792073697a65a265627a7a723158207068c93019b2686c3aaa6b0508facc3360f8f4db640c51255473612168174a7c64736f6c63430005100032 \ No newline at end of file +608060405234801561001057600080fd5b5060405162004a7938038062004a798339818101604052602081101561003557600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055614a1180620000686000396000f3fe60806040526004361061041b5760003560e01c8063819faf7b1161021e578063c989b66711610123578063dc1536b2116100ab578063f4b9fa751161007a578063f4b9fa7514610ae2578063f56e281f14610af7578063f69e204614610b0c578063fa3f110b14610b21578063fbe4ed9514610b365761041b565b8063dc1536b2146109d9578063e2a7515e146109ee578063e355812314610ab8578063e44987b414610acd5761041b565b8063cede5f6a116100f2578063cede5f6a14610985578063d1aee5e314610580578063d393c3e91461099a578063d70a2d1f146109af578063d77366a4146109c45761041b565b8063c989b66714610931578063c9b42c6714610946578063cc26e9fc1461095b578063ce74b7ac146109705761041b565b8063b0a7ef29116101a6578063bf2c5a0711610175578063bf2c5a07146108c8578063c762a46c146108dd578063c77b9de6146108f2578063c7f112e414610907578063c92577751461091c5761041b565b8063b0a7ef2914610889578063b184a3ae1461089e578063b3bc7844146107f6578063b69d0456146108b35761041b565b80638ea812c0116101ed5780638ea812c014610820578063a1b4d01114610835578063a2878cb11461084a578063a4792ab31461085f578063a734f06e146108745761041b565b8063819faf7b146107cc578063851954fa146107e15780638aea49d2146107f65780638bdb2afa1461080b5761041b565b80634226a9b9116103245780635ae51b82116102ac5780636cbc4a6e1161027b5780636cbc4a6e1461076357806375a8b0121461077857806375b5be2d1461078d5780637a88bdbd146107a25780637e09b9c2146107b75761041b565b80635ae51b821461070f5780635c0cb4791461072457806364ec4e5c1461073957806368e2a0141461074e5761041b565b80634a7101d5116102f35780634a7101d5146106a65780635187c091146106bb57806351f1985c146106d057806352a701b4146106e55780635aa8fb48146106fa5761041b565b80634226a9b9146105aa578063423d03f91461066757806344211d621461067c5780634752c680146106915761041b565b80632d3b5207116103a7578063372a26cb11610376578063372a26cb146105fe5780633ca5b234146106135780633e413bee146106285780633fc8cef31461063d57806340ab7b8c146106525761041b565b80632d3b5207146105aa5780632e707bd2146105bf5780632f48ab7d146105d457806334b4dabb146105e95761041b565b806313989140116103ee57806313989140146105415780631d209b65146105565780632113240d1461056b57806321a360f51461058057806322320c98146105955761041b565b806305d8aa0a1461042a578063085e2c5b1461045157806312dea160146104fb5780631388b4201461052c575b3332141561042857600080fd5b005b34801561043657600080fd5b5061043f610b4b565b60408051918252519081900360200190f35b34801561045d57600080fd5b506104a0600480360360a081101561047457600080fd5b506001600160a01b03813581169160208101359091169060408101359060608101359060800135610b52565b6040518083815260200180602001828103825283818151815260200191508051906020019060200280838360005b838110156104e65781810151838201526020016104ce565b50505050905001935050505060405180910390f35b34801561050757600080fd5b50610510610c9e565b604080516001600160a01b039092168252519081900360200190f35b34801561053857600080fd5b50610510610cb6565b34801561054d57600080fd5b5061043f610cce565b34801561056257600080fd5b5061043f610cd4565b34801561057757600080fd5b5061043f610cdc565b34801561058c57600080fd5b5061043f610ce2565b3480156105a157600080fd5b50610510610ceb565b3480156105b657600080fd5b5061043f610d03565b3480156105cb57600080fd5b5061043f610d0c565b3480156105e057600080fd5b50610510610d11565b3480156105f557600080fd5b5061043f610d23565b34801561060a57600080fd5b50610510610d28565b34801561061f57600080fd5b50610510610d40565b34801561063457600080fd5b50610510610d58565b34801561064957600080fd5b50610510610d6a565b34801561065e57600080fd5b50610510610d82565b34801561067357600080fd5b50610510610d9a565b34801561068857600080fd5b5061043f610db2565b34801561069d57600080fd5b5061043f610db7565b3480156106b257600080fd5b5061043f610dbf565b3480156106c757600080fd5b50610510610dc4565b3480156106dc57600080fd5b50610510610ddc565b3480156106f157600080fd5b50610510610df4565b34801561070657600080fd5b5061043f610e0c565b34801561071b57600080fd5b5061043f610e12565b34801561073057600080fd5b5061043f610e18565b34801561074557600080fd5b5061043f610e1d565b34801561075a57600080fd5b5061043f610e24565b34801561076f57600080fd5b5061043f610e2b565b34801561078457600080fd5b5061043f610e32565b34801561079957600080fd5b50610510610e38565b3480156107ae57600080fd5b5061043f610e4b565b3480156107c357600080fd5b5061043f610e50565b3480156107d857600080fd5b50610510610e57565b3480156107ed57600080fd5b50610510610e6f565b34801561080257600080fd5b5061043f610e87565b34801561081757600080fd5b50610510610e90565b34801561082c57600080fd5b5061043f610ea8565b34801561084157600080fd5b50610510610eb1565b34801561085657600080fd5b5061043f610ec9565b34801561086b57600080fd5b50610510610ed1565b34801561088057600080fd5b50610510610ee9565b34801561089557600080fd5b5061043f610f01565b3480156108aa57600080fd5b50610510610f07565b3480156108bf57600080fd5b50610510610f1f565b3480156108d457600080fd5b5061043f610f37565b3480156108e957600080fd5b5061043f610f3f565b3480156108fe57600080fd5b5061043f610f44565b34801561091357600080fd5b5061043f610f4a565b34801561092857600080fd5b50610510610f52565b34801561093d57600080fd5b5061043f610f6a565b34801561095257600080fd5b5061043f610f71565b34801561096757600080fd5b5061043f610f78565b34801561097c57600080fd5b5061043f610f7d565b34801561099157600080fd5b50610510610f85565b3480156109a657600080fd5b5061043f610f9d565b3480156109bb57600080fd5b50610510610fa4565b3480156109d057600080fd5b50610510610fbc565b3480156109e557600080fd5b5061043f610fd4565b610428600480360360c0811015610a0457600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135640100000000811115610a4457600080fd5b820183602082011115610a5657600080fd5b80359060200191846020830284011164010000000083111715610a7857600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505091359250610fda915050565b348015610ac457600080fd5b5061043f61122c565b348015610ad957600080fd5b5061043f611234565b348015610aee57600080fd5b5061051061123c565b348015610b0357600080fd5b5061043f61124e565b348015610b1857600080fd5b50610510611253565b348015610b2d57600080fd5b5061043f61126b565b348015610b4257600080fd5b50610510611273565b6220000081565b600080546040805163085e2c5b60e01b81526001600160a01b03898116600483015288811660248301526044820188905260648201879052608482018690529151606093929092169163085e2c5b9160a4808201928792909190829003018186803b158015610bc057600080fd5b505afa158015610bd4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040908152811015610bfd57600080fd5b815160208301805160405192949293830192919084640100000000821115610c2457600080fd5b908301906020820185811115610c3957600080fd5b8251866020820283011164010000000082111715610c5657600080fd5b82525081516020918201928201910280838360005b83811015610c83578181015183820152602001610c6b565b50505050905001604052505050915091509550959350505050565b7352ae12abe5d8bd778bd5397f99ca900624cfadd481565b73794e6e91555438afc3ccf1c5076a74f42133d08d81565b61200081565b630400000081565b61800081565b64020000000081565b73a5407eae9ba41422680e2e00537571bcc53efbfd81565b64010000000081565b608081565b60008051602061491b83398151915281565b604081565b7379a8c46dea5ada233abaffd40f3a0a2b1e5a4f2781565b734fabb145d64652a948d72533023f6e7a623c7c5381565b6000805160206148fb83398151915281565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c81565b7345f783cce6b7ff23b2ab2d70e416cdb7d6055f5181565b601081565b631e00000081565b602081565b735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f81565b73a2b47e3d5c44877cca798226b7b8118f9bfb7a5681565b738e870d67f660d95d5be530380d0ec0bd388289e181565b61400081565b61080081565b600881565b6202000081565b6210000081565b6208000081565b61040081565b6e085d4780b73119b644ae5ecd22b37681565b600281565b6240000081565b73398ec7346dcd622edc5ae82352f02be94c62d11981565b73c0829421c1d260bd3cb3e0f06cfe2d52db2ce31581565b64040000000081565b73c0a47dfe034b400b47bdad5fecda2621de6c4d9581565b64080000000081565b734ddc2d193948926d02f9b1fe9e1daa0718270ed581565b634000000081565b737079e8517594e5b21d2b9a0d17cb33f5fe2bca7081565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81565b61100081565b7306364f10b501e868329afbc005b3492902d6c76381565b7306af07097c9eeb7fd685c692751d5c66db49c21581565b630800000081565b600181565b61020081565b638000000081565b7357ab1ec28d129707052df4df418d58a2d46d5f5181565b6280000081565b6204000081565b601281565b630200000081565b7352ea46506b9cc5ef470c5bf89f17dc28bb35d85c81565b6201000081565b73f6e2d7f616b67e46d708e4410746e9aab3a4c51881565b73818e6fecd516ecc3849daf6845e3ec868087b75581565b61010081565b846001600160a01b0316866001600160a01b03161415610ff957611224565b61100161485f565b6040518061024001604052806112828152602001611503815260200161168e81526020016119b38152602001611c8c8152602001611e178152602001611fdc81526020016121f58152602001612418815260200161263b81526020016127d981526020016129858152602001612af18152602001612c398152602001612c468152602001612c688152602001612c848152602001612ca081525090506012835111156110de5760405162461bcd60e51b815260040180806020018281038252604281526020018061499b6042913960600191505060405180910390fd5b600080805b855181101561113c5760008682815181106110fa57fe5b602002602001015111156111345761112e86828151811061111757fe5b602002602001015184612ec390919063ffffffff16565b92508091505b6001016110e3565b506000821161117c5760405162461bcd60e51b815260040180806020018281038252602f8152602001806148ab602f913960400191505060405180910390fd5b8660005b865181101561121e5786818151811061119557fe5b6020026020010151600014156111aa57611216565b60006111e2856111d68a85815181106111bf57fe5b60200260200101518d612f2690919063ffffffff16565b9063ffffffff612f7f16565b9050838214156111ef5750815b80830392506112138c8c8389866012811061120657fe5b602002015163ffffffff16565b50505b600101611180565b50505050505b505050505050565b631000000081565b632000000081565b60008051602061488b83398151915281565b600481565b733d9819210a31b4961b30ef54be2aed79b9c9cd3b81565b630100000081565b6000546001600160a01b031681565b6000816112976001600160a01b038616612fc1565b6113c757604080516303795fb160e11b81526001600160a01b0387166004820152905160009173c0a47dfe034b400b47bdad5fecda2621de6c4d95916306f2bf6291602480820192602092909190829003018186803b1580156112f957600080fd5b505afa15801561130d573d6000803e3d6000fd5b505050506040513d602081101561132357600080fd5b505190506001600160a01b038116156113c5576113408682612ffd565b604080516395e3c50b60e01b8152600481018490526001602482015242604482015290516001600160a01b038316916395e3c50b9160648083019260209291908290030181600087803b15801561139657600080fd5b505af11580156113aa573d6000803e3d6000fd5b505050506040513d60208110156113c057600080fd5b505191505b505b6113d9846001600160a01b0316612fc1565b6114f957604080516303795fb160e11b81526001600160a01b0386166004820152905160009173c0a47dfe034b400b47bdad5fecda2621de6c4d95916306f2bf6291602480820192602092909190829003018186803b15801561143b57600080fd5b505afa15801561144f573d6000803e3d6000fd5b505050506040513d602081101561146557600080fd5b505190506001600160a01b038116156114f757806001600160a01b031663f39b5b9b836001426040518463ffffffff1660e01b815260040180838152602001828152602001925050506020604051808303818588803b1580156114c757600080fd5b505af11580156114db573d6000803e3d6000fd5b50505050506040513d60208110156114f257600080fd5b505191505b505b90505b9392505050565b60006115238473818e6fecd516ecc3849daf6845e3ec868087b755612ffd565b73818e6fecd516ecc3849daf6845e3ec868087b7556329589f6161154f6001600160a01b038716612fc1565b61155a57600061155c565b835b61156e876001600160a01b0316612fc1565b611578578661158e565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b856115a1886001600160a01b0316612fc1565b6115ab57876115c1565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b604080516001600160e01b031960e088901b1681526001600160a01b039485166004820152602481019390935292166044820152306064820152600160ff1b6084820152600060a48201819052734d37f28d2db99e8d35a6c725a5f1749a085850a360c483015261010060e4830152610104820152905161014480830192602092919082900301818588803b15801561165957600080fd5b505af115801561166d573d6000803e3d6000fd5b50505050506040513d602081101561168457600080fd5b5051949350505050565b60006116a2846001600160a01b0316612fc1565b156117105773c0829421c1d260bd3cb3e0f06cfe2d52db2ce3156001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b1580156116f657600080fd5b505af115801561170a573d6000803e3d6000fd5b50505050505b60007352ae12abe5d8bd778bd5397f99ca900624cfadd46001600160a01b031663bb34534c6040518163ffffffff1660e01b815260040180806c42616e636f724e6574776f726b60981b815250602001905060206040518083038186803b15801561177a57600080fd5b505afa15801561178e573d6000803e3d6000fd5b505050506040513d60208110156117a457600080fd5b5051905060606117b486866130b6565b90506117f16117cb876001600160a01b0316612fc1565b6117d557866117eb565b73c0829421c1d260bd3cb3e0f06cfe2d52db2ce3155b83612ffd565b6000826001600160a01b031663c7ba24bc838760016040518463ffffffff1660e01b81526004018080602001848152602001838152602001828103825285818151815260200191508051906020019060200280838360005b83811015611861578181015183820152602001611849565b50505050905001945050505050602060405180830381600087803b15801561188857600080fd5b505af115801561189c573d6000803e3d6000fd5b505050506040513d60208110156118b257600080fd5b505190506118c86001600160a01b038716612fc1565b156119a957604080516370a0823160e01b8152306004820152905173c0829421c1d260bd3cb3e0f06cfe2d52db2ce31591632e1a7d4d9183916370a08231916024808301926020929190829003018186803b15801561192657600080fd5b505afa15801561193a573d6000803e3d6000fd5b505050506040513d602081101561195057600080fd5b5051604080516001600160e01b031960e085901b168152600481019290925251602480830192600092919082900301818387803b15801561199057600080fd5b505af11580156119a4573d6000803e3d6000fd5b505050505b9695505050505050565b60006119c7846001600160a01b0316612fc1565b15611a355773c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b158015611a1b57600080fd5b505af1158015611a2f573d6000803e3d6000fd5b50505050505b611a84611a4a856001600160a01b0316612fc1565b611a545784611a6a565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b73794e6e91555438afc3ccf1c5076a74f42133d08d612ffd565b600073794e6e91555438afc3ccf1c5076a74f42133d08d630621b4f6611ab26001600160a01b038816612fc1565b611abc5786611ad2565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b85611ae5886001600160a01b0316612fc1565b611aef5787611b05565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b604080516001600160e01b031960e087901b1681526001600160a01b03948516600482015260248101939093529216604482015260016064820152905160848083019260209291908290030181600087803b158015611b6357600080fd5b505af1158015611b77573d6000803e3d6000fd5b505050506040513d6020811015611b8d57600080fd5b50519050611ba36001600160a01b038516612fc1565b156114f957604080516370a0823160e01b8152306004820152905173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc291632e1a7d4d9183916370a08231916024808301926020929190829003018186803b158015611c0157600080fd5b505afa158015611c15573d6000803e3d6000fd5b505050506040513d6020811015611c2b57600080fd5b5051604080516001600160e01b031960e085901b168152600481019290925251602480830192600092919082900301818387803b158015611c6b57600080fd5b505af1158015611c7f573d6000803e3d6000fd5b5050505090509392505050565b6000806001600160a01b0385166000805160206148fb83398151915214611cb4576000611cb7565b60025b6001600160a01b03861660008051602061488b83398151915214611cdc576000611cdf565b60015b0160ff16905060006000805160206148fb8339815191526001600160a01b03861614611d0c576000611d0f565b60025b6001600160a01b03861660008051602061488b83398151915214611d34576000611d37565b60015b0160ff16905081600f0b60001480611d52575080600f0b6000145b15611d62576000925050506114fc565b611d808673a2b47e3d5c44877cca798226b7b8118f9bfb7a56612ffd565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b602482015260448101869052600060648201819052915173a2b47e3d5c44877cca798226b7b8118f9bfb7a569263a6417ed6926084808201939182900301818387803b158015611df657600080fd5b505af1158015611e0a573d6000803e3d6000fd5b5050505050509392505050565b6000806001600160a01b03851660008051602061491b83398151915214611e3f576000611e42565b60035b6001600160a01b0386166000805160206148fb83398151915214611e67576000611e6a565b60025b6001600160a01b03871660008051602061488b83398151915214611e8f576000611e92565b60015b010160ff169050600060008051602061491b8339815191526001600160a01b0316856001600160a01b031614611ec9576000611ecc565b60035b6001600160a01b0386166000805160206148fb83398151915214611ef1576000611ef4565b60025b6001600160a01b03871660008051602061488b83398151915214611f19576000611f1c565b60015b010160ff16905081600f0b60001480611f38575080600f0b6000145b15611f48576000925050506114fc565b611f66867352ea46506b9cc5ef470c5bf89f17dc28bb35d85c612ffd565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517352ea46506b9cc5ef470c5bf89f17dc28bb35d85c9263a6417ed6926084808201939182900301818387803b158015611df657600080fd5b6000806001600160a01b0385166e085d4780b73119b644ae5ecd22b37614612005576000612008565b60045b6001600160a01b03861660008051602061491b8339815191521461202d576000612030565b60035b6001600160a01b0387166000805160206148fb83398151915214612055576000612058565b60025b6001600160a01b03881660008051602061488b8339815191521461207d576000612080565b60015b01010160ff16905060006e085d4780b73119b644ae5ecd22b3766001600160a01b0316856001600160a01b0316146120b95760006120bc565b60045b6001600160a01b03861660008051602061491b833981519152146120e15760006120e4565b60035b6001600160a01b0387166000805160206148fb8339815191521461210957600061210c565b60025b6001600160a01b03881660008051602061488b83398151915214612131576000612134565b60015b01010160ff16905081600f0b60001480612151575080600f0b6000145b15612161576000925050506114fc565b61217f867345f783cce6b7ff23b2ab2d70e416cdb7d6055f51612ffd565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517345f783cce6b7ff23b2ab2d70e416cdb7d6055f519263a6417ed6926084808201939182900301818387803b158015611df657600080fd5b6000806001600160a01b038516734fabb145d64652a948d72533023f6e7a623c7c5314612223576000612226565b60045b6001600160a01b03861660008051602061491b8339815191521461224b57600061224e565b60035b6001600160a01b0387166000805160206148fb83398151915214612273576000612276565b60025b6001600160a01b03881660008051602061488b8339815191521461229b57600061229e565b60015b01010160ff1690506000734fabb145d64652a948d72533023f6e7a623c7c536001600160a01b0316856001600160a01b0316146122dc5760006122df565b60045b6001600160a01b03861660008051602061491b83398151915214612304576000612307565b60035b6001600160a01b0387166000805160206148fb8339815191521461232c57600061232f565b60025b6001600160a01b03881660008051602061488b83398151915214612354576000612357565b60015b01010160ff16905081600f0b60001480612374575080600f0b6000145b15612384576000925050506114fc565b6123a2867379a8c46dea5ada233abaffd40f3a0a2b1e5a4f27612ffd565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517379a8c46dea5ada233abaffd40f3a0a2b1e5a4f279263a6417ed6926084808201939182900301818387803b158015611df657600080fd5b6000806001600160a01b0385167357ab1ec28d129707052df4df418d58a2d46d5f5114612446576000612449565b60045b6001600160a01b03861660008051602061491b8339815191521461246e576000612471565b60035b6001600160a01b0387166000805160206148fb83398151915214612496576000612499565b60025b6001600160a01b03881660008051602061488b833981519152146124be5760006124c1565b60015b01010160ff16905060007357ab1ec28d129707052df4df418d58a2d46d5f516001600160a01b0316856001600160a01b0316146124ff576000612502565b60045b6001600160a01b03861660008051602061491b8339815191521461252757600061252a565b60035b6001600160a01b0387166000805160206148fb8339815191521461254f576000612552565b60025b6001600160a01b03881660008051602061488b8339815191521461257757600061257a565b60015b01010160ff16905081600f0b60001480612597575080600f0b6000145b156125a7576000925050506114fc565b6125c58673a5407eae9ba41422680e2e00537571bcc53efbfd612ffd565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b602482015260448101869052600060648201819052915173a5407eae9ba41422680e2e00537571bcc53efbfd9263a6417ed6926084808201939182900301818387803b158015611df657600080fd5b600061264f846001600160a01b0316612fc1565b61270857600061265e856137c0565b905061266a8582612ffd565b806001600160a01b031663a0712d68846040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b1580156126b057600080fd5b505af11580156126c4573d6000803e3d6000fd5b505050506040513d60208110156126da57600080fd5b50612700905081856126fb6001600160a01b0383163063ffffffff6139aa16565b611282565b9150506114fc565b61271a836001600160a01b0316612fc1565b6127cf576000612729846137c0565b90506000612738868386611282565b9050816001600160a01b031663db006a75826040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b15801561278057600080fd5b505af1158015612794573d6000803e3d6000fd5b505050506040513d60208110156127aa57600080fd5b506127c690506001600160a01b0386163063ffffffff6139aa16565b925050506114fc565b5060009392505050565b60006001600160a01b03841660008051602061488b83398151915214156128bc57612818847306af07097c9eeb7fd685c692751d5c66db49c215612ffd565b60408051633b4da69f60e01b81523060048201526024810184905290517306af07097c9eeb7fd685c692751d5c66db49c21591633b4da69f91604480830192600092919082900301818387803b15801561287157600080fd5b505af1158015612885573d6000803e3d6000fd5b506128b592507306af07097c9eeb7fd685c692751d5c66db49c21591508590506126fb823063ffffffff6139aa16565b90506114fc565b6001600160a01b03831660008051602061488b83398151915214156127cf5760006128fc857306af07097c9eeb7fd685c692751d5c66db49c21585611282565b6040805163ef693bed60e01b81523060048201526024810183905290519192507306af07097c9eeb7fd685c692751d5c66db49c2159163ef693bed9160448082019260009290919082900301818387803b15801561295957600080fd5b505af115801561296d573d6000803e3d6000fd5b50612700925050506001600160a01b038516306139aa565b6000612999846001600160a01b0316612fc1565b612a575760006129a885613a54565b90506129b48582612ffd565b60408051636968703360e11b81526001600160a01b03871660048201526024810185905261044d6044820152905173398ec7346dcd622edc5ae82352f02be94c62d1199163d2d0e06691606480830192600092919082900301818387803b158015612a1e57600080fd5b505af1158015612a32573d6000803e3d6000fd5b5050505061270081856126fb30856001600160a01b03166139aa90919063ffffffff16565b612a69836001600160a01b0316612fc1565b6127cf576000612a7884613a54565b90506000612a87868386611282565b9050816001600160a01b031663db006a75826040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b158015612acf57600080fd5b505af1158015612ae3573d6000803e3d6000fd5b5050505080925050506114fc565b600080737079e8517594e5b21d2b9a0d17cb33f5fe2bca706001600160a01b031663d4b839926040518163ffffffff1660e01b815260040160206040518083038186803b158015612b4157600080fd5b505afa158015612b55573d6000803e3d6000fd5b505050506040513d6020811015612b6b57600080fd5b50519050612b798582612ffd565b806001600160a01b031663fe029156612b9a876001600160a01b0316612fc1565b612ba5576000612ba7565b845b604080516001600160e01b031960e085901b1681526001600160a01b03808b1660048301528916602482015260448101889052600060648201529051608480830192602092919082900301818588803b158015612c0357600080fd5b505af1158015612c17573d6000803e3d6000fd5b50505050506040513d6020811015612c2e57600080fd5b505195945050505050565b60006114f9848484613e71565b60006114f98473c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2858561423a565b60006114f98460008051602061488b833981519152858561423a565b60006114f9846000805160206148fb833981519152858561423a565b6000806001600160a01b038516738e870d67f660d95d5be530380d0ec0bd388289e114612cce576000612cd1565b60045b6001600160a01b03861660008051602061491b83398151915214612cf6576000612cf9565b60035b6001600160a01b0387166000805160206148fb83398151915214612d1e576000612d21565b60025b6001600160a01b03881660008051602061488b83398151915214612d46576000612d49565b60015b01010160ff1690506000738e870d67f660d95d5be530380d0ec0bd388289e16001600160a01b0316856001600160a01b031614612d87576000612d8a565b60045b6001600160a01b03861660008051602061491b83398151915214612daf576000612db2565b60035b6001600160a01b0387166000805160206148fb83398151915214612dd7576000612dda565b60025b6001600160a01b03881660008051602061488b83398151915214612dff576000612e02565b60015b01010160ff16905081600f0b60001480612e1f575080600f0b6000145b15612e2f576000925050506114fc565b612e4d867306364f10b501e868329afbc005b3492902d6c763612ffd565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517306364f10b501e868329afbc005b3492902d6c7639263a6417ed6926084808201939182900301818387803b158015611df657600080fd5b600082820183811015612f1d576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b90505b92915050565b600082612f3557506000612f20565b82820282848281612f4257fe5b0414612f1d5760405162461bcd60e51b81526004018080602001828103825260218152602001806148da6021913960400191505060405180910390fd5b6000612f1d83836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f00000000000081525061425a565b60006001600160a01b0382161580612ff557506001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee145b90505b919050565b61300f826001600160a01b0316612fc1565b6130b25760408051636eb1769f60e11b81523060048201526001600160a01b038381166024830152915160ff9285169163dd62ed3e916044808301926020929190829003018186803b15801561306457600080fd5b505afa158015613078573d6000803e3d6000fd5b505050506040513d602081101561308e57600080fd5b5051901c6130b2576130b26001600160a01b0383168260001963ffffffff6142fc16565b5050565b6060816001600160a01b0316836001600160a01b031614156130e75750604080516000815260208101909152612f20565b6130f9836001600160a01b0316612fc1565b156131165773c0829421c1d260bd3cb3e0f06cfe2d52db2ce31592505b613128826001600160a01b0316612fc1565b156131455773c0829421c1d260bd3cb3e0f06cfe2d52db2ce31591505b6001600160a01b038316731f573d6fb3f13d689ff844b4ce37794d79a7ff1c148061318c57506001600160a01b038216731f573d6fb3f13d689ff844b4ce37794d79a7ff1c145b156131b7576040805160038082526080820190925290602082016060803883390190505090506131d9565b60408051600580825260c08201909252906020820160a0803883390190505090505b6000806001600160a01b038516731f573d6fb3f13d689ff844b4ce37794d79a7ff1c146133a2576000606073f6e2d7f616b67e46d708e4410746e9aab3a4c518612710636b625ad960e11b6132366001600160a01b038b16612fc1565b6132405789613256565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c5b604080516001600160a01b039092166024830152600060448084019190915281518084039091018152606490920181526020820180516001600160e01b03166001600160e01b0319909416939093178352518151919290918291908083835b602083106132d45780518252601f1990920191602091820191016132b5565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303818686fa925050503d8060008114613335576040519150601f19603f3d011682016040523d82523d6000602084013e61333a565b606091505b5091509150816133625760408051600080825260208201909252905b50945050505050612f20565b80806020019051602081101561337757600080fd5b505193506001600160a01b03841661339f576040805160008082526020820190925290613356565b50505b6001600160a01b038416731f573d6fb3f13d689ff844b4ce37794d79a7ff1c14613560576000606073f6e2d7f616b67e46d708e4410746e9aab3a4c518612710636b625ad960e11b6133fc6001600160a01b038a16612fc1565b613406578861341c565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c5b604080516001600160a01b039092166024830152600060448084019190915281518084039091018152606490920181526020820180516001600160e01b03166001600160e01b0319909416939093178352518151919290918291908083835b6020831061349a5780518252601f19909201916020918201910161347b565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303818686fa925050503d80600081146134fb576040519150601f19603f3d011682016040523d82523d6000602084013e613500565b606091505b509150915081613520576040805160008082526020820190925290613356565b80806020019051602081101561353557600080fd5b505192506001600160a01b03831661355d576040805160008082526020820190925290613356565b50505b6001600160a01b038416731f573d6fb3f13d689ff844b4ce37794d79a7ff1c141561362357848360008151811061359357fe5b60200260200101906001600160a01b031690816001600160a01b03168152505081836001815181106135c157fe5b60200260200101906001600160a01b031690816001600160a01b031681525050731f573d6fb3f13d689ff844b4ce37794d79a7ff1c8360028151811061360357fe5b6001600160a01b039092166020928302919091019091015250612f209050565b6001600160a01b038516731f573d6fb3f13d689ff844b4ce37794d79a7ff1c14156136c657731f573d6fb3f13d689ff844b4ce37794d79a7ff1c8360008151811061366a57fe5b60200260200101906001600160a01b031690816001600160a01b031681525050808360018151811061369857fe5b60200260200101906001600160a01b031690816001600160a01b031681525050838360028151811061360357fe5b84836000815181106136d457fe5b60200260200101906001600160a01b031690816001600160a01b031681525050818360018151811061370257fe5b60200260200101906001600160a01b031690816001600160a01b031681525050731f573d6fb3f13d689ff844b4ce37794d79a7ff1c8360028151811061374457fe5b60200260200101906001600160a01b031690816001600160a01b031681525050808360038151811061377257fe5b60200260200101906001600160a01b031690816001600160a01b03168152505083836004815181106137a057fe5b6001600160a01b0390921660209283029190910190910152505092915050565b60006137d4826001600160a01b0316612fc1565b156137f45750734ddc2d193948926d02f9b1fe9e1daa0718270ed5612ff8565b6001600160a01b03821660008051602061488b833981519152141561382e5750735d3a536e4d6dbd6114cc1ead35777bab948e3643612ff8565b6001600160a01b038216730d8775f648430679a709e98d2b0cb6250d2887ef141561386e5750736c8c6b02e7b2be14d4fa6022dfd6d75921d90e4e612ff8565b6001600160a01b038216731985365e9f78359a9b6ad760e32412f4a445e86214156138ae575073158079ee67fce2f58472a96584a73c7ab9ac95c1612ff8565b6001600160a01b0382166000805160206148fb83398151915214156138e857507339aa39c021dfbae8fac545936693ac917d5e7563612ff8565b6001600160a01b038216732260fac5e5542a773aa44fbcfedf7c193bc2c5991415613928575073c11b1268c1a384e55c48c2391d8d480264a3a7f4612ff8565b6001600160a01b03821673e41d2489571d322189246dafa5ebde1f4699f4981415613968575073b3319f5d18bc0d84dd1b4825dcde5d5f7266d407612ff8565b6001600160a01b03821660008051602061491b83398151915214156139a2575073f650c3d88d12db855b8bf7d11be6c55a4e07dcc9612ff8565b506000919050565b60006139b583612fc1565b156139cb57506001600160a01b03811631612f20565b826001600160a01b03166370a08231836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015613a2157600080fd5b505afa158015613a35573d6000803e3d6000fd5b505050506040513d6020811015613a4b57600080fd5b50519050612f20565b6000613a68826001600160a01b0316612fc1565b15613a885750733a3a65aab0dd2a17e3f1947ba16138cd37d08c04612ff8565b6001600160a01b03821660008051602061488b8339815191521415613ac2575073fc1e690f61efd961294b3e1ce3313fbd8aa4f85d612ff8565b6001600160a01b0382166000805160206148fb8339815191521415613afc5750739ba00d6856a4edf4665bca2c2309936572473b7e612ff8565b6001600160a01b0382167357ab1ec28d129707052df4df418d58a2d46d5f511415613b3c575073625ae63000f46200499120b906716420bd059240612ff8565b6001600160a01b038216734fabb145d64652a948d72533023f6e7a623c7c531415613b7c5750736ee0f7bb50a54ab5253da0667b0dc2ee526c30a8612ff8565b6001600160a01b0382166e085d4780b73119b644ae5ecd22b3761415613bb75750734da9b813057d04baef4e5800e36083717b4a0341612ff8565b6001600160a01b03821660008051602061491b8339815191521415613bf157507371fc860f7d3a592a4a98740e39db31d25db65ae8612ff8565b6001600160a01b038216730d8775f648430679a709e98d2b0cb6250d2887ef1415613c31575073e1ba0fb44ccb0d11b80f92f4f8ed94ca3ff51d00612ff8565b6001600160a01b03821673dd974d5c2e2928dea5f71b9825b8b646686bd2001415613c715750739d91be44c06d373a8a226e1f3b146956083803eb612ff8565b6001600160a01b0382167380fb784b7ed66730e8b1dbd9820afd29931aab031415613cb15750737d2d3688df45ce7c552e19c27e007673da9204b8612ff8565b6001600160a01b03821673514910771af9ca656af840dff83e8264ecf986ca1415613cf1575073a64bd6c70cb9051f6a9ba1f163fdc07e0dfb5f84612ff8565b6001600160a01b038216730f5d2fb29fb7d3cfee444a200298f468908cc9421415613d315750736fce4a401b6b80ace52baaefe4421bd188e76f6f612ff8565b6001600160a01b038216739f8f72aa9304c8b593d555f12ef6589cc3a579a21415613d715750737deb5e830be29f91e298ba5ff1356bb7f8146998612ff8565b6001600160a01b038216731985365e9f78359a9b6ad760e32412f4a445e8621415613db157507371010a9d003445ac60c4e6a7017c1e89a477b438612ff8565b6001600160a01b03821673c011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f1415613df1575073328c4c80bc7aca0834db37e6600a6c49e12da4de612ff8565b6001600160a01b038216732260fac5e5542a773aa44fbcfedf7c193bc2c5991415613e31575073fc4b8ed459e00e5400be803a9bb3954234fd50e3612ff8565b6001600160a01b03821673e41d2489571d322189246dafa5ebde1f4699f49814156139a25750736fb0855c404e09c47c3fbca25f08d4e41f9f062f612ff8565b6000613e85846001600160a01b0316612fc1565b15613ef35773c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b158015613ed957600080fd5b505af1158015613eed573d6000803e3d6000fd5b50505050505b6000613f07856001600160a01b0316612fc1565b613f115784613f27565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b90506000613f3d856001600160a01b0316612fc1565b613f475784613f5d565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b6040805163e6a4390560e01b81526001600160a01b038581166004830152831660248201529051919250600091735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f9163e6a43905916044808301926020929190829003018186803b158015613fc557600080fd5b505afa158015613fd9573d6000803e3d6000fd5b505050506040513d6020811015613fef57600080fd5b5051905061400e6001600160a01b03821684848863ffffffff6143d216565b935061402a6001600160a01b038416828763ffffffff61448916565b50816001600160a01b0316836001600160a01b031610156140c3576040805163022c0d9f60e01b815260006004820181905260248201879052306044830152608060648301526084820181905291516001600160a01b0384169263022c0d9f9260c4808201939182900301818387803b1580156140a657600080fd5b505af11580156140ba573d6000803e3d6000fd5b5050505061413d565b6040805163022c0d9f60e01b815260048101869052600060248201819052306044830152608060648301526084820181905291516001600160a01b0384169263022c0d9f9260c4808201939182900301818387803b15801561412457600080fd5b505af1158015614138573d6000803e3d6000fd5b505050505b61414f866001600160a01b0316612fc1565b1561423057604080516370a0823160e01b8152306004820152905173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc291632e1a7d4d9183916370a08231916024808301926020929190829003018186803b1580156141ad57600080fd5b505afa1580156141c1573d6000803e3d6000fd5b505050506040513d60208110156141d757600080fd5b5051604080516001600160e01b031960e085901b168152600481019290925251602480830192600092919082900301818387803b15801561421757600080fd5b505af115801561422b573d6000803e3d6000fd5b505050505b5050509392505050565b6000614251848461424c888887613e71565b613e71565b95945050505050565b600081836142e65760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156142ab578181015183820152602001614293565b50505050905090810190601f1680156142d85780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385816142f257fe5b0495945050505050565b61430583612fc1565b6143cd57600081118015614393575060408051636eb1769f60e11b81523060048201526001600160a01b038481166024830152915160009286169163dd62ed3e916044808301926020929190829003018186803b15801561436557600080fd5b505afa158015614379573d6000803e3d6000fd5b505050506040513d602081101561438f57600080fd5b5051115b156143b3576143b36001600160a01b03841683600063ffffffff61450416565b6143cd6001600160a01b038416838363ffffffff61450416565b505050565b6000806143ee6001600160a01b0386168763ffffffff6139aa16565b9050600061440b6001600160a01b0386168863ffffffff6139aa16565b90506000614421856103e563ffffffff612f2616565b90506000614435828463ffffffff612f2616565b9050600061445b8361444f876103e863ffffffff612f2616565b9063ffffffff612ec316565b9050801561447857614473828263ffffffff612f7f16565b61447b565b60005b9a9950505050505050505050565b600081614498575060016114fc565b6144a184612fc1565b156144e2576040516001600160a01b0384169083156108fc029084906000818181858888f193505050501580156144dc573d6000803e3d6000fd5b506114fc565b6144fc6001600160a01b038516848463ffffffff61461716565b5060016114fc565b80158061458a575060408051636eb1769f60e11b81523060048201526001600160a01b03848116602483015291519185169163dd62ed3e91604480820192602092909190829003018186803b15801561455c57600080fd5b505afa158015614570573d6000803e3d6000fd5b505050506040513d602081101561458657600080fd5b5051155b6145c55760405162461bcd60e51b81526004018080602001828103825260368152602001806149656036913960400191505060405180910390fd5b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b1790526143cd908490614665565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526143cd9084905b614677826001600160a01b0316614823565b6146c8576040805162461bcd60e51b815260206004820152601f60248201527f5361666545524332303a2063616c6c20746f206e6f6e2d636f6e747261637400604482015290519081900360640190fd5b60006060836001600160a01b0316836040518082805190602001908083835b602083106147065780518252601f1990920191602091820191016146e7565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114614768576040519150601f19603f3d011682016040523d82523d6000602084013e61476d565b606091505b5091509150816147c4576040805162461bcd60e51b815260206004820181905260248201527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564604482015290519081900360640190fd5b80511561481d578080602001905160208110156147e057600080fd5b505161481d5760405162461bcd60e51b815260040180806020018281038252602a81526020018061493b602a913960400191505060405180910390fd5b50505050565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47081811480159061485757508115155b949350505050565b6040518061024001604052806012905b61488881526020019060019003908161486f5790505090565bfefe0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f4f6e6553706c69743a20646973747269627574696f6e2073686f756c6420636f6e7461696e206e6f6e2d7a65726f73536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec75361666545524332303a204552433230206f7065726174696f6e20646964206e6f7420737563636565645361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f20746f206e6f6e2d7a65726f20616c6c6f77616e63654f6e6553706c69743a20446973747269627574696f6e2061727261792073686f756c64206e6f74206578636565642072657365727665732061727261792073697a65a265627a7a723158204781288028f83a88cf5954bcec8ff13b251aeb217faf21dd0b26af86c124971364736f6c63430005110032 \ No newline at end of file diff --git a/OneSplit.full.sol b/OneSplit.full.sol index 6ba7e25..23162f5 100644 --- a/OneSplit.full.sol +++ b/OneSplit.full.sol @@ -139,6 +139,10 @@ contract IOneSplitConsts { uint256 public constant FLAG_DISABLE_ALL_SPLIT_SOURCES = 0x20000000; uint256 public constant FLAG_DISABLE_ALL_WRAP_SOURCES = 0x40000000; uint256 public constant FLAG_DISABLE_CURVE_PAX = 0x80000000; + uint256 public constant FLAG_DISABLE_UNISWAP_POOL_TOKEN = 0x100000000; + uint256 public constant FLAG_DISABLE_BALANCER_POOL_TOKEN = 0x200000000; + uint256 public constant FLAG_DISABLE_CURVE_ZAP = 0x400000000; + uint256 public constant FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN = 0x800000000; } @@ -353,6 +357,10 @@ interface IUniswapExchange { uint256 deadline, address tokenAddr ) external returns (uint256 tokensBought); + + function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); + + function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256); } // File: contracts/interface/IUniswapFactory.sol @@ -363,6 +371,8 @@ pragma solidity ^0.5.0; interface IUniswapFactory { function getExchange(IERC20 token) external view returns (IUniswapExchange exchange); + + function getToken(address exchange) external view returns (IERC20 token); } // File: contracts/interface/IKyberNetworkContract.sol @@ -539,8 +549,14 @@ interface ICurve { // solium-disable-next-line mixedcase function get_dy_underlying(int128 i, int128 j, uint256 dx) external view returns(uint256 dy); + function get_virtual_price() external view returns(uint256); + // solium-disable-next-line mixedcase function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 minDy) external; + + function coins(int128 arg0) external view returns (address); + + function balances(int128 arg0) external view returns (uint256); } // File: contracts/interface/IChai.sol @@ -4143,42 +4159,107 @@ contract OneSplitWeth is OneSplitBaseWrap { } } -// File: contracts/OneSplit.sol +// File: contracts/interface/IBFactory.sol pragma solidity ^0.5.0; +interface IBFactory { + function isBPool(address b) external view returns (bool); +} +// File: contracts/interface/IBPool.sol +pragma solidity ^0.5.0; +contract BConst { + uint public constant EXIT_FEE = 0; +} +contract IBMath is BConst { + function calcPoolOutGivenSingleIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint tokenAmountIn, + uint swapFee + ) + public + pure returns (uint poolAmountOut); +} +contract IBPool is IERC20, IBMath { + function joinPool(uint poolAmountOut, uint[] calldata maxAmountsIn) external; -//import "./OneSplitSmartToken.sol"; + function exitPool(uint poolAmountIn, uint[] calldata minAmountsOut) external; + function joinswapExternAmountIn(address tokenIn, uint tokenAmountIn, uint minPoolAmountOut) external returns (uint poolAmountOut); -contract OneSplitViewWrap is - OneSplitViewWrapBase, - OneSplitMultiPathView, - OneSplitChaiView, - OneSplitBdaiView, - OneSplitAaveView, - OneSplitFulcrumView, - OneSplitCompoundView, - OneSplitIearnView, - OneSplitIdleView, - OneSplitWethView - //OneSplitSmartTokenView -{ - IOneSplitView public oneSplitView; + function getCurrentTokens() external view returns (address[] memory tokens); - constructor(IOneSplitView _oneSplit) public { - oneSplitView = _oneSplit; + function getBalance(address token) external view returns (uint); + + function getNormalizedWeight(address token) external view returns (uint); + + function getDenormalizedWeight(address token) external view returns (uint); + + function getTotalDenormalizedWeight() external view returns (uint); + + function getSwapFee() external view returns (uint); +} + +// File: contracts/OneSplitBalancerPoolToken.sol + +pragma solidity ^0.5.0; + + + + + +contract OneSplitBalancerPoolTokenBase { + using SafeMath for uint256; + + // todo: factory for Bronze release + // may be changed in future + IBFactory bFactory = IBFactory(0x9424B1412450D0f8Fc2255FAf6046b98213B76Bd); + + struct TokenWithWeight { + IERC20 token; + uint256 reserveBalance; + uint256 denormalizedWeight; + } + + struct PoolTokenDetails { + TokenWithWeight[] tokens; + uint256 totalWeight; + uint256 totalSupply; + } + + function _getPoolDetails(IBPool poolToken) + internal + view + returns(PoolTokenDetails memory details) + { + address[] memory currentTokens = poolToken.getCurrentTokens(); + details.tokens = new TokenWithWeight[](currentTokens.length); + details.totalWeight = poolToken.getTotalDenormalizedWeight(); + details.totalSupply = poolToken.totalSupply(); + for (uint256 i = 0; i < details.tokens.length; i++) { + details.tokens[i].token = IERC20(currentTokens[i]); + details.tokens[i].denormalizedWeight = poolToken.getDenormalizedWeight(currentTokens[i]); + details.tokens[i].reserveBalance = poolToken.getBalance(currentTokens[i]); + } } +} + + +contract OneSplitBalancerPoolTokenView is OneSplitViewWrapBase, OneSplitBalancerPoolTokenBase { + function getExpectedReturn( IERC20 fromToken, IERC20 toToken, @@ -4188,7 +4269,7 @@ contract OneSplitViewWrap is ) public view - returns( + returns ( uint256 returnAmount, uint256[] memory distribution ) @@ -4197,6 +4278,62 @@ contract OneSplitViewWrap is return (amount, new uint256[](DEXES_COUNT)); } + + if (!flags.check(FLAG_DISABLE_BALANCER_POOL_TOKEN)) { + bool isPoolTokenFrom = bFactory.isBPool(address(fromToken)); + bool isPoolTokenTo = bFactory.isBPool(address(toToken)); + + if (isPoolTokenFrom && isPoolTokenTo) { + ( + uint256 returnETHAmount, + uint256[] memory poolTokenFromDistribution + ) = _getExpectedReturnFromBalancerPoolToken( + fromToken, + ETH_ADDRESS, + amount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + + ( + uint256 returnPoolTokenToAmount, + uint256[] memory poolTokenToDistribution + ) = _getExpectedReturnToBalancerPoolToken( + ETH_ADDRESS, + toToken, + returnETHAmount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + + for (uint i = 0; i < poolTokenToDistribution.length; i++) { + poolTokenFromDistribution[i] |= poolTokenToDistribution[i] << 128; + } + + return (returnPoolTokenToAmount, poolTokenFromDistribution); + } + + if (isPoolTokenFrom) { + return _getExpectedReturnFromBalancerPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _getExpectedReturnToBalancerPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + } + return super.getExpectedReturn( fromToken, toToken, @@ -4206,96 +4343,2571 @@ contract OneSplitViewWrap is ); } - function _getExpectedReturnFloor( - IERC20 fromToken, + function _getExpectedReturnFromBalancerPoolToken( + IERC20 poolToken, IERC20 toToken, uint256 amount, uint256 parts, uint256 flags ) - internal + private view - returns( + returns ( uint256 returnAmount, uint256[] memory distribution ) { - return oneSplitView.getExpectedReturn( - fromToken, - toToken, - amount, - parts, - flags + distribution = new uint256[](DEXES_COUNT); + + IBPool bToken = IBPool(address(poolToken)); + address[] memory currentTokens = bToken.getCurrentTokens(); + + uint256 pAiAfterExitFee = amount.sub( + amount.mul(bToken.EXIT_FEE()) ); - } -} + uint256 ratio = pAiAfterExitFee.mul(1e18).div(poolToken.totalSupply()); + for (uint i = 0; i < currentTokens.length; i++) { + uint256 tokenAmountOut = bToken.getBalance(currentTokens[i]).mul(ratio).div(1e18); + if (currentTokens[i] == address(toToken)) { + returnAmount = returnAmount.add(tokenAmountOut); + continue; + } -contract OneSplitWrap is - OneSplitBaseWrap, - OneSplitMultiPath, - OneSplitChai, - OneSplitBdai, - OneSplitAave, - OneSplitFulcrum, - OneSplitCompound, - OneSplitIearn, - OneSplitIdle, - OneSplitWeth - //OneSplitSmartToken -{ - IOneSplitView public oneSplitView; - IOneSplit public oneSplit; + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + IERC20(currentTokens[i]), + toToken, + tokenAmountOut, + parts, + flags + ); - constructor(IOneSplitView _oneSplitView, IOneSplit _oneSplit) public { - oneSplitView = _oneSplitView; - oneSplit = _oneSplit; - } + returnAmount = returnAmount.add(ret); - function() external payable { - // solium-disable-next-line security/no-tx-origin - require(msg.sender != tx.origin); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); } - function getExpectedReturn( + function _getExpectedReturnToBalancerPoolToken( IERC20 fromToken, - IERC20 toToken, + IERC20 poolToken, uint256 amount, uint256 parts, - uint256 flags // 1 - Uniswap, 2 - Kyber, 4 - Bancor, 8 - Oasis, 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI + uint256 flags ) - public + private view - returns( - uint256 /*returnAmount*/, - uint256[] memory /*distribution*/ + returns ( + uint256 minFundAmount, + uint256[] memory distribution ) { - return oneSplitView.getExpectedReturn( + distribution = new uint256[](DEXES_COUNT); + minFundAmount = uint256(-1); + + PoolTokenDetails memory details = _getPoolDetails(IBPool(address(poolToken))); + + uint256[] memory tokenAmounts = new uint256[](details.tokens.length); + uint256[] memory dist; + uint256[] memory fundAmounts = new uint256[](details.tokens.length); + + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount.mul( + details.tokens[i].denormalizedWeight + ).div(details.totalWeight); + + if (details.tokens[i].token != fromToken) { + (tokenAmounts[i], dist) = getExpectedReturn( + fromToken, + details.tokens[i].token, + exchangeAmount, + parts, + flags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } else { + tokenAmounts[i] = exchangeAmount; + } + + fundAmounts[i] = tokenAmounts[i] + .mul(details.totalSupply) + .div(details.tokens[i].reserveBalance); + + if (fundAmounts[i] < minFundAmount) { + minFundAmount = fundAmounts[i]; + } + } + +// uint256 _minFundAmount = minFundAmount; +// uint256 swapFee = IBPool(address(poolToken)).getSwapFee(); + // Swap leftovers for PoolToken +// for (uint i = 0; i < details.tokens.length; i++) { +// if (_minFundAmount == fundAmounts[i]) { +// continue; +// } +// +// uint256 leftover = tokenAmounts[i].sub( +// fundAmounts[i].mul(details.tokens[i].reserveBalance).div(details.totalSupply) +// ); +// +// uint256 tokenRet = IBPool(address(poolToken)).calcPoolOutGivenSingleIn( +// details.tokens[i].reserveBalance, +// details.tokens[i].denormalizedWeight, +// details.totalSupply, +// details.totalWeight, +// leftover, +// swapFee +// ); +// +// minFundAmount = minFundAmount.add(tokenRet); +// } + + return (minFundAmount, distribution); + } + +} + + +contract OneSplitBalancerPoolToken is OneSplitBaseWrap, OneSplitBalancerPoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!flags.check(FLAG_DISABLE_BALANCER_POOL_TOKEN)) { + bool isPoolTokenFrom = bFactory.isBPool(address(fromToken)); + bool isPoolTokenTo = bFactory.isBPool(address(toToken)); + + if (isPoolTokenFrom && isPoolTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 ethBalanceBefore = address(this).balance; + + _swapFromBalancerPoolToken( + fromToken, + ETH_ADDRESS, + amount, + dist, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 ethBalanceAfter = address(this).balance; + + return _swapToBalancerPoolToken( + ETH_ADDRESS, + toToken, + ethBalanceAfter.sub(ethBalanceBefore), + dist, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + + if (isPoolTokenFrom) { + return _swapFromBalancerPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _swapToBalancerPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + } + + return super._swap( fromToken, toToken, amount, - parts, + distribution, flags ); } - function swap( - IERC20 fromToken, + function _swapFromBalancerPoolToken( + IERC20 poolToken, IERC20 toToken, uint256 amount, - uint256 minReturn, - uint256[] memory distribution, // [Uniswap, Kyber, Bancor, Oasis] - uint256 flags // 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI - ) public payable { - fromToken.universalTransferFrom(msg.sender, address(this), amount); + uint256[] memory distribution, + uint256 flags + ) private { - _swap(fromToken, toToken, amount, distribution, flags); + IBPool bToken = IBPool(address(poolToken)); - uint256 returnAmount = toToken.universalBalanceOf(address(this)); - require(returnAmount >= minReturn, "OneSplit: actual return amount is less than minReturn"); - toToken.universalTransfer(msg.sender, returnAmount); - fromToken.universalTransfer(msg.sender, fromToken.universalBalanceOf(address(this))); + address[] memory currentTokens = bToken.getCurrentTokens(); + + uint256 ratio = amount.sub( + amount.mul(bToken.EXIT_FEE()) + ).mul(1e18).div(poolToken.totalSupply()); + + uint256[] memory minAmountsOut = new uint256[](currentTokens.length); + for (uint i = 0; i < currentTokens.length; i++) { + minAmountsOut[i] = bToken.getBalance(currentTokens[i]).mul(ratio).div(1e18).mul(995).div(1000); // 0.5% slippage; + } + + bToken.exitPool(amount, minAmountsOut); + + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < currentTokens.length; i++) { + + if (currentTokens[i] == address(toToken)) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + uint256 exchangeTokenAmount = IERC20(currentTokens[i]).balanceOf(address(this)); + + this.swap( + IERC20(currentTokens[i]), + toToken, + exchangeTokenAmount, + 0, + dist, + flags + ); + } + + } + + function _swapToBalancerPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + uint256[] memory dist = new uint256[](distribution.length); + uint256 minFundAmount = uint256(-1); + + PoolTokenDetails memory details = _getPoolDetails(IBPool(address(poolToken))); + + uint256[] memory maxAmountsIn = new uint256[](details.tokens.length); + uint256 curFundAmount; + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].denormalizedWeight) + .div(details.totalWeight); + + if (details.tokens[i].token != fromToken) { + uint256 tokenBalanceBefore = details.tokens[i].token.balanceOf(address(this)); + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + fromToken, + details.tokens[i].token, + exchangeAmount, + 0, + dist, + flags + ); + + uint256 tokenBalanceAfter = details.tokens[i].token.balanceOf(address(this)); + + curFundAmount = ( + tokenBalanceAfter.sub(tokenBalanceBefore) + ).mul(details.totalSupply).div(details.tokens[i].reserveBalance); + } else { + curFundAmount = ( + exchangeAmount + ).mul(details.totalSupply).div(details.tokens[i].reserveBalance); + } + + if (curFundAmount < minFundAmount) { + minFundAmount = curFundAmount; + } + + maxAmountsIn[i] = uint256(-1); + _infiniteApproveIfNeeded(details.tokens[i].token, address(poolToken)); + } + + // todo: check for vulnerability + IBPool(address(poolToken)).joinPool(minFundAmount, maxAmountsIn); + + // Return leftovers + for (uint i = 0; i < details.tokens.length; i++) { + details.tokens[i].token.universalTransfer(msg.sender, details.tokens[i].token.balanceOf(address(this))); + } + } +} + +// File: contracts/OneSplitUniswapPoolToken.sol + +pragma solidity ^0.5.0; + + + + + +contract OneSplitUniswapPoolTokenBase { + using SafeMath for uint256; + + IUniswapFactory constant uniswapFactory = IUniswapFactory(0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95); + + function isLiquidityPool(IERC20 token) internal view returns (bool) { + return address(uniswapFactory.getToken(address(token))) != address(0); + } + + function getMaxPossibleFund( + IERC20 poolToken, + IERC20 uniswapToken, + uint256 tokenAmount, + uint256 existEthAmount + ) + internal + view + returns ( + uint256, + uint256 + ) + { + uint256 ethReserve = address(poolToken).balance; + uint256 totalLiquidity = poolToken.totalSupply(); + uint256 tokenReserve = uniswapToken.balanceOf(address(poolToken)); + + uint256 possibleEthAmount = ethReserve.mul( + tokenAmount.sub(1) + ).div(tokenReserve); + + if (existEthAmount > possibleEthAmount) { + return ( + possibleEthAmount, + possibleEthAmount.mul(totalLiquidity).div(ethReserve) + ); + } + + return ( + existEthAmount, + existEthAmount.mul(totalLiquidity).div(ethReserve) + ); + } + +} + + +contract OneSplitUniswapPoolTokenView is OneSplitViewWrapBase, OneSplitUniswapPoolTokenBase { + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + public + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!flags.check(FLAG_DISABLE_UNISWAP_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + ( + uint256 returnETHAmount, + uint256[] memory poolTokenFromDistribution + ) = _getExpectedReturnFromPoolToken( + fromToken, + ETH_ADDRESS, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + ( + uint256 returnPoolTokenToAmount, + uint256[] memory poolTokenToDistribution + ) = _getExpectedReturnToPoolToken( + ETH_ADDRESS, + toToken, + returnETHAmount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + for (uint i = 0; i < poolTokenToDistribution.length; i++) { + poolTokenFromDistribution[i] |= poolTokenToDistribution[i] << 128; + } + + return (returnPoolTokenToAmount, poolTokenFromDistribution); + } + + if (isPoolTokenFrom) { + return _getExpectedReturnFromPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _getExpectedReturnToPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } + + function _getExpectedReturnFromPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + + distribution = new uint256[](DEXES_COUNT); + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 totalSupply = poolToken.totalSupply(); + + uint256 ethReserve = address(poolToken).balance; + uint256 ethAmount = amount.mul(ethReserve).div(totalSupply); + + if (!toToken.isETH()) { + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + ETH_ADDRESS, + toToken, + ethAmount, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j]; + } + } else { + returnAmount = returnAmount.add(ethAmount); + } + + uint256 tokenReserve = uniswapToken.balanceOf(address(poolToken)); + uint256 exchangeTokenAmount = amount.mul(tokenReserve).div(totalSupply); + + if (toToken != uniswapToken) { + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + uniswapToken, + toToken, + exchangeTokenAmount, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << 8; + } + } else { + returnAmount = returnAmount.add(exchangeTokenAmount); + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + + distribution = new uint256[](DEXES_COUNT); + + uint256[] memory dist = new uint256[](DEXES_COUNT); + + uint256 ethAmount; + uint256 partAmountForEth = amount.div(2); + if (!fromToken.isETH()) { + (ethAmount, dist) = super.getExpectedReturn( + fromToken, + ETH_ADDRESS, + partAmountForEth, + parts, + flags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j]; + } + } else { + ethAmount = partAmountForEth; + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 tokenAmount; + uint256 partAmountForToken = amount.sub(partAmountForEth); + if (fromToken != uniswapToken) { + (tokenAmount, dist) = super.getExpectedReturn( + fromToken, + uniswapToken, + partAmountForToken, + parts, + flags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << 8; + } + } else { + tokenAmount = partAmountForToken; + } + + (, returnAmount) = getMaxPossibleFund( + poolToken, + uniswapToken, + tokenAmount, + ethAmount + ); + + return ( + returnAmount, + distribution + ); + } + +} + + +contract OneSplitUniswapPoolToken is OneSplitBaseWrap, OneSplitUniswapPoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!flags.check(FLAG_DISABLE_UNISWAP_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 ethBalanceBefore = address(this).balance; + + _swapFromPoolToken( + fromToken, + ETH_ADDRESS, + amount, + dist, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 ethBalanceAfter = address(this).balance; + + return _swapToPoolToken( + ETH_ADDRESS, + toToken, + ethBalanceAfter.sub(ethBalanceBefore), + dist, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenFrom) { + return _swapFromPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _swapToPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + flags + ); + } + + function _swapFromPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + + uint256[] memory dist = new uint256[](distribution.length); + + ( + uint256 ethAmount, + uint256 exchangeTokenAmount + ) = IUniswapExchange(address(poolToken)).removeLiquidity( + amount, + 1, + 1, + now.add(1800) + ); + + if (!toToken.isETH()) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j]) & 0xFF; + } + + super._swap( + ETH_ADDRESS, + toToken, + ethAmount, + dist, + flags + ); + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + if (toToken != uniswapToken) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> 8) & 0xFF; + } + + super._swap( + uniswapToken, + toToken, + exchangeTokenAmount, + dist, + flags + ); + } + } + + function _swapToPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + uint256[] memory dist = new uint256[](distribution.length); + + uint256 partAmountForEth = amount.div(2); + if (!fromToken.isETH()) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j]) & 0xFF; + } + + super._swap( + fromToken, + ETH_ADDRESS, + partAmountForEth, + dist, + flags + ); + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 partAmountForToken = amount.sub(partAmountForEth); + if (fromToken != uniswapToken) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> 8) & 0xFF; + } + + super._swap( + fromToken, + uniswapToken, + partAmountForToken, + dist, + flags + ); + + _infiniteApproveIfNeeded(uniswapToken, address(poolToken)); + } + + uint256 ethBalance = address(this).balance; + uint256 tokenBalance = uniswapToken.balanceOf(address(this)); + + (uint256 ethAmount, uint256 returnAmount) = getMaxPossibleFund( + poolToken, + uniswapToken, + tokenBalance, + ethBalance + ); + + IUniswapExchange(address(poolToken)).addLiquidity.value(ethAmount)( + returnAmount.mul(995).div(1000), // 0.5% slippage + uint256(-1), // todo: think about another value + now.add(1800) + ); + + // todo: do we need to check difference between balance before and balance after? + uniswapToken.universalTransfer(msg.sender, uniswapToken.balanceOf(address(this))); + ETH_ADDRESS.universalTransfer(msg.sender, address(this).balance); + } +} + +// File: contracts/OneSplitCurvePoolToken.sol + +pragma solidity ^0.5.0; + + + + +contract OneSplitCurvePoolTokenBase { + using SafeMath for uint256; + using UniversalERC20 for IERC20; + + IERC20 constant curveSusdToken = IERC20(0xC25a3A3b969415c80451098fa907EC722572917F); + IERC20 constant curveIearnToken = IERC20(0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8); + IERC20 constant curveCompoundToken = IERC20(0x845838DF265Dcd2c412A1Dc9e959c7d08537f8a2); + IERC20 constant curveUsdtToken = IERC20(0x9fC689CCaDa600B6DF723D9E47D84d76664a1F23); + IERC20 constant curveBinanceToken = IERC20(0x3B3Ac5386837Dc563660FB6a0937DFAa5924333B); + IERC20 constant curvePaxToken = IERC20(0xD905e2eaeBe188fc92179b6350807D8bd91Db0D8); + IERC20 constant curveRenBtcToken = IERC20(0x7771F704490F9C0C3B06aFe8960dBB6c58CBC812); + IERC20 constant curveTBtcToken = IERC20(0x1f2a662FB513441f06b8dB91ebD9a1466462b275); + + ICurve constant curveSusd = ICurve(0xA5407eAE9Ba41422680e2e00537571bcC53efBfD); + ICurve constant curveIearn = ICurve(0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51); + ICurve constant curveCompound = ICurve(0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56); + ICurve constant curveUsdt = ICurve(0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C); + ICurve constant curveBinance = ICurve(0x79a8C46DeA5aDa233ABaFFD40F3A0A2B1e5A4F27); + ICurve constant curvePax = ICurve(0x06364f10B501e868329afBc005b3492902d6C763); + ICurve constant curveRenBtc = ICurve(0x8474c1236F0Bc23830A23a41aBB81B2764bA9f4F); + ICurve constant curveTBtc = ICurve(0x9726e9314eF1b96E45f40056bEd61A088897313E); + + struct CurveTokenInfo { + IERC20 token; + uint256 weightedReserveBalance; + } + + struct CurveInfo { + ICurve curve; + uint256 tokenCount; + } + + struct CurvePoolTokenDetails { + CurveTokenInfo[] tokens; + uint256 totalWeightedBalance; + } + + function _isPoolToken(IERC20 token) + internal + pure + returns (bool) + { + if ( + token == curveSusdToken || + token == curveIearnToken || + token == curveCompoundToken || + token == curveUsdtToken || + token == curveBinanceToken || + token == curvePaxToken || + token == curveRenBtcToken || + token == curveTBtcToken + ) { + return true; + } + return false; + } + + function _getCurve(IERC20 poolToken) + internal + pure + returns (CurveInfo memory curveInfo) + { + if (poolToken == curveSusdToken) { + curveInfo.curve = curveSusd; + curveInfo.tokenCount = 4; + return curveInfo; + } + + if (poolToken == curveIearnToken) { + curveInfo.curve = curveIearn; + curveInfo.tokenCount = 4; + return curveInfo; + } + + if (poolToken == curveCompoundToken) { + curveInfo.curve = curveCompound; + curveInfo.tokenCount = 2; + return curveInfo; + } + + if (poolToken == curveUsdtToken) { + curveInfo.curve = curveUsdt; + curveInfo.tokenCount = 3; + return curveInfo; + } + + if (poolToken == curveBinanceToken) { + curveInfo.curve = curveBinance; + curveInfo.tokenCount = 4; + return curveInfo; + } + + if (poolToken == curvePaxToken) { + curveInfo.curve = curvePax; + curveInfo.tokenCount = 4; + return curveInfo; + } + + if (poolToken == curveRenBtcToken) { + curveInfo.curve = curveRenBtc; + curveInfo.tokenCount = 2; + return curveInfo; + } + + if (poolToken == curveTBtcToken) { + curveInfo.curve = curveTBtc; + curveInfo.tokenCount = 3; + return curveInfo; + } + + revert(); + } + + function _getCurveCalcTokenAmountSelector(uint256 tokenCount) + internal + pure + returns (bytes4) + { + return bytes4(keccak256(abi.encodePacked( + "calc_token_amount(uint256[", uint8(48 + tokenCount) ,"],bool)" + ))); + } + + function _getCurveRemoveLiquiditySelector(uint256 tokenCount) + internal + pure + returns (bytes4) + { + return bytes4(keccak256(abi.encodePacked( + "remove_liquidity(uint256,uint256[", uint8(48 + tokenCount) ,"])" + ))); + } + + function _getCurveAddLiquiditySelector(uint256 tokenCount) + internal + pure + returns (bytes4) + { + return bytes4(keccak256(abi.encodePacked( + "add_liquidity(uint256[", uint8(48 + tokenCount) ,"],uint256)" + ))); + } + + function _getPoolDetails(ICurve curve, uint256 tokenCount) + internal + view + returns(CurvePoolTokenDetails memory details) + { + details.tokens = new CurveTokenInfo[](tokenCount); + for (uint256 i = 0; i < tokenCount; i++) { + details.tokens[i].token = IERC20(curve.coins(int128(i))); + details.tokens[i].weightedReserveBalance = curve.balances(int128(i)) + .mul(1e18).div(10 ** details.tokens[i].token.universalDecimals()); + details.totalWeightedBalance = details.totalWeightedBalance.add( + details.tokens[i].weightedReserveBalance + ); + } + } +} + + +contract OneSplitCurvePoolTokenView is OneSplitViewWrapBase, OneSplitCurvePoolTokenBase { + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + public + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!flags.check(FLAG_DISABLE_CURVE_ZAP)) { + if (_isPoolToken(fromToken)) { + return _getExpectedReturnFromCurvePoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_CURVE_ZAP + ); + } + + if (_isPoolToken(toToken)) { + return _getExpectedReturnToCurvePoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_CURVE_ZAP + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } + + function _getExpectedReturnFromCurvePoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + CurveInfo memory curveInfo = _getCurve(poolToken); + uint256 totalSupply = poolToken.totalSupply(); + for (uint i = 0; i < curveInfo.tokenCount; i++) { + IERC20 coin = IERC20(curveInfo.curve.coins(int128(i))); + + uint256 tokenAmountOut = curveInfo.curve.balances(int128(i)) + .mul(amount) + .div(totalSupply); + + if (coin == toToken) { + returnAmount = returnAmount.add(tokenAmountOut); + continue; + } + + (uint256 ret, uint256[] memory dist) = this.getExpectedReturn( + coin, + toToken, + tokenAmountOut, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToCurvePoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + CurveInfo memory curveInfo = _getCurve(poolToken); + CurvePoolTokenDetails memory details = _getPoolDetails( + curveInfo.curve, + curveInfo.tokenCount + ); + + bytes memory tokenAmounts; + for (uint i = 0; i < curveInfo.tokenCount; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].weightedReserveBalance) + .div(details.totalWeightedBalance); + + if (details.tokens[i].token == fromToken) { + tokenAmounts = abi.encodePacked(tokenAmounts, exchangeAmount); + continue; + } + + (uint256 tokenAmount, uint256[] memory dist) = this.getExpectedReturn( + fromToken, + details.tokens[i].token, + exchangeAmount, + parts, + flags + ); + + tokenAmounts = abi.encodePacked(tokenAmounts, tokenAmount); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + (bool success, bytes memory data) = address(curveInfo.curve).staticcall( + abi.encodePacked( + _getCurveCalcTokenAmountSelector(curveInfo.tokenCount), + tokenAmounts, + uint256(1) + ) + ); + + require(success, "calc_token_amount failed"); + + return (abi.decode(data, (uint256)), distribution); + } +} + + +contract OneSplitCurvePoolToken is OneSplitBaseWrap, OneSplitCurvePoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!flags.check(FLAG_DISABLE_CURVE_ZAP)) { + if (_isPoolToken(fromToken)) { + return _swapFromCurvePoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_CURVE_ZAP + ); + } + + if (_isPoolToken(toToken)) { + return _swapToCurvePoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_CURVE_ZAP + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + flags + ); + } + + function _swapFromCurvePoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + CurveInfo memory curveInfo = _getCurve(poolToken); + + bytes memory minAmountsOut; + for (uint i = 0; i < curveInfo.tokenCount; i++) { + minAmountsOut = abi.encodePacked(minAmountsOut, uint256(1)); + } + + (bool success,) = address(curveInfo.curve).call( + abi.encodePacked( + _getCurveRemoveLiquiditySelector(curveInfo.tokenCount), + amount, + minAmountsOut + ) + ); + + require(success, "remove_liquidity failed"); + + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < curveInfo.tokenCount; i++) { + IERC20 coin = IERC20(curveInfo.curve.coins(int128(i))); + + if (coin == toToken) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + uint256 exchangeTokenAmount = coin.universalBalanceOf(address(this)); + + this.swap( + coin, + toToken, + exchangeTokenAmount, + 0, + dist, + flags + ); + } + } + + function _swapToCurvePoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + uint256[] memory dist = new uint256[](distribution.length); + + CurveInfo memory curveInfo = _getCurve(poolToken); + CurvePoolTokenDetails memory details = _getPoolDetails( + curveInfo.curve, + curveInfo.tokenCount + ); + + bytes memory tokenAmounts; + for (uint i = 0; i < curveInfo.tokenCount; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].weightedReserveBalance) + .div(details.totalWeightedBalance); + + _infiniteApproveIfNeeded(details.tokens[i].token, address(curveInfo.curve)); + + if (details.tokens[i].token == fromToken) { + tokenAmounts = abi.encodePacked(tokenAmounts, exchangeAmount); + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + fromToken, + details.tokens[i].token, + exchangeAmount, + 0, + dist, + flags + ); + + tokenAmounts = abi.encodePacked( + tokenAmounts, + details.tokens[i].token.universalBalanceOf(address(this)) + ); + } + + (bool success,) = address(curveInfo.curve).call( + abi.encodePacked( + _getCurveAddLiquiditySelector(curveInfo.tokenCount), + tokenAmounts, + uint256(0) + ) + ); + + require(success, "add_liquidity failed"); + } +} + +// File: contracts/interface/ISmartTokenConverter.sol + +pragma solidity ^0.5.0; + + +interface ISmartTokenConverter { + + function version() external view returns (uint16); + + function connectors(address) external view returns (uint256, uint32, bool, bool, bool); + + function getReserveRatio(IERC20 token) external view returns (uint256); + + function connectorTokenCount() external view returns (uint256); + + function connectorTokens(uint256 i) external view returns (IERC20); + + function liquidate(uint256 _amount) external; + + function fund(uint256 _amount) external; + + function convert2(IERC20 _fromToken, IERC20 _toToken, uint256 _amount, uint256 _minReturn, address _affiliateAccount, uint256 _affiliateFee) external returns (uint256); + + function convert(IERC20 _fromToken, IERC20 _toToken, uint256 _amount, uint256 _minReturn) external returns (uint256); + +} + +// File: contracts/interface/ISmartToken.sol + +pragma solidity ^0.5.0; + + + + +interface ISmartToken { + function owner() external view returns (ISmartTokenConverter); +} + +// File: contracts/interface/ISmartTokenRegistry.sol + +pragma solidity ^0.5.0; + + + +interface ISmartTokenRegistry { + function isSmartToken(IERC20 token) external view returns (bool); +} + +// File: contracts/interface/ISmartTokenFormula.sol + +pragma solidity ^0.5.0; + + + +interface ISmartTokenFormula { + function calculateLiquidateReturn( + uint256 supply, + uint256 reserveBalance, + uint32 totalRatio, + uint256 amount + ) external view returns (uint256); + + function calculatePurchaseReturn( + uint256 supply, + uint256 reserveBalance, + uint32 totalRatio, + uint256 amount + ) external view returns (uint256); +} + +// File: contracts/OneSplitSmartToken.sol + +pragma solidity ^0.5.0; + + + + + + + +contract OneSplitSmartTokenBase { + using SafeMath for uint256; + + ISmartTokenRegistry constant smartTokenRegistry = ISmartTokenRegistry(0xf6E2D7F616B67E46D708e4410746E9AAb3a4C518); + ISmartTokenFormula constant smartTokenFormula = ISmartTokenFormula(0x524619EB9b4cdFFa7DA13029b33f24635478AFc0); + IERC20 constant bntToken = IERC20(0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C); + IERC20 constant usdbToken = IERC20(0x309627af60F0926daa6041B8279484312f2bf060); + + IERC20 constant susd = IERC20(0x57Ab1ec28D129707052df4dF418D58a2D46d5f51); + IERC20 constant acientSUSD = IERC20(0x57Ab1E02fEE23774580C119740129eAC7081e9D3); + + struct TokenWithRatio { + IERC20 token; + uint256 ratio; + } + + struct SmartTokenDetails { + TokenWithRatio[] tokens; + address converter; + uint256 totalRatio; + } + + function _getSmartTokenDetails(ISmartToken smartToken) + internal + view + returns(SmartTokenDetails memory details) + { + ISmartTokenConverter converter = smartToken.owner(); + details.converter = address(converter); + details.tokens = new TokenWithRatio[](converter.connectorTokenCount()); + + for (uint256 i = 0; i < details.tokens.length; i++) { + details.tokens[i].token = converter.connectorTokens(i); + details.tokens[i].ratio = _getReserveRatio(converter, details.tokens[i].token); + details.totalRatio = details.totalRatio.add(details.tokens[i].ratio); + } + } + + function _getReserveRatio( + ISmartTokenConverter converter, + IERC20 token + ) + internal + view + returns (uint256) + { + (bool success, bytes memory data) = address(converter).staticcall.gas(10000)( + abi.encodeWithSelector( + converter.getReserveRatio.selector, + token + ) + ); + + if (!success) { + (, uint32 ratio, , ,) = converter.connectors(address(token)); + + return uint256(ratio); + } + + return abi.decode(data, (uint256)); + } + + function _canonicalSUSD(IERC20 token) internal pure returns(IERC20) { + return token == acientSUSD ? susd : token; + } +} + + +contract OneSplitSmartTokenView is OneSplitViewWrapBase, OneSplitSmartTokenBase { + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + public + view + returns( + uint256, + uint256[] memory + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + if (!flags.check(FLAG_DISABLE_SMART_TOKEN)) { + bool isSmartTokenFrom = smartTokenRegistry.isSmartToken(fromToken); + bool isSmartTokenTo = smartTokenRegistry.isSmartToken(toToken); + + if (isSmartTokenFrom && isSmartTokenTo) { + ( + uint256 returnBntAmount, + uint256[] memory smartTokenFromDistribution + ) = _getExpectedReturnFromSmartToken( + fromToken, + bntToken, + amount, + parts, + FLAG_DISABLE_SMART_TOKEN + ); + + ( + uint256 returnSmartTokenToAmount, + uint256[] memory smartTokenToDistribution + ) = _getExpectedReturnToSmartToken( + bntToken, + toToken, + returnBntAmount, + parts, + FLAG_DISABLE_SMART_TOKEN + ); + + for (uint i = 0; i < smartTokenToDistribution.length; i++) { + smartTokenFromDistribution[i] |= smartTokenToDistribution[i] << 128; + } + + return (returnSmartTokenToAmount, smartTokenFromDistribution); + } + + if (isSmartTokenFrom) { + return _getExpectedReturnFromSmartToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_SMART_TOKEN + ); + } + + if (isSmartTokenTo) { + return _getExpectedReturnToSmartToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_SMART_TOKEN + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } + + function _getExpectedReturnFromSmartToken( + IERC20 smartToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + for (uint i = 0; i < details.tokens.length; i++) { + uint256 srcAmount = smartTokenFormula.calculateLiquidateReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + amount + ); + + if (details.tokens[i].token == toToken) { + returnAmount = returnAmount.add(srcAmount); + continue; + } + + (uint256 ret, uint256[] memory dist) = this.getExpectedReturn( + _canonicalSUSD(details.tokens[i].token), + toToken, + srcAmount, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToSmartToken( + IERC20 fromToken, + IERC20 smartToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 minFundAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + minFundAmount = uint256(-1); + + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + uint256[] memory tokenAmounts = new uint256[](details.tokens.length); + uint256[] memory dist; + uint256[] memory fundAmounts = new uint256[](details.tokens.length); + + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].ratio) + .div(details.totalRatio); + + if (details.tokens[i].token != fromToken) { + (tokenAmounts[i], dist) = this.getExpectedReturn( + fromToken, + _canonicalSUSD(details.tokens[i].token), + exchangeAmount, + parts, + flags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } else { + tokenAmounts[i] = exchangeAmount; + } + + fundAmounts[i] = smartTokenFormula.calculatePurchaseReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + tokenAmounts[i] + ); + + if (fundAmounts[i] < minFundAmount) { + minFundAmount = fundAmounts[i]; + } + } + + return (minFundAmount, distribution); + } +} + + +contract OneSplitSmartToken is OneSplitBaseWrap, OneSplitSmartTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!flags.check(FLAG_DISABLE_SMART_TOKEN)) { + + bool isSmartTokenFrom = smartTokenRegistry.isSmartToken(fromToken); + bool isSmartTokenTo = smartTokenRegistry.isSmartToken(toToken); + + if (isSmartTokenFrom && isSmartTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 bntBalanceBefore = bntToken.balanceOf(address(this)); + + _swapFromSmartToken( + fromToken, + bntToken, + amount, + dist, + FLAG_DISABLE_SMART_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 bntBalanceAfter = bntToken.balanceOf(address(this)); + + return _swapToSmartToken( + bntToken, + toToken, + bntBalanceAfter.sub(bntBalanceBefore), + dist, + FLAG_DISABLE_SMART_TOKEN + ); + } + + if (isSmartTokenFrom) { + return _swapFromSmartToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_SMART_TOKEN + ); + } + + if (isSmartTokenTo) { + return _swapToSmartToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_SMART_TOKEN + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + flags + ); + } + + function _swapFromSmartToken( + IERC20 smartToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + ISmartTokenConverter(details.converter).liquidate(amount); + + uint256[] memory dist = new uint256[](distribution.length); + + for (uint i = 0; i < details.tokens.length; i++) { + if (details.tokens[i].token == toToken) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + _canonicalSUSD(details.tokens[i].token), + toToken, + _canonicalSUSD(details.tokens[i].token).balanceOf(address(this)), + 0, + dist, + flags + ); + } + } + + function _swapToSmartToken( + IERC20 fromToken, + IERC20 smartToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + + uint256[] memory dist = new uint256[](distribution.length); + uint256 minFundAmount = uint256(-1); + + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + uint256 curFundAmount; + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].ratio) + .div(details.totalRatio); + + if (details.tokens[i].token != fromToken) { + + uint256 tokenBalanceBefore = _canonicalSUSD(details.tokens[i].token).balanceOf(address(this)); + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + fromToken, + _canonicalSUSD(details.tokens[i].token), + exchangeAmount, + 0, + dist, + flags + ); + + uint256 tokenBalanceAfter = _canonicalSUSD(details.tokens[i].token).balanceOf(address(this)); + + curFundAmount = smartTokenFormula.calculatePurchaseReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + tokenBalanceAfter.sub(tokenBalanceBefore) + ); + } else { + curFundAmount = smartTokenFormula.calculatePurchaseReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + exchangeAmount + ); + } + + if (curFundAmount < minFundAmount) { + minFundAmount = curFundAmount; + } + + _infiniteApproveIfNeeded(_canonicalSUSD(details.tokens[i].token), details.converter); + } + + ISmartTokenConverter(details.converter).fund(minFundAmount); + + for (uint i = 0; i < details.tokens.length; i++) { + IERC20 reserveToken = _canonicalSUSD(details.tokens[i].token); + reserveToken.universalTransfer( + msg.sender, + reserveToken.universalBalanceOf(address(this)) + ); + } + } +} + +// File: contracts/interface/IUniswapV2Router.sol + +pragma solidity ^0.5.0; + + +interface IUniswapV2Router { + function addLiquidity( + IERC20 tokenA, + IERC20 tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint256[2] memory amounts, uint liquidity); + + function removeLiquidity( + IERC20 tokenA, + IERC20 tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint256[2] memory); + + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + IERC20[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function getAmountsOut(uint amountIn, IERC20[] calldata path) external view returns (uint[] memory amounts); +} + +// File: contracts/interface/IUniswapV2Pair.sol + +pragma solidity ^0.5.0; + + +interface IUniswapV2Pair { + function factory() external view returns (address); + + function token0() external view returns (IERC20); + function token1() external view returns (IERC20); + + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); +} + +// File: contracts/OneSplitUniswapV2PoolToken.sol + +pragma solidity ^0.5.0; + + + + + +library Math { + function min(uint x, uint y) internal pure returns (uint z) { + z = x < y ? x : y; + } + + // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) + function sqrt(uint y) internal pure returns (uint z) { + if (y > 3) { + z = y; + uint x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } +} + + +contract OneSplitUniswapV2PoolTokenBase { + using SafeMath for uint256; + + IUniswapV2Router constant uniswapRouter = IUniswapV2Router(0xf164fC0Ec4E93095b804a4795bBe1e041497b92a); + + function isLiquidityPool(IERC20 token) internal view returns (bool) { + (bool success, bytes memory data) = address(token).staticcall.gas(2000)( + abi.encode(IUniswapV2Pair(address(token)).factory.selector) + ); + if (!success || data.length == 0) { + return false; + } + return abi.decode(data, (address)) == 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + } + + struct TokenInfo { + IERC20 token; + uint256 reserve; + } + + struct PoolDetails { + TokenInfo[2] tokens; + uint256 totalSupply; + } + + function _getPoolDetails(IUniswapV2Pair pair) internal view returns (PoolDetails memory details) { + (uint112 reserve0, uint112 reserve1, ) = pair.getReserves(); + + details.tokens[0] = TokenInfo({ + token: pair.token0(), + reserve: reserve0 + }); + details.tokens[1] = TokenInfo({ + token: pair.token1(), + reserve: reserve1 + }); + + details.totalSupply = IERC20(address(pair)).totalSupply(); + } + + function _calcRebalanceAmount( + uint256 leftover, + uint256 balanceOfLeftoverAsset, + uint256 secondAssetBalance + ) internal pure returns (uint256) { + + return Math.sqrt( + 3988000 * leftover * balanceOfLeftoverAsset + + 3988009 * balanceOfLeftoverAsset * balanceOfLeftoverAsset - + 9 * balanceOfLeftoverAsset * balanceOfLeftoverAsset / (secondAssetBalance - 1) + ) / 1994 - balanceOfLeftoverAsset * 1997 / 1994; + } + +} + + +contract OneSplitUniswapV2PoolTokenView is OneSplitViewWrapBase, OneSplitUniswapV2PoolTokenBase { + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + public + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!flags.check(FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + ( + uint256 returnWETHAmount, + uint256[] memory poolTokenFromDistribution + ) = _getExpectedReturnFromUniswapV2PoolToken( + fromToken, + weth, + amount, + parts, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + + ( + uint256 returnPoolTokenToAmount, + uint256[] memory poolTokenToDistribution + ) = _getExpectedReturnToUniswapV2PoolToken( + weth, + toToken, + returnWETHAmount, + parts, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + + for (uint i = 0; i < poolTokenToDistribution.length; i++) { + poolTokenFromDistribution[i] |= poolTokenToDistribution[i] << 128; + } + + return (returnPoolTokenToAmount, poolTokenFromDistribution); + } + + if (isPoolTokenFrom) { + return _getExpectedReturnFromUniswapV2PoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _getExpectedReturnToUniswapV2PoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } + + function _getExpectedReturnFromUniswapV2PoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + PoolDetails memory details = _getPoolDetails(IUniswapV2Pair(address(poolToken))); + + for (uint i = 0; i < 2; i++) { + + uint256 exchangeAmount = amount + .mul(details.tokens[i].reserve) + .div(details.totalSupply); + + if (toToken == details.tokens[i].token) { + returnAmount = returnAmount.add(exchangeAmount); + continue; + } + + (uint256 ret, uint256[] memory dist) = this.getExpectedReturn( + details.tokens[i].token, + toToken, + exchangeAmount, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToUniswapV2PoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + PoolDetails memory details = _getPoolDetails(IUniswapV2Pair(address(poolToken))); + + // will overwritten to liquidity amounts + uint256[2] memory amounts; + amounts[0] = amount.div(2); + amounts[1] = amount.sub(amounts[0]); + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < 2; i++) { + + if (fromToken == details.tokens[i].token) { + continue; + } + + (amounts[i], dist) = this.getExpectedReturn( + fromToken, + details.tokens[i].token, + amounts[i], + parts, + flags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + uint256 possibleLiquidity0 = amounts[0].mul(details.totalSupply).div(details.tokens[0].reserve); + returnAmount = Math.min( + possibleLiquidity0, + amounts[1].mul(details.totalSupply).div(details.tokens[1].reserve) + ); + + uint256 leftoverIndex = possibleLiquidity0 > returnAmount ? 0 : 1; + IERC20[] memory path = new IERC20[](2); + path[0] = details.tokens[leftoverIndex].token; + path[1] = details.tokens[1 - leftoverIndex].token; + + uint256 optimalAmount = amounts[1 - leftoverIndex].mul( + details.tokens[leftoverIndex].reserve + ).div(details.tokens[1 - leftoverIndex].reserve); + + IERC20 _poolToken = poolToken; // stack too deep + uint256 exchangeAmount = _calcRebalanceAmount( + amounts[leftoverIndex].sub(optimalAmount), + path[0].balanceOf(address(_poolToken)).add(optimalAmount), + path[1].balanceOf(address(_poolToken)).add(amounts[1 - leftoverIndex]) + ); + + (bool success, bytes memory data) = address(uniswapRouter).staticcall.gas(200000)( + abi.encodeWithSelector( + uniswapRouter.getAmountsOut.selector, + exchangeAmount, + path + ) + ); + + if (!success) { + return ( + returnAmount, + distribution + ); + } + + uint256[] memory amountsOutAfterSwap = abi.decode(data, (uint256[])); + + uint256 _addedLiquidity = returnAmount; // stack too deep + PoolDetails memory _details = details; // stack too deep + returnAmount = _addedLiquidity.add( + amountsOutAfterSwap[1] // amountOut after swap + .mul(_details.totalSupply.add(_addedLiquidity)) + .div(_details.tokens[1 - leftoverIndex].reserve.sub(amountsOutAfterSwap[1])) + ); + + return ( + returnAmount, + distribution + ); + } + +} + + +contract OneSplitUniswapV2PoolToken is OneSplitBaseWrap, OneSplitUniswapV2PoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!flags.check(FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 wEthBalanceBefore = weth.balanceOf(address(this)); + + _swapFromUniswapV2PoolToken( + fromToken, + weth, + amount, + dist, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 wEthBalanceAfter = weth.balanceOf(address(this)); + + return _swapToUniswapV2PoolToken( + weth, + toToken, + wEthBalanceAfter.sub(wEthBalanceBefore), + dist, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + } + + if (isPoolTokenFrom) { + return _swapFromUniswapV2PoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _swapToUniswapV2PoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + flags + ); + } + + function _swapFromUniswapV2PoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + _infiniteApproveIfNeeded(poolToken, address(uniswapRouter)); + + IERC20 [2] memory tokens = [ + IUniswapV2Pair(address(poolToken)).token0(), + IUniswapV2Pair(address(poolToken)).token1() + ]; + + uint256[2] memory amounts = uniswapRouter.removeLiquidity( + tokens[0], + tokens[1], + amount, + uint256(0), + uint256(0), + address(this), + now.add(1800) + ); + + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < 2; i++) { + + if (toToken == tokens[i]) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + tokens[i], + toToken, + amounts[i], + 0, + dist, + flags + ); + } + } + + function _swapToUniswapV2PoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + IERC20 [2] memory tokens = [ + IUniswapV2Pair(address(poolToken)).token0(), + IUniswapV2Pair(address(poolToken)).token1() + ]; + + // will overwritten to liquidity amounts + uint256[2] memory amounts; + amounts[0] = amount.div(2); + amounts[1] = amount.sub(amounts[0]); + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < 2; i++) { + + _infiniteApproveIfNeeded(tokens[i], address(uniswapRouter)); + + if (fromToken == tokens[i]) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + fromToken, + tokens[i], + amounts[i], + 0, + dist, + flags + ); + + amounts[i] = tokens[i].universalBalanceOf(address(this)); + } + + (uint256[2] memory redeemAmounts, ) = uniswapRouter.addLiquidity( + tokens[0], + tokens[1], + amounts[0], + amounts[1], + uint256(0), + uint256(0), + address(this), + now.add(1800) + ); + + if ( + redeemAmounts[0] == amounts[0] && + redeemAmounts[1] == amounts[1] + ) { + return; + } + + uint256 leftoverIndex = amounts[0] != redeemAmounts[0] ? 0 : 1; + IERC20[] memory path = new IERC20[](2); + path[0] = tokens[leftoverIndex]; + path[1] = tokens[1 - leftoverIndex]; + + address _poolToken = address(poolToken); // stack too deep + uint256 leftover = amounts[leftoverIndex].sub(redeemAmounts[leftoverIndex]); + uint256 exchangeAmount = _calcRebalanceAmount( + leftover, + path[0].balanceOf(_poolToken), + path[1].balanceOf(_poolToken) + ); + + (bool success, bytes memory data) = address(uniswapRouter).call.gas(1000000)( + abi.encodeWithSelector( + uniswapRouter.swapExactTokensForTokens.selector, + exchangeAmount, + uint256(0), + path, + address(this), + now.add(1800) + ) + ); + + if (!success) { + return; + } + + uint256[] memory amountsOut = abi.decode(data, (uint256[])); + + address(uniswapRouter).call.gas(1000000)( + abi.encodeWithSelector( + uniswapRouter.addLiquidity.selector, + tokens[0], + tokens[1], + leftoverIndex == 0 + ? leftover.sub(amountsOut[0]) + : amountsOut[1], + leftoverIndex == 1 + ? leftover.sub(amountsOut[0]) + : amountsOut[1], + uint256(0), + uint256(0), + address(this), + now.add(1800) + ) + ); + } +} + +// File: contracts/OneSplit.sol + +pragma solidity ^0.5.0; + + + + + + + + + + + + + + + + + + +contract OneSplitViewWrap is + OneSplitViewWrapBase, + OneSplitMultiPathView, + OneSplitChaiView, + OneSplitBdaiView, + OneSplitAaveView, + OneSplitFulcrumView, + OneSplitCompoundView, + OneSplitIearnView, + OneSplitIdleView, + OneSplitWethView, + //OneSplitBalancerPoolTokenView, + //OneSplitUniswapPoolTokenView, + //OneSplitCurvePoolTokenView + //OneSplitSmartTokenView, + OneSplitUniswapV2PoolTokenView +{ + IOneSplitView public oneSplitView; + + constructor(IOneSplitView _oneSplit) public { + oneSplitView = _oneSplit; + } + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + public + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } + + function _getExpectedReturnFloor( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + internal + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + return oneSplitView.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } +} + + +contract OneSplitWrap is + OneSplitBaseWrap, + //OneSplitMultiPath, + OneSplitChai, + OneSplitBdai, + OneSplitAave, +// OneSplitFulcrum, + OneSplitCompound, + OneSplitIearn, + OneSplitIdle, + OneSplitWeth, + //OneSplitBalancerPoolToken, + //OneSplitUniswapPoolToken, + //OneSplitCurvePoolToken + //OneSplitSmartToken, + OneSplitUniswapV2PoolToken +{ + IOneSplitView public oneSplitView; + IOneSplit public oneSplit; + + constructor(IOneSplitView _oneSplitView, IOneSplit _oneSplit) public { + oneSplitView = _oneSplitView; + oneSplit = _oneSplit; + } + + function() external payable { + // solium-disable-next-line security/no-tx-origin + require(msg.sender != tx.origin); + } + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags // 1 - Uniswap, 2 - Kyber, 4 - Bancor, 8 - Oasis, 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI + ) + public + view + returns( + uint256 /*returnAmount*/, + uint256[] memory /*distribution*/ + ) + { + return oneSplitView.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 minReturn, + uint256[] memory distribution, // [Uniswap, Kyber, Bancor, Oasis] + uint256 flags // 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI + ) public payable { + if (msg.sender != address(this)) { + fromToken.universalTransferFrom(msg.sender, address(this), amount); + } + + _swap(fromToken, toToken, amount, distribution, flags); + + uint256 returnAmount = toToken.universalBalanceOf(address(this)); + require(returnAmount >= minReturn, "OneSplit: actual return amount is less than minReturn"); + + if (msg.sender != address(this)) { + toToken.universalTransfer(msg.sender, returnAmount); + fromToken.universalTransfer(msg.sender, fromToken.universalBalanceOf(address(this))); + } } function _swapFloor( diff --git a/contracts/IOneSplit.sol b/contracts/IOneSplit.sol index 7740830..acbd813 100644 --- a/contracts/IOneSplit.sol +++ b/contracts/IOneSplit.sol @@ -58,6 +58,10 @@ contract IOneSplitConsts { uint256 public constant FLAG_DISABLE_ALL_SPLIT_SOURCES = 0x20000000; uint256 public constant FLAG_DISABLE_ALL_WRAP_SOURCES = 0x40000000; uint256 public constant FLAG_DISABLE_CURVE_PAX = 0x80000000; + uint256 public constant FLAG_DISABLE_UNISWAP_POOL_TOKEN = 0x100000000; + uint256 public constant FLAG_DISABLE_BALANCER_POOL_TOKEN = 0x200000000; + uint256 public constant FLAG_DISABLE_CURVE_ZAP = 0x400000000; + uint256 public constant FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN = 0x800000000; } diff --git a/contracts/OneSplit.sol b/contracts/OneSplit.sol index 22c1b0c..a9640f6 100644 --- a/contracts/OneSplit.sol +++ b/contracts/OneSplit.sol @@ -11,7 +11,11 @@ import "./OneSplitIearn.sol"; import "./OneSplitIdle.sol"; import "./OneSplitAave.sol"; import "./OneSplitWeth.sol"; -//import "./OneSplitSmartToken.sol"; +import "./OneSplitBalancerPoolToken.sol"; +import "./OneSplitUniswapPoolToken.sol"; +import "./OneSplitCurvePoolToken.sol"; +import "./OneSplitSmartToken.sol"; +import "./OneSplitUniswapV2PoolToken.sol"; contract OneSplitViewWrap is @@ -24,8 +28,12 @@ contract OneSplitViewWrap is OneSplitCompoundView, OneSplitIearnView, OneSplitIdleView, - OneSplitWethView - //OneSplitSmartTokenView + OneSplitWethView, + //OneSplitBalancerPoolTokenView, + //OneSplitUniswapPoolTokenView, + //OneSplitCurvePoolTokenView + //OneSplitSmartTokenView, + OneSplitUniswapV2PoolTokenView { IOneSplitView public oneSplitView; @@ -87,16 +95,20 @@ contract OneSplitViewWrap is contract OneSplitWrap is OneSplitBaseWrap, - OneSplitMultiPath, + //OneSplitMultiPath, OneSplitChai, OneSplitBdai, OneSplitAave, - OneSplitFulcrum, +// OneSplitFulcrum, OneSplitCompound, OneSplitIearn, OneSplitIdle, - OneSplitWeth - //OneSplitSmartToken + OneSplitWeth, + //OneSplitBalancerPoolToken, + //OneSplitUniswapPoolToken, + //OneSplitCurvePoolToken + //OneSplitSmartToken, + OneSplitUniswapV2PoolToken { IOneSplitView public oneSplitView; IOneSplit public oneSplit; @@ -142,14 +154,19 @@ contract OneSplitWrap is uint256[] memory distribution, // [Uniswap, Kyber, Bancor, Oasis] uint256 flags // 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI ) public payable { - fromToken.universalTransferFrom(msg.sender, address(this), amount); + if (msg.sender != address(this)) { + fromToken.universalTransferFrom(msg.sender, address(this), amount); + } _swap(fromToken, toToken, amount, distribution, flags); uint256 returnAmount = toToken.universalBalanceOf(address(this)); require(returnAmount >= minReturn, "OneSplit: actual return amount is less than minReturn"); - toToken.universalTransfer(msg.sender, returnAmount); - fromToken.universalTransfer(msg.sender, fromToken.universalBalanceOf(address(this))); + + if (msg.sender != address(this)) { + toToken.universalTransfer(msg.sender, returnAmount); + fromToken.universalTransfer(msg.sender, fromToken.universalBalanceOf(address(this))); + } } function _swapFloor( diff --git a/contracts/OneSplitBalancerPoolToken.sol b/contracts/OneSplitBalancerPoolToken.sol new file mode 100644 index 0000000..a5c4ca4 --- /dev/null +++ b/contracts/OneSplitBalancerPoolToken.sol @@ -0,0 +1,450 @@ +pragma solidity ^0.5.0; + +import "./OneSplitBase.sol"; +import "./interface/IBFactory.sol"; +import "./interface/IBPool.sol"; + + +contract OneSplitBalancerPoolTokenBase { + using SafeMath for uint256; + + // todo: factory for Bronze release + // may be changed in future + IBFactory bFactory = IBFactory(0x9424B1412450D0f8Fc2255FAf6046b98213B76Bd); + + struct TokenWithWeight { + IERC20 token; + uint256 reserveBalance; + uint256 denormalizedWeight; + } + + struct PoolTokenDetails { + TokenWithWeight[] tokens; + uint256 totalWeight; + uint256 totalSupply; + } + + function _getPoolDetails(IBPool poolToken) + internal + view + returns(PoolTokenDetails memory details) + { + address[] memory currentTokens = poolToken.getCurrentTokens(); + details.tokens = new TokenWithWeight[](currentTokens.length); + details.totalWeight = poolToken.getTotalDenormalizedWeight(); + details.totalSupply = poolToken.totalSupply(); + for (uint256 i = 0; i < details.tokens.length; i++) { + details.tokens[i].token = IERC20(currentTokens[i]); + details.tokens[i].denormalizedWeight = poolToken.getDenormalizedWeight(currentTokens[i]); + details.tokens[i].reserveBalance = poolToken.getBalance(currentTokens[i]); + } + } + +} + + +contract OneSplitBalancerPoolTokenView is OneSplitViewWrapBase, OneSplitBalancerPoolTokenBase { + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + public + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!flags.check(FLAG_DISABLE_BALANCER_POOL_TOKEN)) { + bool isPoolTokenFrom = bFactory.isBPool(address(fromToken)); + bool isPoolTokenTo = bFactory.isBPool(address(toToken)); + + if (isPoolTokenFrom && isPoolTokenTo) { + ( + uint256 returnETHAmount, + uint256[] memory poolTokenFromDistribution + ) = _getExpectedReturnFromBalancerPoolToken( + fromToken, + ETH_ADDRESS, + amount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + + ( + uint256 returnPoolTokenToAmount, + uint256[] memory poolTokenToDistribution + ) = _getExpectedReturnToBalancerPoolToken( + ETH_ADDRESS, + toToken, + returnETHAmount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + + for (uint i = 0; i < poolTokenToDistribution.length; i++) { + poolTokenFromDistribution[i] |= poolTokenToDistribution[i] << 128; + } + + return (returnPoolTokenToAmount, poolTokenFromDistribution); + } + + if (isPoolTokenFrom) { + return _getExpectedReturnFromBalancerPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _getExpectedReturnToBalancerPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } + + function _getExpectedReturnFromBalancerPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + IBPool bToken = IBPool(address(poolToken)); + address[] memory currentTokens = bToken.getCurrentTokens(); + + uint256 pAiAfterExitFee = amount.sub( + amount.mul(bToken.EXIT_FEE()) + ); + uint256 ratio = pAiAfterExitFee.mul(1e18).div(poolToken.totalSupply()); + for (uint i = 0; i < currentTokens.length; i++) { + uint256 tokenAmountOut = bToken.getBalance(currentTokens[i]).mul(ratio).div(1e18); + + if (currentTokens[i] == address(toToken)) { + returnAmount = returnAmount.add(tokenAmountOut); + continue; + } + + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + IERC20(currentTokens[i]), + toToken, + tokenAmountOut, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToBalancerPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns ( + uint256 minFundAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + minFundAmount = uint256(-1); + + PoolTokenDetails memory details = _getPoolDetails(IBPool(address(poolToken))); + + uint256[] memory tokenAmounts = new uint256[](details.tokens.length); + uint256[] memory dist; + uint256[] memory fundAmounts = new uint256[](details.tokens.length); + + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount.mul( + details.tokens[i].denormalizedWeight + ).div(details.totalWeight); + + if (details.tokens[i].token != fromToken) { + (tokenAmounts[i], dist) = getExpectedReturn( + fromToken, + details.tokens[i].token, + exchangeAmount, + parts, + flags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } else { + tokenAmounts[i] = exchangeAmount; + } + + fundAmounts[i] = tokenAmounts[i] + .mul(details.totalSupply) + .div(details.tokens[i].reserveBalance); + + if (fundAmounts[i] < minFundAmount) { + minFundAmount = fundAmounts[i]; + } + } + +// uint256 _minFundAmount = minFundAmount; +// uint256 swapFee = IBPool(address(poolToken)).getSwapFee(); + // Swap leftovers for PoolToken +// for (uint i = 0; i < details.tokens.length; i++) { +// if (_minFundAmount == fundAmounts[i]) { +// continue; +// } +// +// uint256 leftover = tokenAmounts[i].sub( +// fundAmounts[i].mul(details.tokens[i].reserveBalance).div(details.totalSupply) +// ); +// +// uint256 tokenRet = IBPool(address(poolToken)).calcPoolOutGivenSingleIn( +// details.tokens[i].reserveBalance, +// details.tokens[i].denormalizedWeight, +// details.totalSupply, +// details.totalWeight, +// leftover, +// swapFee +// ); +// +// minFundAmount = minFundAmount.add(tokenRet); +// } + + return (minFundAmount, distribution); + } + +} + + +contract OneSplitBalancerPoolToken is OneSplitBaseWrap, OneSplitBalancerPoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!flags.check(FLAG_DISABLE_BALANCER_POOL_TOKEN)) { + bool isPoolTokenFrom = bFactory.isBPool(address(fromToken)); + bool isPoolTokenTo = bFactory.isBPool(address(toToken)); + + if (isPoolTokenFrom && isPoolTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 ethBalanceBefore = address(this).balance; + + _swapFromBalancerPoolToken( + fromToken, + ETH_ADDRESS, + amount, + dist, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 ethBalanceAfter = address(this).balance; + + return _swapToBalancerPoolToken( + ETH_ADDRESS, + toToken, + ethBalanceAfter.sub(ethBalanceBefore), + dist, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + + if (isPoolTokenFrom) { + return _swapFromBalancerPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _swapToBalancerPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + flags + ); + } + + function _swapFromBalancerPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + + IBPool bToken = IBPool(address(poolToken)); + + address[] memory currentTokens = bToken.getCurrentTokens(); + + uint256 ratio = amount.sub( + amount.mul(bToken.EXIT_FEE()) + ).mul(1e18).div(poolToken.totalSupply()); + + uint256[] memory minAmountsOut = new uint256[](currentTokens.length); + for (uint i = 0; i < currentTokens.length; i++) { + minAmountsOut[i] = bToken.getBalance(currentTokens[i]).mul(ratio).div(1e18).mul(995).div(1000); // 0.5% slippage; + } + + bToken.exitPool(amount, minAmountsOut); + + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < currentTokens.length; i++) { + + if (currentTokens[i] == address(toToken)) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + uint256 exchangeTokenAmount = IERC20(currentTokens[i]).balanceOf(address(this)); + + this.swap( + IERC20(currentTokens[i]), + toToken, + exchangeTokenAmount, + 0, + dist, + flags + ); + } + + } + + function _swapToBalancerPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + uint256[] memory dist = new uint256[](distribution.length); + uint256 minFundAmount = uint256(-1); + + PoolTokenDetails memory details = _getPoolDetails(IBPool(address(poolToken))); + + uint256[] memory maxAmountsIn = new uint256[](details.tokens.length); + uint256 curFundAmount; + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].denormalizedWeight) + .div(details.totalWeight); + + if (details.tokens[i].token != fromToken) { + uint256 tokenBalanceBefore = details.tokens[i].token.balanceOf(address(this)); + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + fromToken, + details.tokens[i].token, + exchangeAmount, + 0, + dist, + flags + ); + + uint256 tokenBalanceAfter = details.tokens[i].token.balanceOf(address(this)); + + curFundAmount = ( + tokenBalanceAfter.sub(tokenBalanceBefore) + ).mul(details.totalSupply).div(details.tokens[i].reserveBalance); + } else { + curFundAmount = ( + exchangeAmount + ).mul(details.totalSupply).div(details.tokens[i].reserveBalance); + } + + if (curFundAmount < minFundAmount) { + minFundAmount = curFundAmount; + } + + maxAmountsIn[i] = uint256(-1); + _infiniteApproveIfNeeded(details.tokens[i].token, address(poolToken)); + } + + // todo: check for vulnerability + IBPool(address(poolToken)).joinPool(minFundAmount, maxAmountsIn); + + // Return leftovers + for (uint i = 0; i < details.tokens.length; i++) { + details.tokens[i].token.universalTransfer(msg.sender, details.tokens[i].token.balanceOf(address(this))); + } + } +} diff --git a/contracts/OneSplitCurvePoolToken.sol b/contracts/OneSplitCurvePoolToken.sol new file mode 100644 index 0000000..cd925e9 --- /dev/null +++ b/contracts/OneSplitCurvePoolToken.sol @@ -0,0 +1,479 @@ +pragma solidity ^0.5.0; + +import "./OneSplitBase.sol"; +import "./interface/ICurve.sol"; + + +contract OneSplitCurvePoolTokenBase { + using SafeMath for uint256; + using UniversalERC20 for IERC20; + + IERC20 constant curveSusdToken = IERC20(0xC25a3A3b969415c80451098fa907EC722572917F); + IERC20 constant curveIearnToken = IERC20(0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8); + IERC20 constant curveCompoundToken = IERC20(0x845838DF265Dcd2c412A1Dc9e959c7d08537f8a2); + IERC20 constant curveUsdtToken = IERC20(0x9fC689CCaDa600B6DF723D9E47D84d76664a1F23); + IERC20 constant curveBinanceToken = IERC20(0x3B3Ac5386837Dc563660FB6a0937DFAa5924333B); + IERC20 constant curvePaxToken = IERC20(0xD905e2eaeBe188fc92179b6350807D8bd91Db0D8); + IERC20 constant curveRenBtcToken = IERC20(0x7771F704490F9C0C3B06aFe8960dBB6c58CBC812); + IERC20 constant curveTBtcToken = IERC20(0x1f2a662FB513441f06b8dB91ebD9a1466462b275); + + ICurve constant curveSusd = ICurve(0xA5407eAE9Ba41422680e2e00537571bcC53efBfD); + ICurve constant curveIearn = ICurve(0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51); + ICurve constant curveCompound = ICurve(0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56); + ICurve constant curveUsdt = ICurve(0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C); + ICurve constant curveBinance = ICurve(0x79a8C46DeA5aDa233ABaFFD40F3A0A2B1e5A4F27); + ICurve constant curvePax = ICurve(0x06364f10B501e868329afBc005b3492902d6C763); + ICurve constant curveRenBtc = ICurve(0x8474c1236F0Bc23830A23a41aBB81B2764bA9f4F); + ICurve constant curveTBtc = ICurve(0x9726e9314eF1b96E45f40056bEd61A088897313E); + + struct CurveTokenInfo { + IERC20 token; + uint256 weightedReserveBalance; + } + + struct CurveInfo { + ICurve curve; + uint256 tokenCount; + } + + struct CurvePoolTokenDetails { + CurveTokenInfo[] tokens; + uint256 totalWeightedBalance; + } + + function _isPoolToken(IERC20 token) + internal + pure + returns (bool) + { + if ( + token == curveSusdToken || + token == curveIearnToken || + token == curveCompoundToken || + token == curveUsdtToken || + token == curveBinanceToken || + token == curvePaxToken || + token == curveRenBtcToken || + token == curveTBtcToken + ) { + return true; + } + return false; + } + + function _getCurve(IERC20 poolToken) + internal + pure + returns (CurveInfo memory curveInfo) + { + if (poolToken == curveSusdToken) { + curveInfo.curve = curveSusd; + curveInfo.tokenCount = 4; + return curveInfo; + } + + if (poolToken == curveIearnToken) { + curveInfo.curve = curveIearn; + curveInfo.tokenCount = 4; + return curveInfo; + } + + if (poolToken == curveCompoundToken) { + curveInfo.curve = curveCompound; + curveInfo.tokenCount = 2; + return curveInfo; + } + + if (poolToken == curveUsdtToken) { + curveInfo.curve = curveUsdt; + curveInfo.tokenCount = 3; + return curveInfo; + } + + if (poolToken == curveBinanceToken) { + curveInfo.curve = curveBinance; + curveInfo.tokenCount = 4; + return curveInfo; + } + + if (poolToken == curvePaxToken) { + curveInfo.curve = curvePax; + curveInfo.tokenCount = 4; + return curveInfo; + } + + if (poolToken == curveRenBtcToken) { + curveInfo.curve = curveRenBtc; + curveInfo.tokenCount = 2; + return curveInfo; + } + + if (poolToken == curveTBtcToken) { + curveInfo.curve = curveTBtc; + curveInfo.tokenCount = 3; + return curveInfo; + } + + revert(); + } + + function _getCurveCalcTokenAmountSelector(uint256 tokenCount) + internal + pure + returns (bytes4) + { + return bytes4(keccak256(abi.encodePacked( + "calc_token_amount(uint256[", uint8(48 + tokenCount) ,"],bool)" + ))); + } + + function _getCurveRemoveLiquiditySelector(uint256 tokenCount) + internal + pure + returns (bytes4) + { + return bytes4(keccak256(abi.encodePacked( + "remove_liquidity(uint256,uint256[", uint8(48 + tokenCount) ,"])" + ))); + } + + function _getCurveAddLiquiditySelector(uint256 tokenCount) + internal + pure + returns (bytes4) + { + return bytes4(keccak256(abi.encodePacked( + "add_liquidity(uint256[", uint8(48 + tokenCount) ,"],uint256)" + ))); + } + + function _getPoolDetails(ICurve curve, uint256 tokenCount) + internal + view + returns(CurvePoolTokenDetails memory details) + { + details.tokens = new CurveTokenInfo[](tokenCount); + for (uint256 i = 0; i < tokenCount; i++) { + details.tokens[i].token = IERC20(curve.coins(int128(i))); + details.tokens[i].weightedReserveBalance = curve.balances(int128(i)) + .mul(1e18).div(10 ** details.tokens[i].token.universalDecimals()); + details.totalWeightedBalance = details.totalWeightedBalance.add( + details.tokens[i].weightedReserveBalance + ); + } + } +} + + +contract OneSplitCurvePoolTokenView is OneSplitViewWrapBase, OneSplitCurvePoolTokenBase { + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + public + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!flags.check(FLAG_DISABLE_CURVE_ZAP)) { + if (_isPoolToken(fromToken)) { + return _getExpectedReturnFromCurvePoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_CURVE_ZAP + ); + } + + if (_isPoolToken(toToken)) { + return _getExpectedReturnToCurvePoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_CURVE_ZAP + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } + + function _getExpectedReturnFromCurvePoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + CurveInfo memory curveInfo = _getCurve(poolToken); + uint256 totalSupply = poolToken.totalSupply(); + for (uint i = 0; i < curveInfo.tokenCount; i++) { + IERC20 coin = IERC20(curveInfo.curve.coins(int128(i))); + + uint256 tokenAmountOut = curveInfo.curve.balances(int128(i)) + .mul(amount) + .div(totalSupply); + + if (coin == toToken) { + returnAmount = returnAmount.add(tokenAmountOut); + continue; + } + + (uint256 ret, uint256[] memory dist) = this.getExpectedReturn( + coin, + toToken, + tokenAmountOut, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToCurvePoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + CurveInfo memory curveInfo = _getCurve(poolToken); + CurvePoolTokenDetails memory details = _getPoolDetails( + curveInfo.curve, + curveInfo.tokenCount + ); + + bytes memory tokenAmounts; + for (uint i = 0; i < curveInfo.tokenCount; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].weightedReserveBalance) + .div(details.totalWeightedBalance); + + if (details.tokens[i].token == fromToken) { + tokenAmounts = abi.encodePacked(tokenAmounts, exchangeAmount); + continue; + } + + (uint256 tokenAmount, uint256[] memory dist) = this.getExpectedReturn( + fromToken, + details.tokens[i].token, + exchangeAmount, + parts, + flags + ); + + tokenAmounts = abi.encodePacked(tokenAmounts, tokenAmount); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + (bool success, bytes memory data) = address(curveInfo.curve).staticcall( + abi.encodePacked( + _getCurveCalcTokenAmountSelector(curveInfo.tokenCount), + tokenAmounts, + uint256(1) + ) + ); + + require(success, "calc_token_amount failed"); + + return (abi.decode(data, (uint256)), distribution); + } +} + + +contract OneSplitCurvePoolToken is OneSplitBaseWrap, OneSplitCurvePoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!flags.check(FLAG_DISABLE_CURVE_ZAP)) { + if (_isPoolToken(fromToken)) { + return _swapFromCurvePoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_CURVE_ZAP + ); + } + + if (_isPoolToken(toToken)) { + return _swapToCurvePoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_CURVE_ZAP + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + flags + ); + } + + function _swapFromCurvePoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + CurveInfo memory curveInfo = _getCurve(poolToken); + + bytes memory minAmountsOut; + for (uint i = 0; i < curveInfo.tokenCount; i++) { + minAmountsOut = abi.encodePacked(minAmountsOut, uint256(1)); + } + + (bool success,) = address(curveInfo.curve).call( + abi.encodePacked( + _getCurveRemoveLiquiditySelector(curveInfo.tokenCount), + amount, + minAmountsOut + ) + ); + + require(success, "remove_liquidity failed"); + + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < curveInfo.tokenCount; i++) { + IERC20 coin = IERC20(curveInfo.curve.coins(int128(i))); + + if (coin == toToken) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + uint256 exchangeTokenAmount = coin.universalBalanceOf(address(this)); + + this.swap( + coin, + toToken, + exchangeTokenAmount, + 0, + dist, + flags + ); + } + } + + function _swapToCurvePoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + uint256[] memory dist = new uint256[](distribution.length); + + CurveInfo memory curveInfo = _getCurve(poolToken); + CurvePoolTokenDetails memory details = _getPoolDetails( + curveInfo.curve, + curveInfo.tokenCount + ); + + bytes memory tokenAmounts; + for (uint i = 0; i < curveInfo.tokenCount; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].weightedReserveBalance) + .div(details.totalWeightedBalance); + + _infiniteApproveIfNeeded(details.tokens[i].token, address(curveInfo.curve)); + + if (details.tokens[i].token == fromToken) { + tokenAmounts = abi.encodePacked(tokenAmounts, exchangeAmount); + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + fromToken, + details.tokens[i].token, + exchangeAmount, + 0, + dist, + flags + ); + + tokenAmounts = abi.encodePacked( + tokenAmounts, + details.tokens[i].token.universalBalanceOf(address(this)) + ); + } + + (bool success,) = address(curveInfo.curve).call( + abi.encodePacked( + _getCurveAddLiquiditySelector(curveInfo.tokenCount), + tokenAmounts, + uint256(0) + ) + ); + + require(success, "add_liquidity failed"); + } +} diff --git a/contracts/OneSplitSmartToken.sol b/contracts/OneSplitSmartToken.sol index b93643e..50f2f0f 100644 --- a/contracts/OneSplitSmartToken.sol +++ b/contracts/OneSplitSmartToken.sol @@ -10,29 +10,67 @@ import "./OneSplitBase.sol"; contract OneSplitSmartTokenBase { using SafeMath for uint256; - ISmartTokenRegistry smartTokenRegistry = ISmartTokenRegistry(0xf6E2D7F616B67E46D708e4410746E9AAb3a4C518); - ISmartTokenFormula smartTokenFormula = ISmartTokenFormula(0x524619EB9b4cdFFa7DA13029b33f24635478AFc0); + ISmartTokenRegistry constant smartTokenRegistry = ISmartTokenRegistry(0xf6E2D7F616B67E46D708e4410746E9AAb3a4C518); + ISmartTokenFormula constant smartTokenFormula = ISmartTokenFormula(0x524619EB9b4cdFFa7DA13029b33f24635478AFc0); + IERC20 constant bntToken = IERC20(0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C); + IERC20 constant usdbToken = IERC20(0x309627af60F0926daa6041B8279484312f2bf060); - struct TokensWithRatio { - IERC20[] tokens; - uint256[] ratios; + IERC20 constant susd = IERC20(0x57Ab1ec28D129707052df4dF418D58a2D46d5f51); + IERC20 constant acientSUSD = IERC20(0x57Ab1E02fEE23774580C119740129eAC7081e9D3); + + struct TokenWithRatio { + IERC20 token; + uint256 ratio; + } + + struct SmartTokenDetails { + TokenWithRatio[] tokens; + address converter; uint256 totalRatio; } - function _getTokens( - ISmartTokenConverter converter + function _getSmartTokenDetails(ISmartToken smartToken) + internal + view + returns(SmartTokenDetails memory details) + { + ISmartTokenConverter converter = smartToken.owner(); + details.converter = address(converter); + details.tokens = new TokenWithRatio[](converter.connectorTokenCount()); + + for (uint256 i = 0; i < details.tokens.length; i++) { + details.tokens[i].token = converter.connectorTokens(i); + details.tokens[i].ratio = _getReserveRatio(converter, details.tokens[i].token); + details.totalRatio = details.totalRatio.add(details.tokens[i].ratio); + } + } + + function _getReserveRatio( + ISmartTokenConverter converter, + IERC20 token ) internal view - returns(TokensWithRatio memory tokens) + returns (uint256) { - tokens.tokens = new IERC20[](converter.connectorTokenCount()); - tokens.ratios = new uint256[](tokens.tokens.length); - for (uint256 i = 0; i < tokens.tokens.length; i++) { - tokens.tokens[i] = converter.connectorTokens(i); - tokens.ratios[i] = converter.getReserveRatio(tokens.tokens[i]); - tokens.totalRatio = tokens.totalRatio.add(tokens.ratios[i]); + (bool success, bytes memory data) = address(converter).staticcall.gas(10000)( + abi.encodeWithSelector( + converter.getReserveRatio.selector, + token + ) + ); + + if (!success) { + (, uint32 ratio, , ,) = converter.connectors(address(token)); + + return uint256(ratio); } + + return abi.decode(data, (uint256)); + } + + function _canonicalSUSD(IERC20 token) internal pure returns(IERC20) { + return token == acientSUSD ? susd : token; } } @@ -48,8 +86,8 @@ contract OneSplitSmartTokenView is OneSplitViewWrapBase, OneSplitSmartTokenBase public view returns( - uint256 returnAmount, - uint256[] memory distribution + uint256, + uint256[] memory ) { if (fromToken == toToken) { @@ -57,85 +95,57 @@ contract OneSplitSmartTokenView is OneSplitViewWrapBase, OneSplitSmartTokenBase } if (!flags.check(FLAG_DISABLE_SMART_TOKEN)) { - distribution = new uint256[](DEXES_COUNT); - if (smartTokenRegistry.isSmartToken(fromToken)) { - this; - // ISmartTokenConverter converter = ISmartToken(address(fromToken)).owner(); - - // TokensWithRatio memory tokens = _getTokens(converter); - - // for (uint256 i = 0; i < tokens.tokens.length; i++) { - // uint256 srcAmount = smartTokenFormula.calculateLiquidateReturn( - // toToken.totalSupply(), - // tokens.tokens[i].balanceOf(address(converter)), - // uint32(tokens.totalRatio), - // amount - // ); - - // (uint256 ret, uint256[] memory dist) = super.getExpectedReturn( - // tokens.tokens[i], - // toToken, - // srcAmount, - // parts, - // flags - // ); - - // returnAmount = returnAmount.add(ret); - // for (uint j = 0; j < distribution.length; j++) { - // distribution[j] = distribution[j].add(dist[j] << (i * 8)); - // } - // } - // return (returnAmount, distribution); + bool isSmartTokenFrom = smartTokenRegistry.isSmartToken(fromToken); + bool isSmartTokenTo = smartTokenRegistry.isSmartToken(toToken); + + if (isSmartTokenFrom && isSmartTokenTo) { + ( + uint256 returnBntAmount, + uint256[] memory smartTokenFromDistribution + ) = _getExpectedReturnFromSmartToken( + fromToken, + bntToken, + amount, + parts, + FLAG_DISABLE_SMART_TOKEN + ); + + ( + uint256 returnSmartTokenToAmount, + uint256[] memory smartTokenToDistribution + ) = _getExpectedReturnToSmartToken( + bntToken, + toToken, + returnBntAmount, + parts, + FLAG_DISABLE_SMART_TOKEN + ); + + for (uint i = 0; i < smartTokenToDistribution.length; i++) { + smartTokenFromDistribution[i] |= smartTokenToDistribution[i] << 128; + } + + return (returnSmartTokenToAmount, smartTokenFromDistribution); } - if (smartTokenRegistry.isSmartToken(toToken)) { - this; - // ISmartTokenConverter converter = ISmartToken(address(fromToken)).owner(); - - // TokensWithRatio memory tokens = _getTokens(converter); - - // uint256 minFundAmount = uint256(-1); - // uint256[] memory fundAmounts = new uint256[](tokens.tokens.length); - // for (uint256 i = 0; i < tokens.tokens.length; i++) { - // (uint256 tokenAmount, uint256[] memory dist) = super.getExpectedReturn( - // fromToken, - // tokens.tokens[i], - // amount.mul(tokens.ratios[i]).div(tokens.totalRatio), - // parts, - // flags | FLAG_DISABLE_BANCOR - // ); - // for (uint j = 0; j < distribution.length; j++) { - // distribution[j] = distribution[j].add(dist[j] << (i * 8)); - // } - - // fundAmounts[i] = toToken.totalSupply() - // .mul(tokenAmount) - // .div(tokens.tokens[i].balanceOf(address(converter))); - - // if (fundAmounts[i] < minFundAmount) { - // minFundAmount = fundAmounts[i]; - // } - // } - - // // Swap leftovers for SmartToken - // for (uint256 i = 0; i < tokens.tokens.length; i++) { - // uint256 leftover = fundAmounts[i].sub(minFundAmount) - // .mul(tokens.tokens[i].balanceOf(address(converter))) - // .div(toToken.totalSupply()); - - // if (leftover > 0) { - // minFundAmount = minFundAmount.add( - // smartTokenFormula.calculatePurchaseReturn( - // toToken.totalSupply(), - // tokens.tokens[i].balanceOf(address(converter)), - // uint32(tokens.totalRatio), - // leftover - // ) - // ); - // } - // } - - // return (minFundAmount, distribution); + if (isSmartTokenFrom) { + return _getExpectedReturnFromSmartToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_SMART_TOKEN + ); + } + + if (isSmartTokenTo) { + return _getExpectedReturnToSmartToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_SMART_TOKEN + ); } } @@ -147,6 +157,113 @@ contract OneSplitSmartTokenView is OneSplitViewWrapBase, OneSplitSmartTokenBase flags ); } + + function _getExpectedReturnFromSmartToken( + IERC20 smartToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + for (uint i = 0; i < details.tokens.length; i++) { + uint256 srcAmount = smartTokenFormula.calculateLiquidateReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + amount + ); + + if (details.tokens[i].token == toToken) { + returnAmount = returnAmount.add(srcAmount); + continue; + } + + (uint256 ret, uint256[] memory dist) = this.getExpectedReturn( + _canonicalSUSD(details.tokens[i].token), + toToken, + srcAmount, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToSmartToken( + IERC20 fromToken, + IERC20 smartToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 minFundAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + minFundAmount = uint256(-1); + + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + uint256[] memory tokenAmounts = new uint256[](details.tokens.length); + uint256[] memory dist; + uint256[] memory fundAmounts = new uint256[](details.tokens.length); + + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].ratio) + .div(details.totalRatio); + + if (details.tokens[i].token != fromToken) { + (tokenAmounts[i], dist) = this.getExpectedReturn( + fromToken, + _canonicalSUSD(details.tokens[i].token), + exchangeAmount, + parts, + flags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } else { + tokenAmounts[i] = exchangeAmount; + } + + fundAmounts[i] = smartTokenFormula.calculatePurchaseReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + tokenAmounts[i] + ); + + if (fundAmounts[i] < minFundAmount) { + minFundAmount = fundAmounts[i]; + } + } + + return (minFundAmount, distribution); + } } @@ -162,7 +279,62 @@ contract OneSplitSmartToken is OneSplitBaseWrap, OneSplitSmartTokenBase { return; } - // TODO: + if (!flags.check(FLAG_DISABLE_SMART_TOKEN)) { + + bool isSmartTokenFrom = smartTokenRegistry.isSmartToken(fromToken); + bool isSmartTokenTo = smartTokenRegistry.isSmartToken(toToken); + + if (isSmartTokenFrom && isSmartTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 bntBalanceBefore = bntToken.balanceOf(address(this)); + + _swapFromSmartToken( + fromToken, + bntToken, + amount, + dist, + FLAG_DISABLE_SMART_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 bntBalanceAfter = bntToken.balanceOf(address(this)); + + return _swapToSmartToken( + bntToken, + toToken, + bntBalanceAfter.sub(bntBalanceBefore), + dist, + FLAG_DISABLE_SMART_TOKEN + ); + } + + if (isSmartTokenFrom) { + return _swapFromSmartToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_SMART_TOKEN + ); + } + + if (isSmartTokenTo) { + return _swapToSmartToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_SMART_TOKEN + ); + } + } return super._swap( fromToken, @@ -172,4 +344,108 @@ contract OneSplitSmartToken is OneSplitBaseWrap, OneSplitSmartTokenBase { flags ); } + + function _swapFromSmartToken( + IERC20 smartToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + ISmartTokenConverter(details.converter).liquidate(amount); + + uint256[] memory dist = new uint256[](distribution.length); + + for (uint i = 0; i < details.tokens.length; i++) { + if (details.tokens[i].token == toToken) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + _canonicalSUSD(details.tokens[i].token), + toToken, + _canonicalSUSD(details.tokens[i].token).balanceOf(address(this)), + 0, + dist, + flags + ); + } + } + + function _swapToSmartToken( + IERC20 fromToken, + IERC20 smartToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + + uint256[] memory dist = new uint256[](distribution.length); + uint256 minFundAmount = uint256(-1); + + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + uint256 curFundAmount; + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].ratio) + .div(details.totalRatio); + + if (details.tokens[i].token != fromToken) { + + uint256 tokenBalanceBefore = _canonicalSUSD(details.tokens[i].token).balanceOf(address(this)); + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + fromToken, + _canonicalSUSD(details.tokens[i].token), + exchangeAmount, + 0, + dist, + flags + ); + + uint256 tokenBalanceAfter = _canonicalSUSD(details.tokens[i].token).balanceOf(address(this)); + + curFundAmount = smartTokenFormula.calculatePurchaseReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + tokenBalanceAfter.sub(tokenBalanceBefore) + ); + } else { + curFundAmount = smartTokenFormula.calculatePurchaseReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + exchangeAmount + ); + } + + if (curFundAmount < minFundAmount) { + minFundAmount = curFundAmount; + } + + _infiniteApproveIfNeeded(_canonicalSUSD(details.tokens[i].token), details.converter); + } + + ISmartTokenConverter(details.converter).fund(minFundAmount); + + for (uint i = 0; i < details.tokens.length; i++) { + IERC20 reserveToken = _canonicalSUSD(details.tokens[i].token); + reserveToken.universalTransfer( + msg.sender, + reserveToken.universalBalanceOf(address(this)) + ); + } + } } diff --git a/contracts/OneSplitUniswapPoolToken.sol b/contracts/OneSplitUniswapPoolToken.sol new file mode 100644 index 0000000..c160491 --- /dev/null +++ b/contracts/OneSplitUniswapPoolToken.sol @@ -0,0 +1,467 @@ +pragma solidity ^0.5.0; + +import "./interface/IUniswapExchange.sol"; +import "./interface/IUniswapFactory.sol"; +import "./OneSplitBase.sol"; + + +contract OneSplitUniswapPoolTokenBase { + using SafeMath for uint256; + + IUniswapFactory constant uniswapFactory = IUniswapFactory(0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95); + + function isLiquidityPool(IERC20 token) internal view returns (bool) { + return address(uniswapFactory.getToken(address(token))) != address(0); + } + + function getMaxPossibleFund( + IERC20 poolToken, + IERC20 uniswapToken, + uint256 tokenAmount, + uint256 existEthAmount + ) + internal + view + returns ( + uint256, + uint256 + ) + { + uint256 ethReserve = address(poolToken).balance; + uint256 totalLiquidity = poolToken.totalSupply(); + uint256 tokenReserve = uniswapToken.balanceOf(address(poolToken)); + + uint256 possibleEthAmount = ethReserve.mul( + tokenAmount.sub(1) + ).div(tokenReserve); + + if (existEthAmount > possibleEthAmount) { + return ( + possibleEthAmount, + possibleEthAmount.mul(totalLiquidity).div(ethReserve) + ); + } + + return ( + existEthAmount, + existEthAmount.mul(totalLiquidity).div(ethReserve) + ); + } + +} + + +contract OneSplitUniswapPoolTokenView is OneSplitViewWrapBase, OneSplitUniswapPoolTokenBase { + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + public + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!flags.check(FLAG_DISABLE_UNISWAP_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + ( + uint256 returnETHAmount, + uint256[] memory poolTokenFromDistribution + ) = _getExpectedReturnFromPoolToken( + fromToken, + ETH_ADDRESS, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + ( + uint256 returnPoolTokenToAmount, + uint256[] memory poolTokenToDistribution + ) = _getExpectedReturnToPoolToken( + ETH_ADDRESS, + toToken, + returnETHAmount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + for (uint i = 0; i < poolTokenToDistribution.length; i++) { + poolTokenFromDistribution[i] |= poolTokenToDistribution[i] << 128; + } + + return (returnPoolTokenToAmount, poolTokenFromDistribution); + } + + if (isPoolTokenFrom) { + return _getExpectedReturnFromPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _getExpectedReturnToPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } + + function _getExpectedReturnFromPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + + distribution = new uint256[](DEXES_COUNT); + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 totalSupply = poolToken.totalSupply(); + + uint256 ethReserve = address(poolToken).balance; + uint256 ethAmount = amount.mul(ethReserve).div(totalSupply); + + if (!toToken.isETH()) { + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + ETH_ADDRESS, + toToken, + ethAmount, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j]; + } + } else { + returnAmount = returnAmount.add(ethAmount); + } + + uint256 tokenReserve = uniswapToken.balanceOf(address(poolToken)); + uint256 exchangeTokenAmount = amount.mul(tokenReserve).div(totalSupply); + + if (toToken != uniswapToken) { + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + uniswapToken, + toToken, + exchangeTokenAmount, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << 8; + } + } else { + returnAmount = returnAmount.add(exchangeTokenAmount); + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + + distribution = new uint256[](DEXES_COUNT); + + uint256[] memory dist = new uint256[](DEXES_COUNT); + + uint256 ethAmount; + uint256 partAmountForEth = amount.div(2); + if (!fromToken.isETH()) { + (ethAmount, dist) = super.getExpectedReturn( + fromToken, + ETH_ADDRESS, + partAmountForEth, + parts, + flags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j]; + } + } else { + ethAmount = partAmountForEth; + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 tokenAmount; + uint256 partAmountForToken = amount.sub(partAmountForEth); + if (fromToken != uniswapToken) { + (tokenAmount, dist) = super.getExpectedReturn( + fromToken, + uniswapToken, + partAmountForToken, + parts, + flags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << 8; + } + } else { + tokenAmount = partAmountForToken; + } + + (, returnAmount) = getMaxPossibleFund( + poolToken, + uniswapToken, + tokenAmount, + ethAmount + ); + + return ( + returnAmount, + distribution + ); + } + +} + + +contract OneSplitUniswapPoolToken is OneSplitBaseWrap, OneSplitUniswapPoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!flags.check(FLAG_DISABLE_UNISWAP_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 ethBalanceBefore = address(this).balance; + + _swapFromPoolToken( + fromToken, + ETH_ADDRESS, + amount, + dist, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 ethBalanceAfter = address(this).balance; + + return _swapToPoolToken( + ETH_ADDRESS, + toToken, + ethBalanceAfter.sub(ethBalanceBefore), + dist, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenFrom) { + return _swapFromPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _swapToPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + flags + ); + } + + function _swapFromPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + + uint256[] memory dist = new uint256[](distribution.length); + + ( + uint256 ethAmount, + uint256 exchangeTokenAmount + ) = IUniswapExchange(address(poolToken)).removeLiquidity( + amount, + 1, + 1, + now.add(1800) + ); + + if (!toToken.isETH()) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j]) & 0xFF; + } + + super._swap( + ETH_ADDRESS, + toToken, + ethAmount, + dist, + flags + ); + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + if (toToken != uniswapToken) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> 8) & 0xFF; + } + + super._swap( + uniswapToken, + toToken, + exchangeTokenAmount, + dist, + flags + ); + } + } + + function _swapToPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + uint256[] memory dist = new uint256[](distribution.length); + + uint256 partAmountForEth = amount.div(2); + if (!fromToken.isETH()) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j]) & 0xFF; + } + + super._swap( + fromToken, + ETH_ADDRESS, + partAmountForEth, + dist, + flags + ); + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 partAmountForToken = amount.sub(partAmountForEth); + if (fromToken != uniswapToken) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> 8) & 0xFF; + } + + super._swap( + fromToken, + uniswapToken, + partAmountForToken, + dist, + flags + ); + + _infiniteApproveIfNeeded(uniswapToken, address(poolToken)); + } + + uint256 ethBalance = address(this).balance; + uint256 tokenBalance = uniswapToken.balanceOf(address(this)); + + (uint256 ethAmount, uint256 returnAmount) = getMaxPossibleFund( + poolToken, + uniswapToken, + tokenBalance, + ethBalance + ); + + IUniswapExchange(address(poolToken)).addLiquidity.value(ethAmount)( + returnAmount.mul(995).div(1000), // 0.5% slippage + uint256(-1), // todo: think about another value + now.add(1800) + ); + + // todo: do we need to check difference between balance before and balance after? + uniswapToken.universalTransfer(msg.sender, uniswapToken.balanceOf(address(this))); + ETH_ADDRESS.universalTransfer(msg.sender, address(this).balance); + } +} diff --git a/contracts/OneSplitUniswapV2PoolToken.sol b/contracts/OneSplitUniswapV2PoolToken.sol new file mode 100644 index 0000000..81c6289 --- /dev/null +++ b/contracts/OneSplitUniswapV2PoolToken.sol @@ -0,0 +1,544 @@ +pragma solidity ^0.5.0; + +import "./OneSplitBase.sol"; +import "./interface/IUniswapV2Router.sol"; +import "./interface/IUniswapV2Pair.sol"; + + +library Math { + function min(uint x, uint y) internal pure returns (uint z) { + z = x < y ? x : y; + } + + // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) + function sqrt(uint y) internal pure returns (uint z) { + if (y > 3) { + z = y; + uint x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } +} + + +contract OneSplitUniswapV2PoolTokenBase { + using SafeMath for uint256; + + IUniswapV2Router constant uniswapRouter = IUniswapV2Router(0xf164fC0Ec4E93095b804a4795bBe1e041497b92a); + + function isLiquidityPool(IERC20 token) internal view returns (bool) { + (bool success, bytes memory data) = address(token).staticcall.gas(2000)( + abi.encode(IUniswapV2Pair(address(token)).factory.selector) + ); + if (!success || data.length == 0) { + return false; + } + return abi.decode(data, (address)) == 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + } + + struct TokenInfo { + IERC20 token; + uint256 reserve; + } + + struct PoolDetails { + TokenInfo[2] tokens; + uint256 totalSupply; + } + + function _getPoolDetails(IUniswapV2Pair pair) internal view returns (PoolDetails memory details) { + (uint112 reserve0, uint112 reserve1, ) = pair.getReserves(); + + details.tokens[0] = TokenInfo({ + token: pair.token0(), + reserve: reserve0 + }); + details.tokens[1] = TokenInfo({ + token: pair.token1(), + reserve: reserve1 + }); + + details.totalSupply = IERC20(address(pair)).totalSupply(); + } + + function _calcRebalanceAmount( + uint256 leftover, + uint256 balanceOfLeftoverAsset, + uint256 secondAssetBalance + ) internal pure returns (uint256) { + + return Math.sqrt( + 3988000 * leftover * balanceOfLeftoverAsset + + 3988009 * balanceOfLeftoverAsset * balanceOfLeftoverAsset - + 9 * balanceOfLeftoverAsset * balanceOfLeftoverAsset / (secondAssetBalance - 1) + ) / 1994 - balanceOfLeftoverAsset * 1997 / 1994; + } + +} + + +contract OneSplitUniswapV2PoolTokenView is OneSplitViewWrapBase, OneSplitUniswapV2PoolTokenBase { + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + public + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!flags.check(FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + ( + uint256 returnWETHAmount, + uint256[] memory poolTokenFromDistribution + ) = _getExpectedReturnFromUniswapV2PoolToken( + fromToken, + weth, + amount, + parts, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + + ( + uint256 returnPoolTokenToAmount, + uint256[] memory poolTokenToDistribution + ) = _getExpectedReturnToUniswapV2PoolToken( + weth, + toToken, + returnWETHAmount, + parts, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + + for (uint i = 0; i < poolTokenToDistribution.length; i++) { + poolTokenFromDistribution[i] |= poolTokenToDistribution[i] << 128; + } + + return (returnPoolTokenToAmount, poolTokenFromDistribution); + } + + if (isPoolTokenFrom) { + return _getExpectedReturnFromUniswapV2PoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _getExpectedReturnToUniswapV2PoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + flags + ); + } + + function _getExpectedReturnFromUniswapV2PoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + PoolDetails memory details = _getPoolDetails(IUniswapV2Pair(address(poolToken))); + + for (uint i = 0; i < 2; i++) { + + uint256 exchangeAmount = amount + .mul(details.tokens[i].reserve) + .div(details.totalSupply); + + if (toToken == details.tokens[i].token) { + returnAmount = returnAmount.add(exchangeAmount); + continue; + } + + (uint256 ret, uint256[] memory dist) = this.getExpectedReturn( + details.tokens[i].token, + toToken, + exchangeAmount, + parts, + flags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToUniswapV2PoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256 parts, + uint256 flags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + PoolDetails memory details = _getPoolDetails(IUniswapV2Pair(address(poolToken))); + + // will overwritten to liquidity amounts + uint256[2] memory amounts; + amounts[0] = amount.div(2); + amounts[1] = amount.sub(amounts[0]); + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < 2; i++) { + + if (fromToken == details.tokens[i].token) { + continue; + } + + (amounts[i], dist) = this.getExpectedReturn( + fromToken, + details.tokens[i].token, + amounts[i], + parts, + flags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + uint256 possibleLiquidity0 = amounts[0].mul(details.totalSupply).div(details.tokens[0].reserve); + returnAmount = Math.min( + possibleLiquidity0, + amounts[1].mul(details.totalSupply).div(details.tokens[1].reserve) + ); + + uint256 leftoverIndex = possibleLiquidity0 > returnAmount ? 0 : 1; + IERC20[] memory path = new IERC20[](2); + path[0] = details.tokens[leftoverIndex].token; + path[1] = details.tokens[1 - leftoverIndex].token; + + uint256 optimalAmount = amounts[1 - leftoverIndex].mul( + details.tokens[leftoverIndex].reserve + ).div(details.tokens[1 - leftoverIndex].reserve); + + IERC20 _poolToken = poolToken; // stack too deep + uint256 exchangeAmount = _calcRebalanceAmount( + amounts[leftoverIndex].sub(optimalAmount), + path[0].balanceOf(address(_poolToken)).add(optimalAmount), + path[1].balanceOf(address(_poolToken)).add(amounts[1 - leftoverIndex]) + ); + + (bool success, bytes memory data) = address(uniswapRouter).staticcall.gas(200000)( + abi.encodeWithSelector( + uniswapRouter.getAmountsOut.selector, + exchangeAmount, + path + ) + ); + + if (!success) { + return ( + returnAmount, + distribution + ); + } + + uint256[] memory amountsOutAfterSwap = abi.decode(data, (uint256[])); + + uint256 _addedLiquidity = returnAmount; // stack too deep + PoolDetails memory _details = details; // stack too deep + returnAmount = _addedLiquidity.add( + amountsOutAfterSwap[1] // amountOut after swap + .mul(_details.totalSupply.add(_addedLiquidity)) + .div(_details.tokens[1 - leftoverIndex].reserve.sub(amountsOutAfterSwap[1])) + ); + + return ( + returnAmount, + distribution + ); + } + +} + + +contract OneSplitUniswapV2PoolToken is OneSplitBaseWrap, OneSplitUniswapV2PoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!flags.check(FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 wEthBalanceBefore = weth.balanceOf(address(this)); + + _swapFromUniswapV2PoolToken( + fromToken, + weth, + amount, + dist, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 wEthBalanceAfter = weth.balanceOf(address(this)); + + return _swapToUniswapV2PoolToken( + weth, + toToken, + wEthBalanceAfter.sub(wEthBalanceBefore), + dist, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + } + + if (isPoolTokenFrom) { + return _swapFromUniswapV2PoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _swapToUniswapV2PoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_V2_POOL_TOKEN + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + flags + ); + } + + function _swapFromUniswapV2PoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + _infiniteApproveIfNeeded(poolToken, address(uniswapRouter)); + + IERC20 [2] memory tokens = [ + IUniswapV2Pair(address(poolToken)).token0(), + IUniswapV2Pair(address(poolToken)).token1() + ]; + + uint256[2] memory amounts = uniswapRouter.removeLiquidity( + tokens[0], + tokens[1], + amount, + uint256(0), + uint256(0), + address(this), + now.add(1800) + ); + + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < 2; i++) { + + if (toToken == tokens[i]) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + tokens[i], + toToken, + amounts[i], + 0, + dist, + flags + ); + } + } + + function _swapToUniswapV2PoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 flags + ) private { + IERC20 [2] memory tokens = [ + IUniswapV2Pair(address(poolToken)).token0(), + IUniswapV2Pair(address(poolToken)).token1() + ]; + + // will overwritten to liquidity amounts + uint256[2] memory amounts; + amounts[0] = amount.div(2); + amounts[1] = amount.sub(amounts[0]); + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < 2; i++) { + + _infiniteApproveIfNeeded(tokens[i], address(uniswapRouter)); + + if (fromToken == tokens[i]) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + fromToken, + tokens[i], + amounts[i], + 0, + dist, + flags + ); + + amounts[i] = tokens[i].universalBalanceOf(address(this)); + } + + (uint256[2] memory redeemAmounts, ) = uniswapRouter.addLiquidity( + tokens[0], + tokens[1], + amounts[0], + amounts[1], + uint256(0), + uint256(0), + address(this), + now.add(1800) + ); + + if ( + redeemAmounts[0] == amounts[0] && + redeemAmounts[1] == amounts[1] + ) { + return; + } + + uint256 leftoverIndex = amounts[0] != redeemAmounts[0] ? 0 : 1; + IERC20[] memory path = new IERC20[](2); + path[0] = tokens[leftoverIndex]; + path[1] = tokens[1 - leftoverIndex]; + + address _poolToken = address(poolToken); // stack too deep + uint256 leftover = amounts[leftoverIndex].sub(redeemAmounts[leftoverIndex]); + uint256 exchangeAmount = _calcRebalanceAmount( + leftover, + path[0].balanceOf(_poolToken), + path[1].balanceOf(_poolToken) + ); + + (bool success, bytes memory data) = address(uniswapRouter).call.gas(1000000)( + abi.encodeWithSelector( + uniswapRouter.swapExactTokensForTokens.selector, + exchangeAmount, + uint256(0), + path, + address(this), + now.add(1800) + ) + ); + + if (!success) { + return; + } + + uint256[] memory amountsOut = abi.decode(data, (uint256[])); + + address(uniswapRouter).call.gas(1000000)( + abi.encodeWithSelector( + uniswapRouter.addLiquidity.selector, + tokens[0], + tokens[1], + leftoverIndex == 0 + ? leftover.sub(amountsOut[0]) + : amountsOut[1], + leftoverIndex == 1 + ? leftover.sub(amountsOut[0]) + : amountsOut[1], + uint256(0), + uint256(0), + address(this), + now.add(1800) + ) + ); + } +} diff --git a/contracts/interface/IBFactory.sol b/contracts/interface/IBFactory.sol new file mode 100644 index 0000000..f89d171 --- /dev/null +++ b/contracts/interface/IBFactory.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.5.0; + +interface IBFactory { + function isBPool(address b) external view returns (bool); +} diff --git a/contracts/interface/IBPool.sol b/contracts/interface/IBPool.sol new file mode 100644 index 0000000..b9a75d2 --- /dev/null +++ b/contracts/interface/IBPool.sol @@ -0,0 +1,43 @@ +pragma solidity ^0.5.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + + +contract BConst { + uint public constant EXIT_FEE = 0; +} + + +contract IBMath is BConst { + function calcPoolOutGivenSingleIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint tokenAmountIn, + uint swapFee + ) + public + pure returns (uint poolAmountOut); +} + + +contract IBPool is IERC20, IBMath { + function joinPool(uint poolAmountOut, uint[] calldata maxAmountsIn) external; + + function exitPool(uint poolAmountIn, uint[] calldata minAmountsOut) external; + + function joinswapExternAmountIn(address tokenIn, uint tokenAmountIn, uint minPoolAmountOut) external returns (uint poolAmountOut); + + function getCurrentTokens() external view returns (address[] memory tokens); + + function getBalance(address token) external view returns (uint); + + function getNormalizedWeight(address token) external view returns (uint); + + function getDenormalizedWeight(address token) external view returns (uint); + + function getTotalDenormalizedWeight() external view returns (uint); + + function getSwapFee() external view returns (uint); +} diff --git a/contracts/interface/ICurve.sol b/contracts/interface/ICurve.sol index 652e409..64977bb 100644 --- a/contracts/interface/ICurve.sol +++ b/contracts/interface/ICurve.sol @@ -5,6 +5,12 @@ interface ICurve { // solium-disable-next-line mixedcase function get_dy_underlying(int128 i, int128 j, uint256 dx) external view returns(uint256 dy); + function get_virtual_price() external view returns(uint256); + // solium-disable-next-line mixedcase function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 minDy) external; + + function coins(int128 arg0) external view returns (address); + + function balances(int128 arg0) external view returns (uint256); } diff --git a/contracts/interface/ISmartTokenConverter.sol b/contracts/interface/ISmartTokenConverter.sol index 45e8927..02f4744 100644 --- a/contracts/interface/ISmartTokenConverter.sol +++ b/contracts/interface/ISmartTokenConverter.sol @@ -2,11 +2,24 @@ pragma solidity ^0.5.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - interface ISmartTokenConverter { - function getReserveRatio(IERC20 token) external view returns (uint32); + + function version() external view returns (uint16); + + function connectors(address) external view returns (uint256, uint32, bool, bool, bool); + + function getReserveRatio(IERC20 token) external view returns (uint256); function connectorTokenCount() external view returns (uint256); function connectorTokens(uint256 i) external view returns (IERC20); + + function liquidate(uint256 _amount) external; + + function fund(uint256 _amount) external; + + function convert2(IERC20 _fromToken, IERC20 _toToken, uint256 _amount, uint256 _minReturn, address _affiliateAccount, uint256 _affiliateFee) external returns (uint256); + + function convert(IERC20 _fromToken, IERC20 _toToken, uint256 _amount, uint256 _minReturn) external returns (uint256); + } diff --git a/contracts/interface/IUniswapExchange.sol b/contracts/interface/IUniswapExchange.sol index f1200c3..0196bd6 100644 --- a/contracts/interface/IUniswapExchange.sol +++ b/contracts/interface/IUniswapExchange.sol @@ -24,4 +24,8 @@ interface IUniswapExchange { uint256 deadline, address tokenAddr ) external returns (uint256 tokensBought); + + function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); + + function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256); } diff --git a/contracts/interface/IUniswapFactory.sol b/contracts/interface/IUniswapFactory.sol index bc352fc..14f9069 100644 --- a/contracts/interface/IUniswapFactory.sol +++ b/contracts/interface/IUniswapFactory.sol @@ -5,4 +5,6 @@ import "./IUniswapExchange.sol"; interface IUniswapFactory { function getExchange(IERC20 token) external view returns (IUniswapExchange exchange); + + function getToken(address exchange) external view returns (IERC20 token); } diff --git a/contracts/interface/IUniswapV2Pair.sol b/contracts/interface/IUniswapV2Pair.sol new file mode 100644 index 0000000..3a46df6 --- /dev/null +++ b/contracts/interface/IUniswapV2Pair.sol @@ -0,0 +1,15 @@ +pragma solidity ^0.5.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IUniswapV2Pair { + function factory() external view returns (address); + + function token0() external view returns (IERC20); + function token1() external view returns (IERC20); + + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); +} diff --git a/contracts/interface/IUniswapV2Router.sol b/contracts/interface/IUniswapV2Router.sol new file mode 100644 index 0000000..d74e192 --- /dev/null +++ b/contracts/interface/IUniswapV2Router.sol @@ -0,0 +1,36 @@ +pragma solidity ^0.5.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IUniswapV2Router { + function addLiquidity( + IERC20 tokenA, + IERC20 tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint256[2] memory amounts, uint liquidity); + + function removeLiquidity( + IERC20 tokenA, + IERC20 tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint256[2] memory); + + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + IERC20[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function getAmountsOut(uint amountIn, IERC20[] calldata path) external view returns (uint[] memory amounts); +} diff --git a/test/OneSplit.js b/test/OneSplit.js index 1ce439d..0694638 100644 --- a/test/OneSplit.js +++ b/test/OneSplit.js @@ -1,4 +1,4 @@ -// const { expectRevert } = require('openzeppelin-test-helpers'); +// const { expectRevert } = require('@openzeppelin/test-helpers'); // const { expect } = require('chai'); const assert = require('assert');