Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
30835d664c | ||
|
7972c2ce4e | ||
|
173d4ce648 | ||
|
59542f0585 | ||
|
446ef9660e | ||
|
9de1f0263a |
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "0.27.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Refactor Mixins which use WETH to also have an Internal variant, allowing WETH to be passed in for SwapRevertSampler",
|
||||
"pr": 245
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.26.0",
|
||||
"changes": [
|
||||
|
@@ -25,6 +25,7 @@ import "./BridgeProtocols.sol";
|
||||
import "./mixins/MixinBalancer.sol";
|
||||
import "./mixins/MixinBalancerV2.sol";
|
||||
import "./mixins/MixinBancor.sol";
|
||||
import "./mixins/MixinBooster.sol";
|
||||
import "./mixins/MixinCoFiX.sol";
|
||||
import "./mixins/MixinCurve.sol";
|
||||
import "./mixins/MixinCurveV2.sol";
|
||||
@@ -50,6 +51,7 @@ contract BridgeAdapter is
|
||||
MixinBalancer,
|
||||
MixinBalancerV2,
|
||||
MixinBancor,
|
||||
MixinBooster,
|
||||
MixinCoFiX,
|
||||
MixinCurve,
|
||||
MixinCurveV2,
|
||||
@@ -75,6 +77,7 @@ contract BridgeAdapter is
|
||||
MixinBalancer()
|
||||
MixinBalancerV2()
|
||||
MixinBancor(weth)
|
||||
MixinBooster()
|
||||
MixinCoFiX()
|
||||
MixinCurve(weth)
|
||||
MixinCurveV2()
|
||||
@@ -245,6 +248,13 @@ contract BridgeAdapter is
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else if (protocolId == BridgeProtocols.BOOSTER) {
|
||||
boughtAmount = _tradeBooster(
|
||||
sellToken,
|
||||
buyToken,
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else {
|
||||
boughtAmount = _tradeZeroExBridge(
|
||||
sellToken,
|
||||
|
@@ -49,4 +49,5 @@ library BridgeProtocols {
|
||||
uint128 internal constant KYBERDMM = 19;
|
||||
uint128 internal constant CURVEV2 = 20;
|
||||
uint128 internal constant LIDO = 21;
|
||||
uint128 internal constant BOOSTER = 22;
|
||||
}
|
||||
|
@@ -39,6 +39,13 @@ interface IBancorNetwork {
|
||||
external
|
||||
payable
|
||||
returns (uint256);
|
||||
function conversionPath(address _sourceToken, address _targetToken) external view returns (address[] memory);
|
||||
function rateByPath(address[] memory _path, uint256 _amount) external view returns (uint256);
|
||||
}
|
||||
|
||||
interface IBancorRegistry {
|
||||
function getAddress(bytes32 _contractName) external view returns (address);
|
||||
function BANCOR_NETWORK() external view returns (bytes32);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +69,18 @@ contract MixinBancor {
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
return _tradeBancorInternal(WETH, buyToken, sellAmount, bridgeData);
|
||||
}
|
||||
|
||||
function _tradeBancorInternal(
|
||||
IEtherTokenV06 weth,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
// Decode the bridge data.
|
||||
IBancorNetwork bancorNetworkAddress;
|
||||
@@ -79,7 +98,7 @@ contract MixinBancor {
|
||||
require(path.length >= 2, "MixinBancor/PATH_LENGTH_MUST_BE_AT_LEAST_TWO");
|
||||
require(
|
||||
path[path.length - 1] == buyToken ||
|
||||
(path[path.length - 1] == BANCOR_ETH_ADDRESS && buyToken == WETH),
|
||||
(path[path.length - 1] == BANCOR_ETH_ADDRESS && buyToken == weth),
|
||||
"MixinBancor/LAST_ELEMENT_OF_PATH_MUST_MATCH_OUTPUT_TOKEN"
|
||||
);
|
||||
|
||||
@@ -88,7 +107,7 @@ contract MixinBancor {
|
||||
// The Bancor path will have ETH as the 0xeee address
|
||||
// Bancor expects to be paid in ETH not WETH
|
||||
if (path[0] == BANCOR_ETH_ADDRESS) {
|
||||
WETH.withdraw(sellAmount);
|
||||
weth.withdraw(sellAmount);
|
||||
payableAmount = sellAmount;
|
||||
} else {
|
||||
// Grant an allowance to the Bancor Network.
|
||||
@@ -109,7 +128,7 @@ contract MixinBancor {
|
||||
0 // affiliateFee; no fee paid
|
||||
);
|
||||
if (path[path.length - 1] == BANCOR_ETH_ADDRESS) {
|
||||
WETH.deposit{value: boughtAmount}();
|
||||
weth.deposit{value: boughtAmount}();
|
||||
}
|
||||
|
||||
return boughtAmount;
|
||||
|
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "../IBridgeAdapter.sol";
|
||||
|
||||
|
||||
interface IBooster {
|
||||
function swap(
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
address recipient
|
||||
)
|
||||
external
|
||||
returns (uint256);
|
||||
}
|
||||
|
||||
contract MixinBooster {
|
||||
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
|
||||
function _tradeBooster(
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
// Decode the bridge data.
|
||||
(IBooster pool) = abi.decode(bridgeData, (IBooster));
|
||||
// Grant the pool an allowance.
|
||||
sellToken.approveIfBelow(address(pool), sellAmount);
|
||||
|
||||
// Convert the tokens
|
||||
boughtAmount = pool.swap(
|
||||
sellToken,
|
||||
buyToken,
|
||||
sellAmount,
|
||||
address(this)
|
||||
);
|
||||
|
||||
return boughtAmount;
|
||||
}
|
||||
}
|
@@ -57,13 +57,26 @@ contract MixinCurve {
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
return _tradeCurveInternal(WETH, sellToken, buyToken, sellAmount, bridgeData);
|
||||
}
|
||||
|
||||
function _tradeCurveInternal(
|
||||
IEtherTokenV06 weth,
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
// Decode the bridge data to get the Curve metadata.
|
||||
CurveBridgeData memory data = abi.decode(bridgeData, (CurveBridgeData));
|
||||
uint256 payableAmount;
|
||||
if (sellToken == WETH) {
|
||||
if (sellToken == weth) {
|
||||
payableAmount = sellAmount;
|
||||
WETH.withdraw(sellAmount);
|
||||
weth.withdraw(sellAmount);
|
||||
} else {
|
||||
sellToken.approveIfBelow(data.curveAddress, sellAmount);
|
||||
}
|
||||
@@ -83,9 +96,9 @@ contract MixinCurve {
|
||||
resultData.rrevert();
|
||||
}
|
||||
|
||||
if (buyToken == WETH) {
|
||||
if (buyToken == weth) {
|
||||
boughtAmount = address(this).balance;
|
||||
WETH.deposit{ value: boughtAmount }();
|
||||
weth.deposit{ value: boughtAmount }();
|
||||
}
|
||||
|
||||
return buyToken.balanceOf(address(this)).safeSub(beforeBalance);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@@ -52,6 +52,7 @@ interface IKyberNetworkProxy {
|
||||
external
|
||||
payable
|
||||
returns (uint256 boughtAmount);
|
||||
|
||||
}
|
||||
|
||||
contract MixinKyber {
|
||||
@@ -78,12 +79,26 @@ contract MixinKyber {
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
return _tradeKyberInternal(KYBER_ETH_ADDRESS, WETH, sellToken, buyToken, sellAmount, bridgeData);
|
||||
}
|
||||
|
||||
function _tradeKyberInternal(
|
||||
IERC20TokenV06 kyberEthAddress,
|
||||
IEtherTokenV06 weth,
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
(IKyberNetworkProxy kyber, bytes memory hint) =
|
||||
abi.decode(bridgeData, (IKyberNetworkProxy, bytes));
|
||||
|
||||
uint256 payableAmount = 0;
|
||||
if (sellToken != WETH) {
|
||||
if (sellToken != weth) {
|
||||
// If the input token is not WETH, grant an allowance to the exchange
|
||||
// to spend them.
|
||||
sellToken.approveIfBelow(
|
||||
@@ -93,18 +108,18 @@ contract MixinKyber {
|
||||
} else {
|
||||
// If the input token is WETH, unwrap it and attach it to the call.
|
||||
payableAmount = sellAmount;
|
||||
WETH.withdraw(payableAmount);
|
||||
weth.withdraw(payableAmount);
|
||||
}
|
||||
|
||||
// Try to sell all of this contract's input token balance through
|
||||
// `KyberNetworkProxy.trade()`.
|
||||
boughtAmount = kyber.tradeWithHint{ value: payableAmount }(
|
||||
// Input token.
|
||||
sellToken == WETH ? KYBER_ETH_ADDRESS : sellToken,
|
||||
sellToken == weth ? kyberEthAddress : sellToken,
|
||||
// Sell amount.
|
||||
sellAmount,
|
||||
// Output token.
|
||||
buyToken == WETH ? KYBER_ETH_ADDRESS : buyToken,
|
||||
buyToken == weth ? kyberEthAddress : buyToken,
|
||||
// Transfer to this contract
|
||||
address(uint160(address(this))),
|
||||
// Buy as much as possible.
|
||||
@@ -116,8 +131,8 @@ contract MixinKyber {
|
||||
hint
|
||||
);
|
||||
// If receving ETH, wrap it to WETH.
|
||||
if (buyToken == WETH) {
|
||||
WETH.deposit{ value: boughtAmount }();
|
||||
if (buyToken == weth) {
|
||||
weth.deposit{ value: boughtAmount }();
|
||||
}
|
||||
return boughtAmount;
|
||||
}
|
||||
|
@@ -30,6 +30,8 @@ import "../IBridgeAdapter.sol";
|
||||
*/
|
||||
interface IKyberDmmRouter {
|
||||
|
||||
function factory() external view returns (address);
|
||||
|
||||
/// @dev Swaps an exact amount of input tokens for as many output tokens as possible, along the route determined by the path.
|
||||
/// The first element of path is the input token, the last is the output token, and any intermediate elements represent
|
||||
/// intermediate pairs to trade through (if, for example, a direct pair does not exist).
|
||||
|
@@ -58,10 +58,23 @@ contract MixinLido {
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
return _tradeLidoInternal(WETH, sellToken, buyToken, sellAmount, bridgeData);
|
||||
}
|
||||
|
||||
function _tradeLidoInternal(
|
||||
IEtherTokenV06 weth,
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
(ILido lido) = abi.decode(bridgeData, (ILido));
|
||||
if (address(sellToken) == address(WETH) && address(buyToken) == address(lido)) {
|
||||
WETH.withdraw(sellAmount);
|
||||
if (address(sellToken) == address(weth) && address(buyToken) == address(lido)) {
|
||||
weth.withdraw(sellAmount);
|
||||
boughtAmount = lido.getPooledEthByShares(lido.submit{ value: sellAmount}(address(0)));
|
||||
} else {
|
||||
revert("MixinLido/UNSUPPORTED_TOKEN_PAIR");
|
||||
|
@@ -66,12 +66,26 @@ contract MixinMooniswap {
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
|
||||
return _tradeMooniswapInternal(WETH, sellToken, buyToken, sellAmount, bridgeData);
|
||||
}
|
||||
|
||||
function _tradeMooniswapInternal(
|
||||
IEtherTokenV06 weth,
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
(IMooniswapPool pool) = abi.decode(bridgeData, (IMooniswapPool));
|
||||
|
||||
// Convert WETH to ETH.
|
||||
uint256 ethValue = 0;
|
||||
if (sellToken == WETH) {
|
||||
WETH.withdraw(sellAmount);
|
||||
if (sellToken == weth) {
|
||||
weth.withdraw(sellAmount);
|
||||
ethValue = sellAmount;
|
||||
} else {
|
||||
// Grant the pool an allowance.
|
||||
@@ -82,16 +96,16 @@ contract MixinMooniswap {
|
||||
}
|
||||
|
||||
boughtAmount = pool.swap{value: ethValue}(
|
||||
sellToken == WETH ? IERC20TokenV06(0) : sellToken,
|
||||
buyToken == WETH ? IERC20TokenV06(0) : buyToken,
|
||||
sellToken == weth ? IERC20TokenV06(0) : sellToken,
|
||||
buyToken == weth ? IERC20TokenV06(0) : buyToken,
|
||||
sellAmount,
|
||||
1,
|
||||
address(0)
|
||||
);
|
||||
|
||||
// Wrap ETH to WETH.
|
||||
if (buyToken == WETH) {
|
||||
WETH.deposit{value:boughtAmount}();
|
||||
if (buyToken == weth) {
|
||||
weth.deposit{value:boughtAmount}();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -124,21 +124,35 @@ contract MixinUniswap {
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
return _tradeUniswapInternal(WETH, sellToken, buyToken, sellAmount, bridgeData);
|
||||
}
|
||||
|
||||
function _tradeUniswapInternal(
|
||||
IEtherTokenV06 weth,
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
IUniswapExchangeFactory exchangeFactory =
|
||||
abi.decode(bridgeData, (IUniswapExchangeFactory));
|
||||
|
||||
// Get the exchange for the token pair.
|
||||
IUniswapExchange exchange = _getUniswapExchangeForTokenPair(
|
||||
weth,
|
||||
exchangeFactory,
|
||||
sellToken,
|
||||
buyToken
|
||||
);
|
||||
|
||||
// Convert from WETH to a token.
|
||||
if (sellToken == WETH) {
|
||||
if (sellToken == weth) {
|
||||
// Unwrap the WETH.
|
||||
WETH.withdraw(sellAmount);
|
||||
weth.withdraw(sellAmount);
|
||||
// Buy as much of `buyToken` token with ETH as possible
|
||||
boughtAmount = exchange.ethToTokenTransferInput{ value: sellAmount }(
|
||||
// Minimum buy amount.
|
||||
@@ -150,7 +164,7 @@ contract MixinUniswap {
|
||||
);
|
||||
|
||||
// Convert from a token to WETH.
|
||||
} else if (buyToken == WETH) {
|
||||
} else if (buyToken == weth) {
|
||||
// Grant the exchange an allowance.
|
||||
sellToken.approveIfBelow(
|
||||
address(exchange),
|
||||
@@ -166,7 +180,7 @@ contract MixinUniswap {
|
||||
block.timestamp
|
||||
);
|
||||
// Wrap the ETH.
|
||||
WETH.deposit{ value: boughtAmount }();
|
||||
weth.deposit{ value: boughtAmount }();
|
||||
// Convert from one token to another.
|
||||
} else {
|
||||
// Grant the exchange an allowance.
|
||||
@@ -200,6 +214,7 @@ contract MixinUniswap {
|
||||
/// @param buyToken The address of the token we are converting to.
|
||||
/// @return exchange The uniswap exchange.
|
||||
function _getUniswapExchangeForTokenPair(
|
||||
IEtherTokenV06 weth,
|
||||
IUniswapExchangeFactory exchangeFactory,
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken
|
||||
@@ -209,7 +224,7 @@ contract MixinUniswap {
|
||||
returns (IUniswapExchange exchange)
|
||||
{
|
||||
// Whichever isn't WETH is the exchange token.
|
||||
exchange = sellToken == WETH
|
||||
exchange = sellToken == weth
|
||||
? exchangeFactory.getExchange(buyToken)
|
||||
: exchangeFactory.getExchange(sellToken);
|
||||
require(address(exchange) != address(0), "MixinUniswap/NO_EXCHANGE");
|
||||
|
@@ -43,7 +43,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinBooster|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -82,6 +82,7 @@ import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransa
|
||||
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
|
||||
import * as MixinBalancerV2 from '../test/generated-artifacts/MixinBalancerV2.json';
|
||||
import * as MixinBancor from '../test/generated-artifacts/MixinBancor.json';
|
||||
import * as MixinBooster from '../test/generated-artifacts/MixinBooster.json';
|
||||
import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json';
|
||||
import * as MixinCryptoCom from '../test/generated-artifacts/MixinCryptoCom.json';
|
||||
import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json';
|
||||
@@ -257,6 +258,7 @@ export const artifacts = {
|
||||
MixinBalancer: MixinBalancer as ContractArtifact,
|
||||
MixinBalancerV2: MixinBalancerV2 as ContractArtifact,
|
||||
MixinBancor: MixinBancor as ContractArtifact,
|
||||
MixinBooster: MixinBooster as ContractArtifact,
|
||||
MixinCoFiX: MixinCoFiX as ContractArtifact,
|
||||
MixinCryptoCom: MixinCryptoCom as ContractArtifact,
|
||||
MixinCurve: MixinCurve as ContractArtifact,
|
||||
|
@@ -80,6 +80,7 @@ export * from '../test/generated-wrappers/meta_transactions_feature';
|
||||
export * from '../test/generated-wrappers/mixin_balancer';
|
||||
export * from '../test/generated-wrappers/mixin_balancer_v2';
|
||||
export * from '../test/generated-wrappers/mixin_bancor';
|
||||
export * from '../test/generated-wrappers/mixin_booster';
|
||||
export * from '../test/generated-wrappers/mixin_co_fi_x';
|
||||
export * from '../test/generated-wrappers/mixin_crypto_com';
|
||||
export * from '../test/generated-wrappers/mixin_curve';
|
||||
|
@@ -113,6 +113,7 @@
|
||||
"test/generated-artifacts/MixinBalancer.json",
|
||||
"test/generated-artifacts/MixinBalancerV2.json",
|
||||
"test/generated-artifacts/MixinBancor.json",
|
||||
"test/generated-artifacts/MixinBooster.json",
|
||||
"test/generated-artifacts/MixinCoFiX.json",
|
||||
"test/generated-artifacts/MixinCryptoCom.json",
|
||||
"test/generated-artifacts/MixinCurve.json",
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "7.0.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "SwapRevertSampler. Refactored sampler to use exchange functions. Remove gas schedule.",
|
||||
"pr": 245
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1624562704,
|
||||
"version": "6.18.2",
|
||||
|
@@ -1,144 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||
|
||||
|
||||
contract ApproximateBuys {
|
||||
|
||||
/// @dev Information computing buy quotes for sources that do not have native
|
||||
/// buy quote support.
|
||||
struct ApproximateBuyQuoteOpts {
|
||||
// Arbitrary maker token data to pass to `getSellQuoteCallback`.
|
||||
bytes makerTokenData;
|
||||
// Arbitrary taker token data to pass to `getSellQuoteCallback`.
|
||||
bytes takerTokenData;
|
||||
// Callback to retrieve a sell quote.
|
||||
function (bytes memory, bytes memory, uint256)
|
||||
internal
|
||||
view
|
||||
returns (uint256) getSellQuoteCallback;
|
||||
}
|
||||
|
||||
uint256 private constant ONE_HUNDED_PERCENT_BPS = 1e4;
|
||||
/// @dev Maximum approximate (positive) error rate when approximating a buy quote.
|
||||
uint256 private constant APPROXIMATE_BUY_TARGET_EPSILON_BPS = 0.0005e4;
|
||||
/// @dev Maximum iterations to perform when approximating a buy quote.
|
||||
uint256 private constant APPROXIMATE_BUY_MAX_ITERATIONS = 5;
|
||||
|
||||
function _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts memory opts,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
|
||||
if (makerTokenAmounts.length == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
uint256 sellAmount = opts.getSellQuoteCallback(
|
||||
opts.makerTokenData,
|
||||
opts.takerTokenData,
|
||||
makerTokenAmounts[0]
|
||||
);
|
||||
if (sellAmount == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
uint256 buyAmount = opts.getSellQuoteCallback(
|
||||
opts.takerTokenData,
|
||||
opts.makerTokenData,
|
||||
sellAmount
|
||||
);
|
||||
if (buyAmount == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
|
||||
for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) {
|
||||
// adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER
|
||||
sellAmount = _safeGetPartialAmountCeil(
|
||||
makerTokenAmounts[i],
|
||||
buyAmount,
|
||||
sellAmount
|
||||
);
|
||||
if (sellAmount == 0) {
|
||||
break;
|
||||
}
|
||||
sellAmount = _safeGetPartialAmountCeil(
|
||||
(ONE_HUNDED_PERCENT_BPS + APPROXIMATE_BUY_TARGET_EPSILON_BPS),
|
||||
ONE_HUNDED_PERCENT_BPS,
|
||||
sellAmount
|
||||
);
|
||||
if (sellAmount == 0) {
|
||||
break;
|
||||
}
|
||||
uint256 _buyAmount = opts.getSellQuoteCallback(
|
||||
opts.takerTokenData,
|
||||
opts.makerTokenData,
|
||||
sellAmount
|
||||
);
|
||||
if (_buyAmount == 0) {
|
||||
break;
|
||||
}
|
||||
// We re-use buyAmount next iteration, only assign if it is
|
||||
// non zero
|
||||
buyAmount = _buyAmount;
|
||||
// If we've reached our goal, exit early
|
||||
if (buyAmount >= makerTokenAmounts[i]) {
|
||||
uint256 eps =
|
||||
(buyAmount - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS /
|
||||
makerTokenAmounts[i];
|
||||
if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We do our best to close in on the requested amount, but we can either over buy or under buy and exit
|
||||
// if we hit a max iteration limit
|
||||
// We scale the sell amount to get the approximate target
|
||||
takerTokenAmounts[i] = _safeGetPartialAmountCeil(
|
||||
makerTokenAmounts[i],
|
||||
buyAmount,
|
||||
sellAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function _safeGetPartialAmountCeil(
|
||||
uint256 numerator,
|
||||
uint256 denominator,
|
||||
uint256 target
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256 partialAmount)
|
||||
{
|
||||
if (numerator == 0 || target == 0 || denominator == 0) return 0;
|
||||
uint256 c = numerator * target;
|
||||
if (c / numerator != target) return 0;
|
||||
return (c + (denominator - 1)) / denominator;
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,26 +20,29 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IBalancer.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinBalancer.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
contract BalancerSampler is
|
||||
MixinBalancer,
|
||||
SwapRevertSampler
|
||||
{
|
||||
|
||||
contract BalancerSampler {
|
||||
|
||||
/// @dev Base gas limit for Balancer calls.
|
||||
uint256 constant private BALANCER_CALL_GAS = 300e3; // 300k
|
||||
|
||||
// Balancer math constants
|
||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol
|
||||
uint256 constant private BONE = 10 ** 18;
|
||||
uint256 constant private MAX_IN_RATIO = BONE / 2;
|
||||
uint256 constant private MAX_OUT_RATIO = (BONE / 3) + 1 wei;
|
||||
|
||||
struct BalancerState {
|
||||
uint256 takerTokenBalance;
|
||||
uint256 makerTokenBalance;
|
||||
uint256 takerTokenWeight;
|
||||
uint256 makerTokenWeight;
|
||||
uint256 swapFee;
|
||||
function sampleSwapFromBalancer(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeBalancer(
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Balancer.
|
||||
@@ -47,6 +50,7 @@ contract BalancerSampler {
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromBalancer(
|
||||
@@ -56,52 +60,17 @@ contract BalancerSampler {
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
IBalancer pool = IBalancer(poolAddress);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
BalancerState memory poolState;
|
||||
poolState.takerTokenBalance = pool.getBalance(takerToken);
|
||||
poolState.makerTokenBalance = pool.getBalance(makerToken);
|
||||
poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken);
|
||||
poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken);
|
||||
poolState.swapFee = pool.getSwapFee();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
// Handles this revert scenario:
|
||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443
|
||||
if (takerTokenAmounts[i] > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) {
|
||||
break;
|
||||
}
|
||||
try
|
||||
pool.calcOutGivenIn
|
||||
{gas: BALANCER_CALL_GAS}
|
||||
(
|
||||
poolState.takerTokenBalance,
|
||||
poolState.takerTokenWeight,
|
||||
poolState.makerTokenBalance,
|
||||
poolState.makerTokenWeight,
|
||||
takerTokenAmounts[i],
|
||||
poolState.swapFee
|
||||
)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(poolAddress),
|
||||
getSwapQuoteCallback: this.sampleSwapFromBalancer
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Balancer.
|
||||
@@ -109,6 +78,7 @@ contract BalancerSampler {
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromBalancer(
|
||||
@@ -118,74 +88,18 @@ contract BalancerSampler {
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
IBalancer pool = IBalancer(poolAddress);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(poolAddress),
|
||||
buyTokenData: abi.encode(poolAddress),
|
||||
getSwapQuoteCallback: this.sampleSwapFromBalancer
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
|
||||
BalancerState memory poolState;
|
||||
poolState.takerTokenBalance = pool.getBalance(takerToken);
|
||||
poolState.makerTokenBalance = pool.getBalance(makerToken);
|
||||
poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken);
|
||||
poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken);
|
||||
poolState.swapFee = pool.getSwapFee();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
// Handles this revert scenario:
|
||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L505
|
||||
if (makerTokenAmounts[i] > _bmul(poolState.makerTokenBalance, MAX_OUT_RATIO)) {
|
||||
break;
|
||||
}
|
||||
try
|
||||
pool.calcInGivenOut
|
||||
{gas: BALANCER_CALL_GAS}
|
||||
(
|
||||
poolState.takerTokenBalance,
|
||||
poolState.takerTokenWeight,
|
||||
poolState.makerTokenBalance,
|
||||
poolState.makerTokenWeight,
|
||||
makerTokenAmounts[i],
|
||||
poolState.swapFee
|
||||
)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
takerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Hacked version of Balancer's `bmul` function, returning 0 instead
|
||||
/// of reverting.
|
||||
/// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol#L63-L73
|
||||
/// @param a The first operand.
|
||||
/// @param b The second operand.
|
||||
/// @param c The result of the multiplication, or 0 if `bmul` would've reverted.
|
||||
function _bmul(uint256 a, uint256 b)
|
||||
private
|
||||
pure
|
||||
returns (uint256 c)
|
||||
{
|
||||
uint c0 = a * b;
|
||||
if (a != 0 && c0 / a != b) {
|
||||
return 0;
|
||||
}
|
||||
uint c1 = c0 + (BONE / 2);
|
||||
if (c1 < c0) {
|
||||
return 0;
|
||||
}
|
||||
uint c2 = c1 / BONE;
|
||||
return c2;
|
||||
}
|
||||
}
|
||||
|
@@ -20,44 +20,29 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinBalancerV2.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
/// @dev Minimal Balancer V2 Vault interface
|
||||
/// for documentation refer to https://github.com/balancer-labs/balancer-core-v2/blob/master/contracts/vault/interfaces/IVault.sol
|
||||
interface IBalancerV2Vault {
|
||||
enum SwapKind { GIVEN_IN, GIVEN_OUT }
|
||||
contract BalancerV2Sampler is
|
||||
MixinBalancerV2,
|
||||
SwapRevertSampler
|
||||
{
|
||||
|
||||
struct BatchSwapStep {
|
||||
bytes32 poolId;
|
||||
uint256 assetInIndex;
|
||||
uint256 assetOutIndex;
|
||||
uint256 amount;
|
||||
bytes userData;
|
||||
}
|
||||
|
||||
struct FundManagement {
|
||||
address sender;
|
||||
bool fromInternalBalance;
|
||||
address payable recipient;
|
||||
bool toInternalBalance;
|
||||
}
|
||||
|
||||
function queryBatchSwap(
|
||||
SwapKind kind,
|
||||
BatchSwapStep[] calldata swaps,
|
||||
IAsset[] calldata assets,
|
||||
FundManagement calldata funds
|
||||
) external returns (int256[] memory assetDeltas);
|
||||
}
|
||||
interface IAsset {
|
||||
// solhint-disable-previous-line no-empty-blocks
|
||||
}
|
||||
|
||||
contract BalancerV2Sampler is SamplerUtils {
|
||||
|
||||
struct BalancerV2PoolInfo {
|
||||
bytes32 poolId;
|
||||
address vault;
|
||||
function sampleSwapFromBalancerV2(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeBalancerV2(
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Balancer V2.
|
||||
@@ -65,48 +50,27 @@ contract BalancerV2Sampler is SamplerUtils {
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromBalancerV2(
|
||||
BalancerV2PoolInfo memory poolInfo,
|
||||
BalancerV2BridgeData memory poolInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault);
|
||||
IAsset[] memory swapAssets = new IAsset[](2);
|
||||
swapAssets[0] = IAsset(takerToken);
|
||||
swapAssets[1] = IAsset(makerToken);
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
IBalancerV2Vault.FundManagement memory swapFunds =
|
||||
_createSwapFunds();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
||||
_createSwapSteps(poolInfo, takerTokenAmounts[i]);
|
||||
|
||||
try
|
||||
// For sells we specify the takerToken which is what the vault will receive from the trade
|
||||
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_IN, swapSteps, swapAssets, swapFunds)
|
||||
// amounts represent pool balance deltas from the swap (incoming balance, outgoing balance)
|
||||
returns (int256[] memory amounts) {
|
||||
// Outgoing balance is negative so we need to flip the sign
|
||||
int256 amountOutFromPool = amounts[1] * -1;
|
||||
if (amountOutFromPool <= 0) {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = uint256(amountOutFromPool);
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(poolInfo),
|
||||
getSwapQuoteCallback: this.sampleSwapFromBalancerV2
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Balancer V2.
|
||||
@@ -114,76 +78,27 @@ contract BalancerV2Sampler is SamplerUtils {
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromBalancerV2(
|
||||
BalancerV2PoolInfo memory poolInfo,
|
||||
BalancerV2BridgeData memory poolInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault);
|
||||
IAsset[] memory swapAssets = new IAsset[](2);
|
||||
swapAssets[0] = IAsset(takerToken);
|
||||
swapAssets[1] = IAsset(makerToken);
|
||||
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
IBalancerV2Vault.FundManagement memory swapFunds =
|
||||
_createSwapFunds();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
||||
_createSwapSteps(poolInfo, makerTokenAmounts[i]);
|
||||
|
||||
try
|
||||
// For buys we specify the makerToken which is what taker will receive from the trade
|
||||
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_OUT, swapSteps, swapAssets, swapFunds)
|
||||
returns (int256[] memory amounts) {
|
||||
int256 amountIntoPool = amounts[0];
|
||||
if (amountIntoPool <= 0) {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = uint256(amountIntoPool);
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _createSwapSteps(
|
||||
BalancerV2PoolInfo memory poolInfo,
|
||||
uint256 amount
|
||||
) private pure returns (IBalancerV2Vault.BatchSwapStep[] memory) {
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
||||
new IBalancerV2Vault.BatchSwapStep[](1);
|
||||
swapSteps[0] = IBalancerV2Vault.BatchSwapStep({
|
||||
poolId: poolInfo.poolId,
|
||||
assetInIndex: 0,
|
||||
assetOutIndex: 1,
|
||||
amount: amount,
|
||||
userData: ""
|
||||
});
|
||||
|
||||
return swapSteps;
|
||||
}
|
||||
|
||||
function _createSwapFunds()
|
||||
private
|
||||
view
|
||||
returns (IBalancerV2Vault.FundManagement memory)
|
||||
{
|
||||
return
|
||||
IBalancerV2Vault.FundManagement({
|
||||
sender: address(this),
|
||||
fromInternalBalance: false,
|
||||
recipient: payable(address(this)),
|
||||
toInternalBalance: false
|
||||
});
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(poolInfo),
|
||||
buyTokenData: abi.encode(poolInfo),
|
||||
getSwapQuoteCallback: this.sampleSwapFromBalancerV2
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,11 +20,21 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IBancor.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinBancor.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
contract CompilerHack {}
|
||||
|
||||
contract BancorSampler is CompilerHack {
|
||||
contract BancorSampler is
|
||||
CompilerHack,
|
||||
MixinBancor,
|
||||
SwapRevertSampler
|
||||
{
|
||||
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
MixinBancor(weth)
|
||||
{ }
|
||||
|
||||
/// @dev Base gas limit for Bancor calls.
|
||||
uint256 constant private BANCOR_CALL_GAS = 300e3; // 300k
|
||||
@@ -34,6 +44,23 @@ contract BancorSampler is CompilerHack {
|
||||
address[][] paths;
|
||||
}
|
||||
|
||||
function sampleSwapFromBancor(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeBancorInternal(
|
||||
_getNativeWrappedToken(),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Bancor.
|
||||
/// @param opts BancorSamplerOpts The Bancor registry contract address and paths
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
@@ -41,6 +68,7 @@ contract BancorSampler is CompilerHack {
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return bancorNetwork the Bancor Network address
|
||||
/// @return path the selected conversion path from bancor
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromBancor(
|
||||
@@ -50,34 +78,30 @@ contract BancorSampler is CompilerHack {
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (address bancorNetwork, address[] memory path, uint256[] memory makerTokenAmounts)
|
||||
returns (
|
||||
address bancorNetwork,
|
||||
address[] memory path,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
{
|
||||
if (opts.paths.length == 0) {
|
||||
return (bancorNetwork, path, makerTokenAmounts);
|
||||
return (bancorNetwork, path, gasUsed, makerTokenAmounts);
|
||||
}
|
||||
(bancorNetwork, path) = _findBestPath(opts, takerToken, makerToken, takerTokenAmounts);
|
||||
makerTokenAmounts = new uint256[](takerTokenAmounts.length);
|
||||
|
||||
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
|
||||
try
|
||||
IBancorNetwork(bancorNetwork)
|
||||
.rateByPath
|
||||
{gas: BANCOR_CALL_GAS}
|
||||
(path, takerTokenAmounts[i])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (bancorNetwork, path, makerTokenAmounts);
|
||||
(bancorNetwork, path) = _findBestPath(opts, takerToken, makerToken, takerTokenAmounts);
|
||||
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(bancorNetwork, path),
|
||||
getSwapQuoteCallback: this.sampleSwapFromBancor
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
|
||||
return (bancorNetwork, path, gasUsed, makerTokenAmounts);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Bancor. Unimplemented
|
||||
@@ -87,6 +111,7 @@ contract BancorSampler is CompilerHack {
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return bancorNetwork the Bancor Network address
|
||||
/// @return path the selected conversion path from bancor
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromBancor(
|
||||
@@ -97,7 +122,7 @@ contract BancorSampler is CompilerHack {
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (address bancorNetwork, address[] memory path, uint256[] memory takerTokenAmounts)
|
||||
returns (address bancorNetwork, address[] memory path, uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
}
|
||||
|
||||
|
104
packages/asset-swapper/contracts/src/BoosterSampler.sol
Normal file
104
packages/asset-swapper/contracts/src/BoosterSampler.sol
Normal file
@@ -0,0 +1,104 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinBooster.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
contract BoosterSampler is
|
||||
MixinBooster,
|
||||
SwapRevertSampler
|
||||
{
|
||||
|
||||
function sampleSwapFromBooster(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeBooster(
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Booster.
|
||||
/// @param pool Address of the Booster Pool.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromBooster(
|
||||
address pool,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(pool),
|
||||
getSwapQuoteCallback: this.sampleSwapFromBooster
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Booster.
|
||||
/// @param pool Address of the Booster pool.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromBooster(
|
||||
address pool,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(pool),
|
||||
buyTokenData: abi.encode(pool),
|
||||
getSwapQuoteCallback: this.sampleSwapFromBooster
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,142 +20,99 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/ICurve.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinCurve.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
|
||||
contract CurveSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
MixinCurve,
|
||||
SwapRevertSampler
|
||||
{
|
||||
/// @dev Information for sampling from curve sources.
|
||||
struct CurveInfo {
|
||||
address poolAddress;
|
||||
bytes4 sellQuoteFunctionSelector;
|
||||
bytes4 buyQuoteFunctionSelector;
|
||||
}
|
||||
|
||||
/// @dev Base gas limit for Curve calls. Some Curves have multiple tokens
|
||||
/// So a reasonable ceil is 150k per token. Biggest Curve has 4 tokens.
|
||||
uint256 constant private CURVE_CALL_GAS = 2000e3; // Was 600k for Curve but SnowSwap is using 1500k+
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
MixinCurve(weth)
|
||||
{ }
|
||||
|
||||
function sampleSwapFromCurve(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeCurveInternal(
|
||||
_getNativeWrappedToken(),
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Curve.
|
||||
/// @param curveInfo Curve information specific to this token pair.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
||||
/// @param takerToken The taker token to sell.
|
||||
/// @param makerToken The maker token to buy.
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromCurve(
|
||||
CurveInfo memory curveInfo,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
CurveBridgeData memory curveInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
curveInfo.sellQuoteFunctionSelector,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(curveInfo),
|
||||
getSwapQuoteCallback: this.sampleSwapFromCurve
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Curve.
|
||||
/// @param curveInfo Curve information specific to this token pair.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
||||
/// @param takerToken The taker token to sell.
|
||||
/// @param makerToken The maker token to buy.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromCurve(
|
||||
CurveInfo memory curveInfo,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
CurveBridgeData memory curveInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
if (curveInfo.buyQuoteFunctionSelector == bytes4(0)) {
|
||||
// Buys not supported on this curve, so approximate it.
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(toTokenIdx, curveInfo),
|
||||
takerTokenData: abi.encode(fromTokenIdx, curveInfo),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromCurve
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
curveInfo.buyQuoteFunctionSelector,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
makerTokenAmounts[i]
|
||||
));
|
||||
uint256 sellAmount = 0;
|
||||
if (didSucceed) {
|
||||
sellAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromCurve(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(int128 takerTokenIdx, CurveInfo memory curveInfo) =
|
||||
abi.decode(takerTokenData, (int128, CurveInfo));
|
||||
(int128 makerTokenIdx) =
|
||||
abi.decode(makerTokenData, (int128));
|
||||
(bool success, bytes memory resultData) =
|
||||
address(this).staticcall(abi.encodeWithSelector(
|
||||
this.sampleSellsFromCurve.selector,
|
||||
curveInfo,
|
||||
takerTokenIdx,
|
||||
makerTokenIdx,
|
||||
_toSingleValueArray(sellAmount)
|
||||
));
|
||||
if (!success) {
|
||||
return 0;
|
||||
}
|
||||
// solhint-disable-next-line indent
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(curveInfo),
|
||||
buyTokenData: abi.encode(
|
||||
CurveBridgeData({
|
||||
curveAddress: curveInfo.curveAddress,
|
||||
exchangeFunctionSelector: curveInfo.exchangeFunctionSelector,
|
||||
fromCoinIdx: curveInfo.toCoinIdx,
|
||||
toCoinIdx: curveInfo.fromCoinIdx
|
||||
})
|
||||
),
|
||||
getSwapQuoteCallback: this.sampleSwapFromCurve
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
112
packages/asset-swapper/contracts/src/CurveV2Sampler.sol
Normal file
112
packages/asset-swapper/contracts/src/CurveV2Sampler.sol
Normal file
@@ -0,0 +1,112 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinCurveV2.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
|
||||
contract CurveV2Sampler is
|
||||
MixinCurveV2,
|
||||
SwapRevertSampler
|
||||
{
|
||||
|
||||
function sampleSwapFromCurveV2(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeCurveV2(
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Curve.
|
||||
/// @param curveInfo Curve information specific to this token pair.
|
||||
/// @param takerToken The taker token to sell.
|
||||
/// @param makerToken The maker token to buy.
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromCurveV2(
|
||||
CurveBridgeDataV2 memory curveInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(curveInfo),
|
||||
getSwapQuoteCallback: this.sampleSwapFromCurveV2
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Curve.
|
||||
/// @param curveInfo Curve information specific to this token pair.
|
||||
/// @param takerToken The taker token to sell.
|
||||
/// @param makerToken The maker token to buy.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromCurveV2(
|
||||
CurveBridgeDataV2 memory curveInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(curveInfo),
|
||||
buyTokenData: abi.encode(
|
||||
CurveBridgeDataV2({
|
||||
curveAddress: curveInfo.curveAddress,
|
||||
exchangeFunctionSelector: curveInfo.exchangeFunctionSelector,
|
||||
fromCoinIdx: curveInfo.toCoinIdx,
|
||||
toCoinIdx: curveInfo.fromCoinIdx
|
||||
})
|
||||
),
|
||||
getSwapQuoteCallback: this.sampleSwapFromCurveV2
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
}
|
@@ -20,35 +20,40 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinDodo.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
|
||||
interface IDODOZoo {
|
||||
function getDODO(address baseToken, address quoteToken) external view returns (address);
|
||||
}
|
||||
|
||||
interface IDODOHelper {
|
||||
function querySellQuoteToken(address dodo, uint256 amount) external view returns (uint256);
|
||||
}
|
||||
|
||||
interface IDODO {
|
||||
function querySellBaseToken(uint256 amount) external view returns (uint256);
|
||||
function _TRADE_ALLOWED_() external view returns (bool);
|
||||
}
|
||||
|
||||
contract DODOSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
MixinDodo,
|
||||
SwapRevertSampler
|
||||
{
|
||||
|
||||
/// @dev Gas limit for DODO calls.
|
||||
uint256 constant private DODO_CALL_GAS = 300e3; // 300k
|
||||
struct DODOSamplerOpts {
|
||||
address registry;
|
||||
address helper;
|
||||
}
|
||||
|
||||
function sampleSwapFromDodo(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeDodo(
|
||||
IERC20TokenV06(sellToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from DODO.
|
||||
/// @param opts DODOSamplerOpts DODO Registry and helper addresses
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
@@ -56,6 +61,7 @@ contract DODOSampler is
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return sellBase whether the bridge needs to sell the base token
|
||||
/// @return pool the DODO pool address
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromDODO(
|
||||
@@ -65,13 +71,13 @@ contract DODOSampler is
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts)
|
||||
returns (
|
||||
bool sellBase,
|
||||
address pool,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
pool = IDODOZoo(opts.registry).getDODO(takerToken, makerToken);
|
||||
address baseToken;
|
||||
// If pool exists we have the correct order of Base/Quote
|
||||
@@ -82,29 +88,21 @@ contract DODOSampler is
|
||||
pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken);
|
||||
// No pool either direction
|
||||
if (address(pool) == address(0)) {
|
||||
return (sellBase, pool, makerTokenAmounts);
|
||||
return (sellBase, pool, gasUsed, makerTokenAmounts);
|
||||
}
|
||||
baseToken = makerToken;
|
||||
sellBase = false;
|
||||
}
|
||||
|
||||
// DODO Pool has been disabled
|
||||
if (!IDODO(pool)._TRADE_ALLOWED_()) {
|
||||
return (sellBase, pool, makerTokenAmounts);
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 buyAmount = _sampleSellForApproximateBuyFromDODO(
|
||||
abi.encode(takerToken, pool, baseToken, opts.helper), // taker token data
|
||||
abi.encode(makerToken, pool, baseToken, opts.helper), // maker token data
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(opts.helper, pool, sellBase),
|
||||
getSwapQuoteCallback: this.sampleSwapFromDodo
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from DODO.
|
||||
@@ -114,6 +112,7 @@ contract DODOSampler is
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return sellBase whether the bridge needs to sell the base token
|
||||
/// @return pool the DODO pool address
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromDODO(
|
||||
@@ -123,13 +122,13 @@ contract DODOSampler is
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts)
|
||||
returns (
|
||||
bool sellBase,
|
||||
address pool,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
// Pool is BASE/QUOTE
|
||||
// Look up the pool from the taker/maker combination
|
||||
pool = IDODOZoo(opts.registry).getDODO(takerToken, makerToken);
|
||||
@@ -143,69 +142,21 @@ contract DODOSampler is
|
||||
pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken);
|
||||
// No pool either direction
|
||||
if (address(pool) == address(0)) {
|
||||
return (sellBase, pool, takerTokenAmounts);
|
||||
return (sellBase, pool, gasUsed, takerTokenAmounts);
|
||||
}
|
||||
baseToken = makerToken;
|
||||
sellBase = false;
|
||||
}
|
||||
|
||||
// DODO Pool has been disabled
|
||||
if (!IDODO(pool)._TRADE_ALLOWED_()) {
|
||||
return (sellBase, pool, takerTokenAmounts);
|
||||
}
|
||||
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, pool, baseToken, opts.helper),
|
||||
takerTokenData: abi.encode(takerToken, pool, baseToken, opts.helper),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromDODO
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(opts.helper, pool, sellBase),
|
||||
buyTokenData: abi.encode(opts.helper, pool, !sellBase),
|
||||
getSwapQuoteCallback: this.sampleSwapFromDodo
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromDODO(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory /* makerTokenData */,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
(address takerToken, address pool, address baseToken, address helper) = abi.decode(
|
||||
takerTokenData,
|
||||
(address, address, address, address)
|
||||
);
|
||||
|
||||
// We will get called to sell both the taker token and also to sell the maker token
|
||||
if (takerToken == baseToken) {
|
||||
// If base token then use the original query on the pool
|
||||
try
|
||||
IDODO(pool).querySellBaseToken
|
||||
{gas: DODO_CALL_GAS}
|
||||
(sellAmount)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
return amount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// If quote token then use helper, this is less accurate
|
||||
try
|
||||
IDODOHelper(helper).querySellQuoteToken
|
||||
{gas: DODO_CALL_GAS}
|
||||
(pool, sellAmount)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
return amount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,8 +20,8 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinDodoV2.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
interface IDODOV2Registry {
|
||||
function getDODOPool(address baseToken, address quoteToken)
|
||||
@@ -30,26 +30,30 @@ interface IDODOV2Registry {
|
||||
returns (address[] memory machines);
|
||||
}
|
||||
|
||||
interface IDODOV2Pool {
|
||||
function querySellBase(address trader, uint256 payBaseAmount)
|
||||
external
|
||||
view
|
||||
returns (uint256 receiveQuoteAmount, uint256 mtFee);
|
||||
|
||||
function querySellQuote(address trader, uint256 payQuoteAmount)
|
||||
external
|
||||
view
|
||||
returns (uint256 receiveBaseAmount, uint256 mtFee);
|
||||
}
|
||||
|
||||
contract DODOV2Sampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
MixinDodoV2,
|
||||
SwapRevertSampler
|
||||
{
|
||||
|
||||
/// @dev Gas limit for DODO V2 calls.
|
||||
uint256 constant private DODO_V2_CALL_GAS = 300e3; // 300k
|
||||
|
||||
function sampleSwapFromDodoV2(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeDodoV2(
|
||||
IERC20TokenV06(sellToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from DODO V2.
|
||||
/// @param registry Address of the registry to look up.
|
||||
/// @param offset offset index for the pool in the registry.
|
||||
@@ -58,6 +62,7 @@ contract DODOV2Sampler is
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return sellBase whether the bridge needs to sell the base token
|
||||
/// @return pool the DODO pool address
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromDODOV2(
|
||||
@@ -68,31 +73,27 @@ contract DODOV2Sampler is
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts)
|
||||
returns (
|
||||
bool sellBase,
|
||||
address pool,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
|
||||
if (pool == address(0)) {
|
||||
return (sellBase, pool, makerTokenAmounts);
|
||||
return (sellBase, pool, gasUsed, makerTokenAmounts);
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 buyAmount = _sampleSellForApproximateBuyFromDODOV2(
|
||||
abi.encode(takerToken, pool, sellBase), // taker token data
|
||||
abi.encode(makerToken, pool, sellBase), // maker token data
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(pool, sellBase),
|
||||
getSwapQuoteCallback: this.sampleSwapFromDodoV2
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from DODO.
|
||||
@@ -103,6 +104,7 @@ contract DODOV2Sampler is
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return sellBase whether the bridge needs to sell the base token
|
||||
/// @return pool the DODO pool address
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromDODOV2(
|
||||
@@ -113,68 +115,30 @@ contract DODOV2Sampler is
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts)
|
||||
returns (
|
||||
bool sellBase,
|
||||
address pool,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
|
||||
if (pool == address(0)) {
|
||||
return (sellBase, pool, takerTokenAmounts);
|
||||
return (sellBase, pool, gasUsed, takerTokenAmounts);
|
||||
}
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, pool, !sellBase),
|
||||
takerTokenData: abi.encode(takerToken, pool, sellBase),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromDODOV2
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(pool, sellBase),
|
||||
buyTokenData: abi.encode(pool, !sellBase),
|
||||
getSwapQuoteCallback: this.sampleSwapFromDodoV2
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromDODOV2(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory /* makerTokenData */,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
(address takerToken, address pool, bool sellBase) = abi.decode(
|
||||
takerTokenData,
|
||||
(address, address, bool)
|
||||
);
|
||||
|
||||
// We will get called to sell both the taker token and also to sell the maker token
|
||||
// since we use approximate buy for sell and buy functions
|
||||
if (sellBase) {
|
||||
try
|
||||
IDODOV2Pool(pool).querySellBase
|
||||
{ gas: DODO_V2_CALL_GAS }
|
||||
(address(0), sellAmount)
|
||||
returns (uint256 amount, uint256)
|
||||
{
|
||||
return amount;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
try
|
||||
IDODOV2Pool(pool).querySellQuote
|
||||
{ gas: DODO_V2_CALL_GAS }
|
||||
(address(0), sellAmount)
|
||||
returns (uint256 amount, uint256)
|
||||
{
|
||||
return amount;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _getNextDODOV2Pool(
|
||||
address registry,
|
||||
uint256 offset,
|
||||
|
26
packages/asset-swapper/contracts/src/DelegateHackedERC20.sol
Normal file
26
packages/asset-swapper/contracts/src/DelegateHackedERC20.sol
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity ^0.6;
|
||||
|
||||
contract DelegateHackedERC20 {
|
||||
|
||||
address private constant HACKED = 0xDEf1000000000000000000000000000000DE7d37;
|
||||
|
||||
receive() external payable {}
|
||||
|
||||
fallback() payable external {
|
||||
(bool success, bytes memory resultData) =
|
||||
HACKED.delegatecall(msg.data);
|
||||
if (!success) {
|
||||
assembly { revert(add(resultData, 32), mload(resultData)) }
|
||||
}
|
||||
assembly { return(add(resultData, 32), mload(resultData)) }
|
||||
}
|
||||
|
||||
/// @dev Hack to get around schema validation
|
||||
function _garbage()
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,9 @@ pragma experimental ABIEncoderV2;
|
||||
import "./BalancerSampler.sol";
|
||||
import "./BalancerV2Sampler.sol";
|
||||
import "./BancorSampler.sol";
|
||||
import "./BoosterSampler.sol";
|
||||
import "./CurveSampler.sol";
|
||||
import "./CurveV2Sampler.sol";
|
||||
import "./DODOSampler.sol";
|
||||
import "./DODOV2Sampler.sol";
|
||||
import "./Eth2DaiSampler.sol";
|
||||
@@ -32,24 +34,25 @@ import "./KyberDmmSampler.sol";
|
||||
import "./LidoSampler.sol";
|
||||
import "./LiquidityProviderSampler.sol";
|
||||
import "./MakerPSMSampler.sol";
|
||||
import "./MultiBridgeSampler.sol";
|
||||
import "./MStableSampler.sol";
|
||||
import "./MooniswapSampler.sol";
|
||||
import "./NativeOrderSampler.sol";
|
||||
import "./ShellSampler.sol";
|
||||
import "./SmoothySampler.sol";
|
||||
import "./TwoHopSampler.sol";
|
||||
import "./UniswapSampler.sol";
|
||||
import "./UniswapV2Sampler.sol";
|
||||
import "./UniswapV3Sampler.sol";
|
||||
import "./UtilitySampler.sol";
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
|
||||
contract ERC20BridgeSampler is
|
||||
BalancerSampler,
|
||||
BalancerV2Sampler,
|
||||
BancorSampler,
|
||||
BoosterSampler,
|
||||
CurveSampler,
|
||||
CurveV2Sampler,
|
||||
DODOSampler,
|
||||
DODOV2Sampler,
|
||||
Eth2DaiSampler,
|
||||
@@ -60,10 +63,8 @@ contract ERC20BridgeSampler is
|
||||
MakerPSMSampler,
|
||||
MStableSampler,
|
||||
MooniswapSampler,
|
||||
MultiBridgeSampler,
|
||||
NativeOrderSampler,
|
||||
ShellSampler,
|
||||
SmoothySampler,
|
||||
TwoHopSampler,
|
||||
UniswapSampler,
|
||||
UniswapV2Sampler,
|
||||
@@ -76,11 +77,22 @@ contract ERC20BridgeSampler is
|
||||
bool success;
|
||||
}
|
||||
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
BancorSampler(weth)
|
||||
CurveSampler(weth)
|
||||
KyberSampler(weth)
|
||||
MooniswapSampler(weth)
|
||||
LidoSampler(weth)
|
||||
UniswapSampler(weth)
|
||||
{ }
|
||||
|
||||
/// @dev Call multiple public functions on this contract in a single transaction.
|
||||
/// @param callDatas ABI-encoded call data for each function call.
|
||||
/// @return callResults ABI-encoded results data for each call.
|
||||
function batchCall(bytes[] calldata callDatas)
|
||||
external
|
||||
payable
|
||||
returns (CallResults[] memory callResults)
|
||||
{
|
||||
callResults = new CallResults[](callDatas.length);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,21 +20,38 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IEth2Dai.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinOasis.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
|
||||
contract Eth2DaiSampler is
|
||||
SamplerUtils
|
||||
MixinOasis,
|
||||
SwapRevertSampler
|
||||
{
|
||||
/// @dev Base gas limit for Eth2Dai calls.
|
||||
uint256 constant private ETH2DAI_CALL_GAS = 1000e3; // 1m
|
||||
|
||||
function sampleSwapFromOasis(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeOasis(
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Eth2Dai/Oasis.
|
||||
/// @param router Address of the Eth2Dai/Oasis contract
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromEth2Dai(
|
||||
@@ -44,29 +61,17 @@ contract Eth2DaiSampler is
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IEth2Dai(router).getBuyAmount
|
||||
{gas: ETH2DAI_CALL_GAS}
|
||||
(makerToken, takerToken, takerTokenAmounts[i])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(router),
|
||||
getSwapQuoteCallback: this.sampleSwapFromOasis
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Eth2Dai/Oasis.
|
||||
@@ -74,6 +79,7 @@ contract Eth2DaiSampler is
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromEth2Dai(
|
||||
@@ -83,28 +89,17 @@ contract Eth2DaiSampler is
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IEth2Dai(router).getPayAmount
|
||||
{gas: ETH2DAI_CALL_GAS}
|
||||
(takerToken, makerToken, makerTokenAmounts[i])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
takerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(router),
|
||||
buyTokenData: abi.encode(router),
|
||||
getSwapQuoteCallback: this.sampleSwapFromOasis
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
30
packages/asset-swapper/contracts/src/GasOverhead.sol
Normal file
30
packages/asset-swapper/contracts/src/GasOverhead.sol
Normal file
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity ^0.6;
|
||||
|
||||
contract GasOverhead {
|
||||
uint256 private _overhead = 2;
|
||||
// Overhead incurred from updating the overhead storage slot
|
||||
uint256 constant SSTORE_OVERHEAD = 20000;
|
||||
|
||||
function addOverhead(uint256 gas, uint256 gasBefore)
|
||||
external
|
||||
{
|
||||
uint256 callOverhead = gasBefore - gasleft();
|
||||
// Add additional est overhead of performing this update
|
||||
_overhead += gas + callOverhead + SSTORE_OVERHEAD;
|
||||
}
|
||||
|
||||
function clearOverhead()
|
||||
external
|
||||
{
|
||||
_overhead = 2;
|
||||
}
|
||||
|
||||
function overhead()
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return _overhead;
|
||||
}
|
||||
}
|
323
packages/asset-swapper/contracts/src/HackedERC20.sol
Normal file
323
packages/asset-swapper/contracts/src/HackedERC20.sol
Normal file
@@ -0,0 +1,323 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity ^0.6;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "./GasOverhead.sol";
|
||||
|
||||
contract HackedERC20 {
|
||||
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
|
||||
struct ShadowedAmount {
|
||||
bool isShadowed;
|
||||
uint256 lastTrueAmount;
|
||||
uint256 shadowedAmount;
|
||||
}
|
||||
|
||||
struct Storage {
|
||||
mapping(address=>ShadowedAmount) shadowedBalances;
|
||||
mapping(address=>mapping(address=>ShadowedAmount)) shadowedAllowances;
|
||||
// When enabled the HackedERC20 token will shadow and track balances
|
||||
// when disabled (default) it will call the original implementation
|
||||
bool enabled;
|
||||
}
|
||||
|
||||
bytes32 private constant STORAGE_SLOT = 0x64fd48372774b9637ace5c8c7a951f04ea13c793935207f2eada5382a0ec82cb;
|
||||
GasOverhead private constant GAS_OVERHEAD = GasOverhead(0xDeF1000000000000000000000000000000001337);
|
||||
|
||||
// HackedERC20 also has the overhead of being Delegated to from the replaced token
|
||||
// USDT->DelegateHackedERC20->HackedERC20
|
||||
uint256 private constant DELEGATE_CALL_OVERHEAD = 5000;
|
||||
|
||||
receive() external payable {}
|
||||
|
||||
fallback() payable external {
|
||||
bytes memory r = _forwardCallToImpl();
|
||||
assembly { return(add(r, 32), mload(r)) }
|
||||
}
|
||||
|
||||
function balanceOf(address owner)
|
||||
external
|
||||
/* view */
|
||||
returns (uint256 balance)
|
||||
{
|
||||
if (!_isEnabled()) {
|
||||
bytes memory r = _forwardCallToImpl();
|
||||
assembly { return(add(r, 32), mload(r)) }
|
||||
}
|
||||
(ShadowedAmount memory sBal,) = _getSyncedBalance(owner);
|
||||
return sBal.shadowedAmount;
|
||||
}
|
||||
|
||||
function allowance(address owner, address spender)
|
||||
external
|
||||
/* view */
|
||||
returns (uint256 allowance_)
|
||||
{
|
||||
if (!_isEnabled()) {
|
||||
bytes memory r = _forwardCallToImpl();
|
||||
assembly { return(add(r, 32), mload(r)) }
|
||||
}
|
||||
(ShadowedAmount memory sBal,) = _getSyncedAllowance(owner, spender);
|
||||
return sBal.shadowedAmount;
|
||||
}
|
||||
|
||||
function transferFrom(address from, address to, uint256 amount)
|
||||
public
|
||||
returns (bool success)
|
||||
{
|
||||
if (!_isEnabled()) {
|
||||
bytes memory r = _forwardCallToImpl();
|
||||
assembly { return(add(r, 32), mload(r)) }
|
||||
}
|
||||
_updateAllowance(from, amount);
|
||||
success = _transferFromInternal(from, to, amount);
|
||||
}
|
||||
|
||||
|
||||
function transfer(address to, uint256 amount)
|
||||
external
|
||||
returns (bool success)
|
||||
{
|
||||
if (!_isEnabled()) {
|
||||
bytes memory r = _forwardCallToImpl();
|
||||
assembly { return(add(r, 32), mload(r)) }
|
||||
}
|
||||
success = _transferFromInternal(msg.sender, to, amount);
|
||||
}
|
||||
|
||||
function approve(address spender, uint256 amount)
|
||||
external
|
||||
returns (bool)
|
||||
{
|
||||
if (!_isEnabled()) {
|
||||
bytes memory r = _forwardCallToImpl();
|
||||
assembly { return(add(r, 32), mload(r)) }
|
||||
}
|
||||
(
|
||||
ShadowedAmount memory sAllowance,
|
||||
) = _getSyncedAllowance(msg.sender, spender);
|
||||
|
||||
sAllowance.shadowedAmount = amount;
|
||||
_writeSyncedAllowance(msg.sender, spender, sAllowance);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function _setBalance(address owner, uint256 amount)
|
||||
public
|
||||
{
|
||||
(ShadowedAmount memory sBal,) = _getSyncedBalance(owner);
|
||||
sBal.shadowedAmount = amount;
|
||||
_writeSyncedBalance(owner, sBal);
|
||||
}
|
||||
|
||||
|
||||
function _getSyncedAllowance(address owner, address spender)
|
||||
private
|
||||
/* view */
|
||||
returns (ShadowedAmount memory sAllowance, uint256 gasOverhead)
|
||||
{
|
||||
uint256 trueAmount = abi.decode(
|
||||
_forwardCallToImpl(abi.encodeWithSelector(
|
||||
IERC20TokenV06.allowance.selector,
|
||||
owner,
|
||||
spender
|
||||
)),
|
||||
(uint256)
|
||||
);
|
||||
// We only want to measure the cost of the underlying token storage lookup
|
||||
// Not including the excess overhead of our shadow lookup
|
||||
uint256 gasBefore = gasleft();
|
||||
sAllowance = _getStorage().shadowedAllowances[owner][spender];
|
||||
_syncShadowedAmount(sAllowance, trueAmount);
|
||||
gasOverhead = gasBefore - gasleft();
|
||||
}
|
||||
|
||||
function _getSyncedBalance(address owner)
|
||||
private
|
||||
returns (ShadowedAmount memory sBal, uint256 gasOverhead)
|
||||
{
|
||||
uint256 trueAmount = abi.decode(
|
||||
_forwardCallToImpl(abi.encodeWithSelector(
|
||||
IERC20TokenV06.balanceOf.selector,
|
||||
owner
|
||||
)),
|
||||
(uint256)
|
||||
);
|
||||
// We only want to measure the cost of the underlying token storage lookup
|
||||
// Not including the excess overhead of our shadow lookup
|
||||
uint256 gasBefore = gasleft();
|
||||
sBal = _getStorage().shadowedBalances[owner];
|
||||
_syncShadowedAmount(sBal, trueAmount);
|
||||
gasOverhead = gasBefore - gasleft();
|
||||
}
|
||||
|
||||
function _syncShadowedAmount(ShadowedAmount memory sAmount, uint256 trueAmount)
|
||||
private
|
||||
pure
|
||||
{
|
||||
if (!sAmount.isShadowed) {
|
||||
sAmount.isShadowed = true;
|
||||
sAmount.shadowedAmount = trueAmount;
|
||||
} else {
|
||||
// Detect balance changes that can occur from outside of ERC20
|
||||
// functions.
|
||||
if (sAmount.lastTrueAmount > trueAmount) {
|
||||
sAmount.shadowedAmount = _sub(
|
||||
sAmount.lastTrueAmount,
|
||||
sAmount.lastTrueAmount - trueAmount,
|
||||
'HackedERC20/SHADOW_ADJUSTMENT_UNDERFLOW'
|
||||
);
|
||||
} else if (sAmount.lastTrueAmount < trueAmount) {
|
||||
sAmount.shadowedAmount = _add(
|
||||
sAmount.lastTrueAmount,
|
||||
trueAmount - sAmount.lastTrueAmount,
|
||||
'HackedERC20/SHADOW_ADJUSTMENT_OVERFLOW'
|
||||
);
|
||||
}
|
||||
}
|
||||
sAmount.lastTrueAmount = trueAmount;
|
||||
}
|
||||
|
||||
function _writeSyncedBalance(address owner, ShadowedAmount memory sBal)
|
||||
private
|
||||
{
|
||||
_getStorage().shadowedBalances[owner] = sBal;
|
||||
}
|
||||
|
||||
function _writeSyncedAllowance(
|
||||
address owner,
|
||||
address spender,
|
||||
ShadowedAmount memory sAllowance
|
||||
)
|
||||
private
|
||||
{
|
||||
_getStorage().shadowedAllowances[owner][spender] = sAllowance;
|
||||
}
|
||||
|
||||
function _getStorage() private pure returns (Storage storage st) {
|
||||
bytes32 slot = STORAGE_SLOT;
|
||||
assembly { st_slot := slot }
|
||||
}
|
||||
|
||||
function _getOriginalImplementationAddress()
|
||||
private
|
||||
view
|
||||
returns (address impl)
|
||||
{
|
||||
return address(uint160(address(this)) + 1);
|
||||
}
|
||||
|
||||
function _forwardCallToImpl()
|
||||
private
|
||||
returns (bytes memory resultData)
|
||||
{
|
||||
bool success;
|
||||
(success, resultData) =
|
||||
_getOriginalImplementationAddress().delegatecall(msg.data);
|
||||
if (!success) {
|
||||
assembly { revert(add(resultData, 32), mload(resultData)) }
|
||||
}
|
||||
}
|
||||
|
||||
function _forwardCallToImpl(bytes memory callData)
|
||||
private
|
||||
returns (bytes memory resultData)
|
||||
{
|
||||
bool success;
|
||||
(success, resultData) =
|
||||
_getOriginalImplementationAddress().delegatecall(callData);
|
||||
if (!success) {
|
||||
assembly { revert(add(resultData, 32), mload(resultData)) }
|
||||
}
|
||||
}
|
||||
|
||||
function _transferFromInternal(address from, address to, uint256 amount)
|
||||
internal
|
||||
returns (bool)
|
||||
{
|
||||
ShadowedAmount memory sFromBal;
|
||||
ShadowedAmount memory sToBal;
|
||||
uint256 gasOverhead;
|
||||
uint256 _gasOverhead;
|
||||
|
||||
(sFromBal, _gasOverhead) = _getSyncedBalance(from);
|
||||
gasOverhead += _gasOverhead;
|
||||
sFromBal.shadowedAmount = _sub(
|
||||
sFromBal.shadowedAmount,
|
||||
amount,
|
||||
'HackedERC20/BALANCE_UNDERFLOW'
|
||||
);
|
||||
_writeSyncedBalance(from, sFromBal);
|
||||
|
||||
(sToBal, _gasOverhead) = _getSyncedBalance(to);
|
||||
gasOverhead += _gasOverhead;
|
||||
sToBal.shadowedAmount = _add(
|
||||
sToBal.shadowedAmount,
|
||||
amount,
|
||||
'HackedERC20/BALANCE_OVERFLOW'
|
||||
);
|
||||
_writeSyncedBalance(to, sToBal);
|
||||
|
||||
// Update the global gas overhead from a transfer call
|
||||
try
|
||||
GAS_OVERHEAD.addOverhead(gasOverhead + DELEGATE_CALL_OVERHEAD, gasleft())
|
||||
{ } catch { }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function _updateAllowance(address from, uint256 amount)
|
||||
internal
|
||||
{
|
||||
(ShadowedAmount memory sAllowance, uint256 gasOverhead) = _getSyncedAllowance(from, msg.sender);
|
||||
if (from != msg.sender && sAllowance.shadowedAmount != uint256(-1)) {
|
||||
sAllowance.shadowedAmount = _sub(
|
||||
sAllowance.shadowedAmount,
|
||||
amount,
|
||||
'HackedERC20/ALLOWANCE_UNDERFLOW'
|
||||
);
|
||||
_writeSyncedAllowance(from, msg.sender, sAllowance);
|
||||
}
|
||||
uint256 gasBefore = gasleft();
|
||||
// Assume a NON MAX_UINT results in allowance update SSTORE
|
||||
_writeSyncedAllowance(from, msg.sender, sAllowance);
|
||||
gasOverhead += gasBefore - gasleft();
|
||||
// Update the global gas overhead from a allowance check
|
||||
try
|
||||
GAS_OVERHEAD.addOverhead(gasOverhead + DELEGATE_CALL_OVERHEAD, gasleft())
|
||||
{ } catch { }
|
||||
}
|
||||
|
||||
function _isEnabled()
|
||||
internal
|
||||
returns (bool)
|
||||
{
|
||||
return _getStorage().enabled;
|
||||
}
|
||||
|
||||
function _setEnabled(bool enabled)
|
||||
public
|
||||
{
|
||||
_getStorage().enabled = enabled;
|
||||
}
|
||||
|
||||
function _add(uint256 a, uint256 b, string memory errMsg)
|
||||
private
|
||||
pure
|
||||
returns (uint256 c)
|
||||
{
|
||||
c = a + b;
|
||||
require(c >= a, errMsg);
|
||||
}
|
||||
|
||||
function _sub(uint256 a, uint256 b, string memory errMsg)
|
||||
private
|
||||
pure
|
||||
returns (uint256 c)
|
||||
{
|
||||
c = a - b;
|
||||
require(c <= a, errMsg);
|
||||
}
|
||||
}
|
@@ -20,6 +20,8 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinKyberDmm.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
interface IKyberDmmFactory {
|
||||
|
||||
function getPoolAtIndex(address token0, address token1, uint256 index)
|
||||
@@ -28,33 +30,36 @@ interface IKyberDmmFactory {
|
||||
returns (address);
|
||||
}
|
||||
|
||||
interface IKyberDmmRouter {
|
||||
|
||||
function factory() external view returns (address);
|
||||
|
||||
function getAmountsOut(uint256 amountIn, address[] calldata pools, address[] calldata path)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory amounts);
|
||||
|
||||
function getAmountsIn(uint256 amountOut, address[] calldata pools, address[] calldata path)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory amounts);
|
||||
}
|
||||
|
||||
|
||||
|
||||
contract KyberDmmSampler
|
||||
contract KyberDmmSampler is
|
||||
MixinKyberDmm,
|
||||
SwapRevertSampler
|
||||
{
|
||||
/// @dev Gas limit for KyberDmm calls.
|
||||
uint256 constant private KYBER_DMM_CALL_GAS = 150e3; // 150k
|
||||
|
||||
function sampleSwapFromKyberDmm(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeKyberDmm(
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from KyberDmm.
|
||||
/// @param router Router to look up tokens and amounts
|
||||
/// @param path Token route. Should be takerToken -> makerToken
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return pools The pool addresses involved in the multi path trade
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromKyberDmm(
|
||||
@@ -63,32 +68,26 @@ contract KyberDmmSampler
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (address[] memory pools, uint256[] memory makerTokenAmounts)
|
||||
returns (
|
||||
address[] memory pools,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
pools = _getKyberDmmPools(router, path);
|
||||
if (pools.length == 0) {
|
||||
return (pools, makerTokenAmounts);
|
||||
}
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IKyberDmmRouter(router).getAmountsOut
|
||||
{gas: KYBER_DMM_CALL_GAS}
|
||||
(takerTokenAmounts[i], pools, path)
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
makerTokenAmounts[i] = amounts[path.length - 1];
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
return (pools, gasUsed, makerTokenAmounts);
|
||||
}
|
||||
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: path[0],
|
||||
buyToken: path[path.length - 1],
|
||||
bridgeData: abi.encode(router, pools, path),
|
||||
getSwapQuoteCallback: this.sampleSwapFromKyberDmm
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from KyberDmm.
|
||||
@@ -96,6 +95,7 @@ contract KyberDmmSampler
|
||||
/// @param path Token route. Should be takerToken -> makerToken.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return pools The pool addresses involved in the multi path trade
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromKyberDmm(
|
||||
@@ -104,32 +104,32 @@ contract KyberDmmSampler
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (address[] memory pools, uint256[] memory takerTokenAmounts)
|
||||
returns (
|
||||
address[] memory pools,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
{
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
pools = _getKyberDmmPools(router, path);
|
||||
if (pools.length == 0) {
|
||||
return (pools, takerTokenAmounts);
|
||||
return (pools, gasUsed, takerTokenAmounts);
|
||||
}
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IKyberDmmRouter(router).getAmountsIn
|
||||
{gas: KYBER_DMM_CALL_GAS}
|
||||
(makerTokenAmounts[i], pools, path)
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
takerTokenAmounts[i] = amounts[0];
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
|
||||
address[] memory reversedPath = new address[](path.length);
|
||||
for (uint256 i = 0; i < path.length; ++i) {
|
||||
reversedPath[i] = path[path.length - i - 1];
|
||||
}
|
||||
address[] memory reversedPools = _getKyberDmmPools(router, reversedPath);
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: path[0],
|
||||
buyToken: path[path.length - 1],
|
||||
sellTokenData: abi.encode(router, pools, path),
|
||||
buyTokenData: abi.encode(router, reversedPools, reversedPath),
|
||||
getSwapQuoteCallback: this.sampleSwapFromKyberDmm
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _getKyberDmmPools(
|
||||
|
@@ -21,18 +21,22 @@ pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IKyberNetwork.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinKyber.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
contract KyberSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
MixinKyber,
|
||||
SwapRevertSampler
|
||||
{
|
||||
|
||||
/// @dev Gas limit for Kyber calls.
|
||||
uint256 constant private KYBER_CALL_GAS = 500e3; // 500k
|
||||
/// @dev Kyber ETH pseudo-address.
|
||||
address constant internal KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
MixinKyber(weth)
|
||||
{ }
|
||||
|
||||
struct KyberSamplerOpts {
|
||||
uint256 reserveOffset;
|
||||
@@ -42,6 +46,26 @@ contract KyberSampler is
|
||||
bytes hint;
|
||||
}
|
||||
|
||||
function sampleSwapFromKyber(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeKyberInternal(
|
||||
// these are Immutable in MixinKyber, since they are only set in constructor they must be passed in
|
||||
IERC20TokenV06(KYBER_ETH_ADDRESS),
|
||||
_getNativeWrappedToken(),
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Kyber.
|
||||
/// @param opts KyberSamplerOpts The nth reserve
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
@@ -49,6 +73,7 @@ contract KyberSampler is
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return reserveId The id of the reserve found at reserveOffset
|
||||
/// @return hint The hint for the selected reserve
|
||||
/// @return gasUsed Gas consumed per sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
|
||||
function sampleSellsFromKyberNetwork(
|
||||
KyberSamplerOpts memory opts,
|
||||
@@ -57,32 +82,29 @@ contract KyberSampler is
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bytes32 reserveId, bytes memory hint, uint256[] memory makerTokenAmounts)
|
||||
returns (
|
||||
bytes32 reserveId,
|
||||
bytes memory hint,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
reserveId = _getNextReserveId(opts, takerToken, makerToken);
|
||||
if (reserveId == 0x0) {
|
||||
return (reserveId, hint, makerTokenAmounts);
|
||||
return (reserveId, hint, gasUsed, makerTokenAmounts);
|
||||
}
|
||||
opts.hint = this.encodeKyberHint(opts, reserveId, takerToken, makerToken);
|
||||
hint = opts.hint;
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 value = this.sampleSellFromKyberNetwork(
|
||||
opts,
|
||||
takerToken,
|
||||
makerToken,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
makerTokenAmounts[i] = value;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(opts.networkProxy, hint),
|
||||
getSwapQuoteCallback: this.sampleSwapFromKyber
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Kyber.
|
||||
@@ -92,6 +114,7 @@ contract KyberSampler is
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return reserveId The id of the reserve found at reserveOffset
|
||||
/// @return hint The hint for the selected reserve
|
||||
/// @return gasUsed Gas consumed for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
|
||||
function sampleBuysFromKyberNetwork(
|
||||
KyberSamplerOpts memory opts,
|
||||
@@ -100,27 +123,30 @@ contract KyberSampler is
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bytes32 reserveId, bytes memory hint, uint256[] memory takerTokenAmounts)
|
||||
returns (
|
||||
bytes32 reserveId,
|
||||
bytes memory hint,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
|
||||
reserveId = _getNextReserveId(opts, takerToken, makerToken);
|
||||
if (reserveId == 0x0) {
|
||||
return (reserveId, hint, takerTokenAmounts);
|
||||
return (reserveId, hint, gasUsed, takerTokenAmounts);
|
||||
}
|
||||
opts.hint = this.encodeKyberHint(opts, reserveId, takerToken, makerToken);
|
||||
hint = opts.hint;
|
||||
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, opts),
|
||||
takerTokenData: abi.encode(takerToken, opts),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromKyber
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(opts.networkProxy, hint),
|
||||
buyTokenData: abi.encode(opts.networkProxy, hint),
|
||||
getSwapQuoteCallback: this.sampleSwapFromKyber
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
return (reserveId, hint, takerTokenAmounts);
|
||||
}
|
||||
|
||||
function encodeKyberHint(
|
||||
@@ -201,73 +227,6 @@ contract KyberSampler is
|
||||
}
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromKyber(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
(address makerToken, KyberSamplerOpts memory opts) =
|
||||
abi.decode(makerTokenData, (address, KyberSamplerOpts));
|
||||
(address takerToken, ) =
|
||||
abi.decode(takerTokenData, (address, KyberSamplerOpts));
|
||||
try
|
||||
this.sampleSellFromKyberNetwork
|
||||
(opts, takerToken, makerToken, sellAmount)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
return amount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function sampleSellFromKyberNetwork(
|
||||
KyberSamplerOpts memory opts,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256 makerTokenAmount)
|
||||
{
|
||||
// If there is no hint do not continue
|
||||
if (opts.hint.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
IKyberNetworkProxy(opts.networkProxy).getExpectedRateAfterFee
|
||||
{gas: KYBER_CALL_GAS}
|
||||
(
|
||||
takerToken == opts.weth ? KYBER_ETH_ADDRESS : takerToken,
|
||||
makerToken == opts.weth ? KYBER_ETH_ADDRESS : makerToken,
|
||||
takerTokenAmount,
|
||||
0, // fee
|
||||
opts.hint
|
||||
)
|
||||
returns (uint256 rate)
|
||||
{
|
||||
uint256 makerTokenDecimals = _getTokenDecimals(makerToken);
|
||||
uint256 takerTokenDecimals = _getTokenDecimals(takerToken);
|
||||
makerTokenAmount =
|
||||
rate *
|
||||
takerTokenAmount *
|
||||
10 ** makerTokenDecimals /
|
||||
10 ** takerTokenDecimals /
|
||||
10 ** 18;
|
||||
return makerTokenAmount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function _getNextReserveId(
|
||||
KyberSamplerOpts memory opts,
|
||||
address takerToken,
|
||||
|
@@ -20,72 +20,84 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinLido.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
contract LidoSampler is SamplerUtils {
|
||||
struct LidoInfo {
|
||||
address stEthToken;
|
||||
address wethToken;
|
||||
contract LidoSampler is
|
||||
MixinLido,
|
||||
SwapRevertSampler
|
||||
{
|
||||
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
MixinLido(weth)
|
||||
{ }
|
||||
|
||||
function sampleSwapFromLido(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeLidoInternal(
|
||||
_getNativeWrappedToken(),
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Lido
|
||||
/// @param lidoInfo Info regarding a specific Lido deployment
|
||||
/// @param lido Address of the Lido contract
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed for each sample amount
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromLido(
|
||||
LidoInfo memory lidoInfo,
|
||||
address lido,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (uint256[] memory)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
|
||||
if (takerToken != lidoInfo.wethToken || makerToken != address(lidoInfo.stEthToken)) {
|
||||
// Return 0 values if not selling WETH for stETH
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
uint256[] memory makerTokenAmounts = new uint256[](numSamples);
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
// Minting stETH is always 1:1 therefore we can just return the same amounts back
|
||||
return takerTokenAmounts;
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(lido),
|
||||
getSwapQuoteCallback: this.sampleSwapFromLido
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Lido.
|
||||
/// @param lidoInfo Info regarding a specific Lido deployment
|
||||
/// @param lido Address of the Lido contract
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return gasUsed gas consumed for each sample amount
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromLido(
|
||||
LidoInfo memory lidoInfo,
|
||||
address lido,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (uint256[] memory)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
|
||||
if (takerToken != lidoInfo.wethToken || makerToken != address(lidoInfo.stEthToken)) {
|
||||
// Return 0 values if not buying stETH for WETH
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
uint256[] memory takerTokenAmounts = new uint256[](numSamples);
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
// Minting stETH is always 1:1 therefore we can just return the same amounts back
|
||||
return makerTokenAmounts;
|
||||
// 1:1 rate so we can perform an WETH sell
|
||||
return this.sampleSellsFromLido(lido, takerToken, makerToken, makerTokenAmounts);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,24 +20,38 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/vendor/ILiquidityProvider.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinZeroExBridge.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
|
||||
contract LiquidityProviderSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
MixinZeroExBridge,
|
||||
SwapRevertSampler
|
||||
{
|
||||
/// @dev Default gas limit for liquidity provider calls.
|
||||
uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k
|
||||
|
||||
function sampleSwapFromLiquidityProvider(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeZeroExBridge(
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from an arbitrary on-chain liquidity provider.
|
||||
/// @param providerAddress Address of the liquidity provider.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromLiquidityProvider(
|
||||
@@ -47,34 +61,18 @@ contract LiquidityProviderSampler is
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
ILiquidityProvider(providerAddress).getSellQuote
|
||||
{gas: DEFAULT_CALL_GAS}
|
||||
(
|
||||
IERC20TokenV06(takerToken),
|
||||
IERC20TokenV06(makerToken),
|
||||
takerTokenAmounts[i]
|
||||
)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
bytes memory lpData;
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(providerAddress, lpData),
|
||||
getSwapQuoteCallback: this.sampleSwapFromLiquidityProvider
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from an arbitrary on-chain liquidity provider.
|
||||
@@ -82,6 +80,7 @@ contract LiquidityProviderSampler is
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromLiquidityProvider(
|
||||
@@ -91,42 +90,18 @@ contract LiquidityProviderSampler is
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, providerAddress),
|
||||
takerTokenData: abi.encode(takerToken, providerAddress),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromLiquidityProvider
|
||||
bytes memory lpData;
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(providerAddress, lpData),
|
||||
buyTokenData: abi.encode(providerAddress, lpData),
|
||||
getSwapQuoteCallback: this.sampleSwapFromLiquidityProvider
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromLiquidityProvider(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(address takerToken, address providerAddress) =
|
||||
abi.decode(takerTokenData, (address, address));
|
||||
(address makerToken) =
|
||||
abi.decode(makerTokenData, (address));
|
||||
try
|
||||
this.sampleSellsFromLiquidityProvider
|
||||
{gas: DEFAULT_CALL_GAS}
|
||||
(providerAddress, takerToken, makerToken, _toSingleValueArray(sellAmount))
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
return amounts[0];
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,23 +20,38 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IMStable.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinMStable.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
|
||||
contract MStableSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
MixinMStable,
|
||||
SwapRevertSampler
|
||||
{
|
||||
/// @dev Default gas limit for mStable calls.
|
||||
uint256 constant private DEFAULT_CALL_GAS = 800e3; // 800k
|
||||
|
||||
function sampleSwapFromMStable(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeMStable(
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from the mStable contract
|
||||
/// @param router Address of the mStable contract
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromMStable(
|
||||
@@ -46,31 +61,17 @@ contract MStableSampler is
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IMStable(router).getSwapOutput
|
||||
{gas: DEFAULT_CALL_GAS}
|
||||
(takerToken, makerToken, takerTokenAmounts[i])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(router),
|
||||
getSwapQuoteCallback: this.sampleSwapFromMStable
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from MStable contract
|
||||
@@ -78,6 +79,7 @@ contract MStableSampler is
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromMStable(
|
||||
@@ -87,41 +89,17 @@ contract MStableSampler is
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, router),
|
||||
takerTokenData: abi.encode(takerToken, router),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromMStable
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(router),
|
||||
buyTokenData: abi.encode(router),
|
||||
getSwapQuoteCallback: this.sampleSwapFromMStable
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromMStable(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(address takerToken, address router) =
|
||||
abi.decode(takerTokenData, (address, address));
|
||||
(address makerToken) =
|
||||
abi.decode(makerTokenData, (address));
|
||||
try
|
||||
this.sampleSellsFromMStable
|
||||
(router, takerToken, makerToken, _toSingleValueArray(sellAmount))
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
return amounts[0];
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,69 +20,13 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||
|
||||
interface IPSM {
|
||||
// @dev Get the fee for selling USDC to DAI in PSM
|
||||
// @return tin toll in [wad]
|
||||
function tin() external view returns (uint256);
|
||||
// @dev Get the fee for selling DAI to USDC in PSM
|
||||
// @return tout toll out [wad]
|
||||
function tout() external view returns (uint256);
|
||||
|
||||
// @dev Get the address of the PSM state Vat
|
||||
// @return address of the Vat
|
||||
function vat() external view returns (address);
|
||||
|
||||
// @dev Get the address of the underlying vault powering PSM
|
||||
// @return address of gemJoin contract
|
||||
function gemJoin() external view returns (address);
|
||||
|
||||
// @dev Get the address of DAI
|
||||
// @return address of DAI contract
|
||||
function dai() external view returns (address);
|
||||
|
||||
// @dev Sell USDC for DAI
|
||||
// @param usr The address of the account trading USDC for DAI.
|
||||
// @param gemAmt The amount of USDC to sell in USDC base units
|
||||
function sellGem(
|
||||
address usr,
|
||||
uint256 gemAmt
|
||||
) external;
|
||||
// @dev Buy USDC for DAI
|
||||
// @param usr The address of the account trading DAI for USDC
|
||||
// @param gemAmt The amount of USDC to buy in USDC base units
|
||||
function buyGem(
|
||||
address usr,
|
||||
uint256 gemAmt
|
||||
) external;
|
||||
}
|
||||
|
||||
interface IVAT {
|
||||
// @dev Get a collateral type by identifier
|
||||
// @param ilkIdentifier bytes32 identifier. Example: ethers.utils.formatBytes32String("PSM-USDC-A")
|
||||
// @return ilk
|
||||
// @return ilk.Art Total Normalised Debt in wad
|
||||
// @return ilk.rate Accumulated Rates in ray
|
||||
// @return ilk.spot Price with Safety Margin in ray
|
||||
// @return ilk.line Debt Ceiling in rad
|
||||
// @return ilk.dust Urn Debt Floor in rad
|
||||
function ilks(
|
||||
bytes32 ilkIdentifier
|
||||
) external view returns (
|
||||
uint256 Art,
|
||||
uint256 rate,
|
||||
uint256 spot,
|
||||
uint256 line,
|
||||
uint256 dust
|
||||
);
|
||||
}
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinMakerPSM.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
contract MakerPSMSampler is
|
||||
SamplerUtils
|
||||
MixinMakerPSM,
|
||||
SwapRevertSampler
|
||||
{
|
||||
using LibSafeMathV06 for uint256;
|
||||
|
||||
/// @dev Information about which PSM module to use
|
||||
struct MakerPsmInfo {
|
||||
@@ -91,18 +35,22 @@ contract MakerPSMSampler is
|
||||
address gemTokenAddress;
|
||||
}
|
||||
|
||||
/// @dev Gas limit for MakerPsm calls.
|
||||
uint256 constant private MAKER_PSM_CALL_GAS = 300e3; // 300k
|
||||
|
||||
|
||||
// Maker units
|
||||
// wad: fixed point decimal with 18 decimals (for basic quantities, e.g. balances)
|
||||
uint256 constant private WAD = 10 ** 18;
|
||||
// ray: fixed point decimal with 27 decimals (for precise quantites, e.g. ratios)
|
||||
uint256 constant private RAY = 10 ** 27;
|
||||
// rad: fixed point decimal with 45 decimals (result of integer multiplication with a wad and a ray)
|
||||
uint256 constant private RAD = 10 ** 45;
|
||||
// See https://github.com/makerdao/dss/blob/master/DEVELOPING.m
|
||||
function sampleSwapFromMakerPsm(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeMakerPsm(
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Maker PSM
|
||||
function sampleSellsFromMakerPsm(
|
||||
@@ -112,29 +60,22 @@ contract MakerPSMSampler is
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
IPSM psm = IPSM(psmInfo.psmAddress);
|
||||
IVAT vat = IVAT(psm.vat());
|
||||
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
if (makerToken != psm.dai() && takerToken != psm.dai()) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 buyAmount = _samplePSMSell(psmInfo, makerToken, takerToken, takerTokenAmounts[i], psm, vat);
|
||||
|
||||
if (buyAmount == 0) {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(
|
||||
MakerPsmBridgeData({
|
||||
psmAddress: psmInfo.psmAddress,
|
||||
gemTokenAddres: psmInfo.gemTokenAddress
|
||||
})
|
||||
),
|
||||
getSwapQuoteCallback: this.sampleSwapFromMakerPsm
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function sampleBuysFromMakerPsm(
|
||||
@@ -144,124 +85,21 @@ contract MakerPSMSampler is
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
IPSM psm = IPSM(psmInfo.psmAddress);
|
||||
IVAT vat = IVAT(psm.vat());
|
||||
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
if (makerToken != psm.dai() && takerToken != psm.dai()) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 sellAmount = _samplePSMBuy(psmInfo, makerToken, takerToken, makerTokenAmounts[i], psm, vat);
|
||||
|
||||
if (sellAmount == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
}
|
||||
|
||||
MakerPsmBridgeData memory data = MakerPsmBridgeData({
|
||||
psmAddress: psmInfo.psmAddress,
|
||||
gemTokenAddres: psmInfo.gemTokenAddress
|
||||
});
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(data),
|
||||
buyTokenData: abi.encode(data),
|
||||
getSwapQuoteCallback: this.sampleSwapFromMakerPsm
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _samplePSMSell(MakerPsmInfo memory psmInfo, address makerToken, address takerToken, uint256 takerTokenAmount, IPSM psm, IVAT vat)
|
||||
private
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
(uint256 totalDebtInWad,,, uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(psmInfo.ilkIdentifier);
|
||||
uint256 gemTokenBaseUnit = uint256(1e6);
|
||||
|
||||
if (takerToken == psmInfo.gemTokenAddress) {
|
||||
// Simulate sellGem
|
||||
// Selling USDC to the PSM, increasing the total debt
|
||||
// Convert USDC 6 decimals to 18 decimals [wad]
|
||||
uint256 takerTokenAmountInWad = takerTokenAmount.safeMul(1e12);
|
||||
|
||||
uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY);
|
||||
|
||||
// PSM is too full to fit
|
||||
if (newTotalDebtInRad >= debtCeilingInRad) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint256 feeInWad = takerTokenAmountInWad.safeMul(psm.tin()).safeDiv(WAD);
|
||||
uint256 makerTokenAmountInWad = takerTokenAmountInWad.safeSub(feeInWad);
|
||||
|
||||
return makerTokenAmountInWad;
|
||||
} else if (makerToken == psmInfo.gemTokenAddress) {
|
||||
// Simulate buyGem
|
||||
// Buying USDC from the PSM, decreasing the total debt
|
||||
// Selling DAI for USDC, already in 18 decimals [wad]
|
||||
uint256 takerTokenAmountInWad = takerTokenAmount;
|
||||
if (takerTokenAmountInWad > totalDebtInWad) {
|
||||
return 0;
|
||||
}
|
||||
uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY);
|
||||
|
||||
// PSM is empty, not enough USDC to buy from it
|
||||
if (newTotalDebtInRad <= debtFloorInRad) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint256 feeDivisorInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout;
|
||||
uint256 makerTokenAmountInGemTokenBaseUnits = takerTokenAmountInWad.safeMul(gemTokenBaseUnit).safeDiv(feeDivisorInWad);
|
||||
|
||||
return makerTokenAmountInGemTokenBaseUnits;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function _samplePSMBuy(MakerPsmInfo memory psmInfo, address makerToken, address takerToken, uint256 makerTokenAmount, IPSM psm, IVAT vat)
|
||||
private
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
(uint256 totalDebtInWad,,, uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(psmInfo.ilkIdentifier);
|
||||
|
||||
if (takerToken == psmInfo.gemTokenAddress) {
|
||||
// Simulate sellGem
|
||||
// Selling USDC to the PSM, increasing the total debt
|
||||
uint256 makerTokenAmountInWad = makerTokenAmount;
|
||||
uint256 feeDivisorInWad = WAD.safeSub(psm.tin()); // eg. 0.999 * 10 ** 18 with 0.1% tin;
|
||||
uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(WAD).safeDiv(feeDivisorInWad);
|
||||
uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY);
|
||||
|
||||
// PSM is too full to fit
|
||||
if (newTotalDebtInRad >= debtCeilingInRad) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint256 takerTokenAmountInGemInGemBaseUnits = (takerTokenAmountInWad.safeDiv(1e12)).safeAdd(1); // Add 1 to deal with cut off decimals converting to lower decimals
|
||||
|
||||
return takerTokenAmountInGemInGemBaseUnits;
|
||||
} else if (makerToken == psmInfo.gemTokenAddress) {
|
||||
// Simulate buyGem
|
||||
// Buying USDC from the PSM, decreasing the total debt
|
||||
uint256 makerTokenAmountInWad = makerTokenAmount.safeMul(1e12);
|
||||
uint256 feeMultiplierInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout;
|
||||
uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(feeMultiplierInWad).safeDiv(WAD);
|
||||
if (takerTokenAmountInWad > totalDebtInWad) {
|
||||
return 0;
|
||||
}
|
||||
uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY);
|
||||
|
||||
// PSM is empty, not enough USDC to buy
|
||||
if (newTotalDebtInRad <= debtFloorInRad) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
return takerTokenAmountInWad;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,17 +20,40 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IMooniswap.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
interface IMooniswapRegistry {
|
||||
function pools(address token1, address token2) external view returns(address);
|
||||
}
|
||||
|
||||
contract MooniswapSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
MixinMooniswap,
|
||||
SwapRevertSampler
|
||||
{
|
||||
/// @dev Gas limit for Mooniswap calls.
|
||||
uint256 constant private MOONISWAP_CALL_GAS = 150e3; // 150k
|
||||
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
MixinMooniswap(weth)
|
||||
{ }
|
||||
|
||||
function sampleSwapFromMooniswap(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeMooniswapInternal(
|
||||
_getNativeWrappedToken(),
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Mooniswap.
|
||||
/// @param registry Address of the Mooniswap Registry.
|
||||
@@ -38,6 +61,7 @@ contract MooniswapSampler is
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return pool The contract address for the pool
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromMooniswap(
|
||||
@@ -47,69 +71,22 @@ contract MooniswapSampler is
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (IMooniswap pool, uint256[] memory makerTokenAmounts)
|
||||
returns (address pool, uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
uint256 buyAmount = sampleSingleSellFromMooniswapPool(
|
||||
registry,
|
||||
takerToken,
|
||||
makerToken,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pool = IMooniswap(
|
||||
IMooniswapRegistry(registry).pools(takerToken, makerToken)
|
||||
);
|
||||
}
|
||||
|
||||
function sampleSingleSellFromMooniswapPool(
|
||||
address registry,
|
||||
address mooniswapTakerToken,
|
||||
address mooniswapMakerToken,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
// Find the pool for the pair.
|
||||
IMooniswap pool = IMooniswap(
|
||||
IMooniswapRegistry(registry).pools(mooniswapTakerToken, mooniswapMakerToken)
|
||||
);
|
||||
// If there is no pool then return early
|
||||
pool = _getMooniswapPool(registry, takerToken, makerToken);
|
||||
if (address(pool) == address(0)) {
|
||||
return 0;
|
||||
}
|
||||
uint256 poolBalance = mooniswapTakerToken == address(0)
|
||||
? address(pool).balance
|
||||
: IERC20TokenV06(mooniswapTakerToken).balanceOf(address(pool));
|
||||
// If the pool balance is smaller than the sell amount
|
||||
// don't sample to avoid multiplication overflow in buys
|
||||
if (poolBalance < takerTokenAmount) {
|
||||
return 0;
|
||||
}
|
||||
try
|
||||
pool.getReturn
|
||||
{gas: MOONISWAP_CALL_GAS}
|
||||
(mooniswapTakerToken, mooniswapMakerToken, takerTokenAmount)
|
||||
returns (uint256 amount)
|
||||
{
|
||||
return amount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
return (pool, gasUsed, makerTokenAmounts);
|
||||
}
|
||||
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(pool),
|
||||
getSwapQuoteCallback: this.sampleSwapFromMooniswap
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Mooniswap.
|
||||
@@ -118,6 +95,7 @@ contract MooniswapSampler is
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return pool The contract address for the pool
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromMooniswap(
|
||||
@@ -127,43 +105,42 @@ contract MooniswapSampler is
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (IMooniswap pool, uint256[] memory takerTokenAmounts)
|
||||
returns (address pool, uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
pool = _getMooniswapPool(registry, takerToken, makerToken);
|
||||
if (address(pool) == address(0)) {
|
||||
return (pool, gasUsed, takerTokenAmounts);
|
||||
}
|
||||
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(registry, makerToken),
|
||||
takerTokenData: abi.encode(registry, takerToken),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(pool),
|
||||
buyTokenData: abi.encode(pool),
|
||||
getSwapQuoteCallback: this.sampleSwapFromMooniswap
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
|
||||
pool = IMooniswap(
|
||||
IMooniswapRegistry(registry).pools(takerToken, makerToken)
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromMooniswap(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
function _getMooniswapPool(
|
||||
address registry,
|
||||
address takerToken,
|
||||
address makerToken
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
internal
|
||||
returns (address pool)
|
||||
{
|
||||
(address registry, address mooniswapTakerToken) = abi.decode(takerTokenData, (address, address));
|
||||
(address _registry, address mooniswapMakerToken) = abi.decode(makerTokenData, (address, address));
|
||||
return sampleSingleSellFromMooniswapPool(
|
||||
registry,
|
||||
mooniswapTakerToken,
|
||||
mooniswapMakerToken,
|
||||
sellAmount
|
||||
);
|
||||
// WETH is actually ETH in these pools and represented as address(0)
|
||||
address _takerToken = takerToken == address(_getNativeWrappedToken()) ? address(0) : takerToken;
|
||||
address _makerToken = makerToken == address(_getNativeWrappedToken()) ? address(0) : makerToken;
|
||||
|
||||
try
|
||||
IMooniswapRegistry(registry).pools{gas: 300e3}(_takerToken, _makerToken)
|
||||
returns (address _pool)
|
||||
{
|
||||
pool = _pool;
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
@@ -1,82 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IMultiBridge.sol";
|
||||
|
||||
|
||||
contract MultiBridgeSampler {
|
||||
|
||||
/// @dev Default gas limit for multibridge calls.
|
||||
uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k
|
||||
|
||||
/// @dev Sample sell quotes from MultiBridge.
|
||||
/// @param multibridge Address of the MultiBridge contract.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param intermediateToken The address of the intermediate token to
|
||||
/// use in an indirect route.
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromMultiBridge(
|
||||
address multibridge,
|
||||
address takerToken,
|
||||
address intermediateToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
// If no address provided, return all zeros.
|
||||
if (multibridge == address(0)) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
multibridge.staticcall.gas(DEFAULT_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IMultiBridge(0).getSellQuote.selector,
|
||||
takerToken,
|
||||
intermediateToken,
|
||||
makerToken,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
// Exit early if the amount is too high for the source to serve
|
||||
if (buyAmount == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
|
||||
|
||||
contract SamplerUtils {
|
||||
|
||||
/// @dev Overridable way to get token decimals.
|
||||
/// @param tokenAddress Address of the token.
|
||||
/// @return decimals The decimal places for the token.
|
||||
function _getTokenDecimals(address tokenAddress)
|
||||
virtual
|
||||
internal
|
||||
view
|
||||
returns (uint8 decimals)
|
||||
{
|
||||
return LibERC20TokenV06.compatDecimals(IERC20TokenV06(tokenAddress));
|
||||
}
|
||||
|
||||
function _toSingleValueArray(uint256 v)
|
||||
internal
|
||||
pure
|
||||
returns (uint256[] memory arr)
|
||||
{
|
||||
arr = new uint256[](1);
|
||||
arr[0] = v;
|
||||
}
|
||||
|
||||
/// @dev Assert that the tokens in a trade pair are valid.
|
||||
/// @param makerToken Address of the maker token.
|
||||
/// @param takerToken Address of the taker token.
|
||||
function _assertValidPair(address makerToken, address takerToken)
|
||||
internal
|
||||
pure
|
||||
{
|
||||
require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR");
|
||||
}
|
||||
}
|
@@ -20,28 +20,41 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./interfaces/IShell.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinShell.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
contract ShellSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
MixinShell,
|
||||
SwapRevertSampler
|
||||
{
|
||||
|
||||
struct ShellInfo {
|
||||
address poolAddress;
|
||||
}
|
||||
|
||||
/// @dev Default gas limit for Shell calls.
|
||||
uint256 constant private DEFAULT_CALL_GAS = 300e3; // 300k
|
||||
function sampleSwapFromShell(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeShell(
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from the Shell pool contract
|
||||
/// @param pool Address of the Shell pool contract
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromShell(
|
||||
@@ -51,26 +64,17 @@ contract ShellSampler is
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IShell(pool).viewOriginSwap
|
||||
{gas: DEFAULT_CALL_GAS}
|
||||
(takerToken, makerToken, takerTokenAmounts[i])
|
||||
returns (uint256 amount)
|
||||
{
|
||||
makerTokenAmounts[i] = amount;
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(pool),
|
||||
getSwapQuoteCallback: this.sampleSwapFromShell
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Shell pool contract
|
||||
@@ -78,6 +82,7 @@ contract ShellSampler is
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromShell(
|
||||
@@ -87,40 +92,17 @@ contract ShellSampler is
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, pool),
|
||||
takerTokenData: abi.encode(takerToken, pool),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromShell
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(pool),
|
||||
buyTokenData: abi.encode(pool),
|
||||
getSwapQuoteCallback: this.sampleSwapFromShell
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromShell(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(address takerToken, address pool) = abi.decode(takerTokenData, (address, address));
|
||||
(address makerToken) = abi.decode(makerTokenData, (address));
|
||||
|
||||
try
|
||||
this.sampleSellsFromShell
|
||||
(pool, takerToken, makerToken, _toSingleValueArray(sellAmount))
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
return amounts[0];
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,156 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
// import "./interfaces/ISmoothy.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
import "./interfaces/ISmoothy.sol";
|
||||
|
||||
contract SmoothySampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
/// @dev Information for sampling from smoothy sources.
|
||||
struct SmoothyInfo {
|
||||
address poolAddress;
|
||||
bytes4 sellQuoteFunctionSelector;
|
||||
bytes4 buyQuoteFunctionSelector;
|
||||
}
|
||||
|
||||
/// @dev Base gas limit for Smoothy calls.
|
||||
uint256 constant private SMOOTHY_CALL_GAS = 600e3;
|
||||
|
||||
/// @dev Sample sell quotes from Smoothy.
|
||||
/// @param smoothyInfo Smoothy information specific to this token pair.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromSmoothy(
|
||||
SmoothyInfo memory smoothyInfo,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
// Basically a Curve fork
|
||||
|
||||
// Smoothy only keep a percentage of its tokens available in reserve
|
||||
uint256 poolReserveMakerAmount = ISmoothy(smoothyInfo.poolAddress).getBalance(uint256(toTokenIdx)) -
|
||||
ISmoothy(smoothyInfo.poolAddress)._yBalances(uint256(toTokenIdx));
|
||||
(, , , uint256 decimals) = ISmoothy(smoothyInfo.poolAddress).getTokenStats(uint256(toTokenIdx));
|
||||
poolReserveMakerAmount = poolReserveMakerAmount/(10**(18-decimals));
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
smoothyInfo.poolAddress.staticcall.gas(SMOOTHY_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
smoothyInfo.sellQuoteFunctionSelector,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
|
||||
// Make sure the quoted buyAmount is available in the pool reserve
|
||||
if (buyAmount >= poolReserveMakerAmount) {
|
||||
// Assign pool reserve amount for all higher samples to break early
|
||||
for (uint256 j = i; j < numSamples; j++) {
|
||||
makerTokenAmounts[j] = poolReserveMakerAmount;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Smoothy.
|
||||
/// @param smoothyInfo Smoothy information specific to this token pair.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromSmoothy(
|
||||
SmoothyInfo memory smoothyInfo,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
// Buys not supported so approximate it.
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(toTokenIdx, smoothyInfo),
|
||||
takerTokenData: abi.encode(fromTokenIdx, smoothyInfo),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromSmoothy
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromSmoothy(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(int128 takerTokenIdx, SmoothyInfo memory smoothyInfo) =
|
||||
abi.decode(takerTokenData, (int128, SmoothyInfo));
|
||||
(int128 makerTokenIdx) =
|
||||
abi.decode(makerTokenData, (int128));
|
||||
(bool success, bytes memory resultData) =
|
||||
address(this).staticcall(abi.encodeWithSelector(
|
||||
this.sampleSellsFromSmoothy.selector,
|
||||
smoothyInfo,
|
||||
takerTokenIdx,
|
||||
makerTokenIdx,
|
||||
_toSingleValueArray(sellAmount)
|
||||
));
|
||||
if (!success) {
|
||||
return 0;
|
||||
}
|
||||
// solhint-disable-next-line indent
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
}
|
||||
}
|
412
packages/asset-swapper/contracts/src/SwapRevertSampler.sol
Normal file
412
packages/asset-swapper/contracts/src/SwapRevertSampler.sol
Normal file
@@ -0,0 +1,412 @@
|
||||
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./GasOverhead.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
|
||||
interface IHackedERC20 {
|
||||
function _setBalance(address owner, uint256 amount) external;
|
||||
function _setEnabled(bool enabled) external;
|
||||
}
|
||||
|
||||
contract SwapRevertSampler {
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
/// @dev Fixed address to register and read Gas overhead introduced by Swap revert sampling
|
||||
GasOverhead private constant GAS_OVERHEAD = GasOverhead(0xDeF1000000000000000000000000000000001337);
|
||||
/// @dev Maximum approximate (positive) error rate when approximating a buy quote.
|
||||
uint256 private constant APPROXIMATE_BUY_TARGET_EPSILON_BPS = 0.0005e4;
|
||||
/// @dev Maximum iterations to perform when approximating a buy quote.
|
||||
uint256 private constant APPROXIMATE_BUY_MAX_ITERATIONS = 3;
|
||||
uint256 private constant ONE_HUNDED_PERCENT_BPS = 1e4;
|
||||
/// @dev Upper limit of gas to give to a single Swap call
|
||||
uint256 private constant CALL_STIPEND = 2e6;
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
/// @dev Payable fallback to receive ETH from Kyber/WETH.
|
||||
receive ()
|
||||
external
|
||||
payable
|
||||
{ }
|
||||
|
||||
struct SwapRevertSamplerQuoteOpts {
|
||||
// Address of the token which is being sold
|
||||
address sellToken;
|
||||
// Address of the token which is wanted
|
||||
address buyToken;
|
||||
// Data required for the bridge to execute the swap
|
||||
bytes bridgeData;
|
||||
// Callback to retrieve a swap quote.
|
||||
function (address sellToken, address buyToken, bytes memory bridgeData, uint256 sellAmount)
|
||||
external
|
||||
returns (uint256)
|
||||
getSwapQuoteCallback;
|
||||
}
|
||||
|
||||
struct SwapRevertSamplerBuyQuoteOpts {
|
||||
// Address of the token which is being sold
|
||||
address sellToken;
|
||||
// Address of the token which is wanted
|
||||
address buyToken;
|
||||
// Data required for the bridge to execute the SELL_TOKEN->BUY_TOKEN swap
|
||||
bytes sellTokenData;
|
||||
// Data required for the bridge to execute the BUY_TOKEN->SELL_TOKEN swap
|
||||
bytes buyTokenData;
|
||||
// Callback to retrieve a swap quote.
|
||||
function (address sellToken, address buyToken, bytes memory bridgeData, uint256 sellAmount)
|
||||
external
|
||||
returns (uint256)
|
||||
getSwapQuoteCallback;
|
||||
}
|
||||
|
||||
function _callRevert(
|
||||
bytes4 selector,
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 amountIn
|
||||
)
|
||||
external
|
||||
{
|
||||
// Clear any registered overhead
|
||||
try
|
||||
GAS_OVERHEAD.clearOverhead()
|
||||
{ } catch { }
|
||||
// Measure the gas
|
||||
uint256 gasUsed = gasleft();
|
||||
// Perform the sell
|
||||
(bool success, bytes memory data) = address(this).call(
|
||||
abi.encodeWithSelector(selector, sellToken, buyToken, bridgeData, amountIn)
|
||||
);
|
||||
gasUsed = gasUsed - gasleft();
|
||||
// Remove any registered gas overhead
|
||||
try
|
||||
GAS_OVERHEAD.overhead()
|
||||
returns (uint256 gasOverhead)
|
||||
{
|
||||
gasUsed = gasUsed - gasOverhead;
|
||||
} catch { }
|
||||
|
||||
if (!success) {
|
||||
data.rrevert();
|
||||
}
|
||||
// Revert with the amount bought
|
||||
_revertSingleSwapSample(abi.decode(data, (uint256)), gasUsed);
|
||||
}
|
||||
|
||||
/// @dev Mints the sell token, then performs the swap, then reverts with the amount out.
|
||||
/// The SwapRevertSamplerQuoteOpts has been unrolled here as our ABI encoder cannot support
|
||||
/// encoding the function
|
||||
function _mintCallRevert(
|
||||
bytes4 selector,
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256[] memory amountsIn
|
||||
)
|
||||
external
|
||||
{
|
||||
// We assume the amounts are ascending and that
|
||||
// the underlying call can handle selling a specific amount
|
||||
uint256 amountIn = amountsIn[amountsIn.length - 1];
|
||||
|
||||
if (sellToken == address(_getNativeWrappedToken())) {
|
||||
try
|
||||
IEtherTokenV06(payable(sellToken)).deposit{ value: amountIn }()
|
||||
{ } catch { }
|
||||
} else {
|
||||
IHackedERC20 hackedSellToken = IHackedERC20(payable(sellToken));
|
||||
// Enable sell token to be tracked and shadowed
|
||||
try
|
||||
hackedSellToken._setEnabled(true)
|
||||
{ } catch { }
|
||||
|
||||
// Mint enough to sell
|
||||
try
|
||||
hackedSellToken._setBalance(address(this), amountIn)
|
||||
{ } catch { }
|
||||
}
|
||||
|
||||
// Burn any excess ETH to avoid balance issues for sources which use ETH directly
|
||||
address(0).transfer(address(this).balance);
|
||||
|
||||
uint256[] memory amountsOut = new uint256[](amountsIn.length);
|
||||
uint256[] memory gasUsed = new uint256[](amountsIn.length);
|
||||
|
||||
for (uint256 i = 0; i < amountsIn.length; i++) {
|
||||
try
|
||||
this._callRevert{gas: CALL_STIPEND}(
|
||||
selector,
|
||||
sellToken,
|
||||
buyToken,
|
||||
bridgeData,
|
||||
amountsIn[i]
|
||||
)
|
||||
{
|
||||
require(false, "Swap Sample should have reverted");
|
||||
} catch (bytes memory reason) {
|
||||
// Parse the reverted sample data
|
||||
(amountsOut[i], gasUsed[i]) = _parseRevertedSingleSwapSample(reason);
|
||||
// If we detect the amount out is 0 then we return early
|
||||
// rather than continue performing excess work
|
||||
|
||||
// Some sources (Balancer) display issues, especially with small amounts
|
||||
// Where the amountsOut can range, e.g 448,0,0,0,2476,3048,0,4279,4941,0,0,7133,
|
||||
|
||||
if (amountsOut[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Revert the entire sampling
|
||||
_revertSwapSample(amountsOut, gasUsed);
|
||||
}
|
||||
|
||||
function _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts memory opts,
|
||||
uint256[] memory amountsIn
|
||||
)
|
||||
internal
|
||||
returns (uint256[] memory gasUsed, uint256[] memory amountsOut)
|
||||
{
|
||||
try
|
||||
this._mintCallRevert(
|
||||
opts.getSwapQuoteCallback.selector,
|
||||
opts.sellToken,
|
||||
opts.buyToken,
|
||||
opts.bridgeData,
|
||||
amountsIn
|
||||
)
|
||||
{
|
||||
require(false, "Swap Sample should have reverted");
|
||||
} catch (bytes memory reason) {
|
||||
// Parse the reverted sample datas
|
||||
(amountsOut, gasUsed) = abi.decode(reason, (uint256[], uint256[]));
|
||||
}
|
||||
}
|
||||
|
||||
function _getNativeWrappedToken()
|
||||
internal
|
||||
view
|
||||
returns (IEtherTokenV06)
|
||||
{
|
||||
uint256 chainId;
|
||||
assembly {
|
||||
chainId := chainid()
|
||||
}
|
||||
address token;
|
||||
if (chainId == 1) {
|
||||
// Ethereum Mainnet
|
||||
token = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
||||
} else if (chainId == 3) {
|
||||
// Ropsten
|
||||
token = 0xc778417E063141139Fce010982780140Aa0cD5Ab;
|
||||
} else if (chainId == 4) {
|
||||
// Rinkeby
|
||||
token = 0xc778417E063141139Fce010982780140Aa0cD5Ab;
|
||||
} else if (chainId == 42) {
|
||||
// Kovan
|
||||
token = 0xd0A1E359811322d97991E03f863a0C30C2cF029C;
|
||||
} else if (chainId == 56) {
|
||||
// BSC
|
||||
token = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
|
||||
} else if (chainId == 137) {
|
||||
// Polygon
|
||||
token = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270;
|
||||
} else if (chainId == 1337) {
|
||||
// 0x Ganache
|
||||
token = 0x0B1ba0af832d7C05fD64161E0Db78E85978E8082;
|
||||
}
|
||||
if (token == address(0)) {
|
||||
revert("No native wrapped token");
|
||||
}
|
||||
return IEtherTokenV06(token);
|
||||
}
|
||||
|
||||
function _revertSingleSwapSample(
|
||||
uint256 amount,
|
||||
uint256 gasUsed
|
||||
)
|
||||
internal
|
||||
{
|
||||
// Revert it so there is no state change
|
||||
assembly {
|
||||
mstore(0, amount)
|
||||
mstore(32, gasUsed)
|
||||
revert(0, 64)
|
||||
}
|
||||
}
|
||||
|
||||
function _revertSwapSample(
|
||||
uint256[] memory amounts,
|
||||
uint256[] memory gasUsed
|
||||
)
|
||||
internal
|
||||
{
|
||||
bytes memory data = abi.encode(amounts, gasUsed);
|
||||
// Revert it so there is no state change
|
||||
assembly {
|
||||
revert(add(data, 32), mload(data))
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Parses the reverted swap sample data. If no amount
|
||||
/// is decoded, 0 is returned.
|
||||
/// @param reason the string which contains the possible
|
||||
/// sample amount
|
||||
/// @return the decoded sample amount or 0
|
||||
/// @return the gas used in the sample
|
||||
function _parseRevertedSingleSwapSample(
|
||||
bytes memory reason
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (uint256, uint256)
|
||||
{
|
||||
if (reason.length != 64) {
|
||||
return (0,0);
|
||||
}
|
||||
return abi.decode(reason, (uint256, uint256));
|
||||
}
|
||||
|
||||
function _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts memory opts,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
internal
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
if (makerTokenAmounts.length == 0) {
|
||||
return (gasUsed, takerTokenAmounts);
|
||||
}
|
||||
|
||||
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
|
||||
gasUsed = new uint256[](makerTokenAmounts.length);
|
||||
|
||||
uint256[] memory sellAmounts = new uint256[](1);
|
||||
sellAmounts[0] = makerTokenAmounts[0];
|
||||
|
||||
SwapRevertSamplerQuoteOpts memory sellOpts = SwapRevertSamplerQuoteOpts({
|
||||
sellToken: opts.sellToken,
|
||||
buyToken: opts.buyToken,
|
||||
bridgeData: opts.sellTokenData,
|
||||
getSwapQuoteCallback: opts.getSwapQuoteCallback
|
||||
});
|
||||
|
||||
SwapRevertSamplerQuoteOpts memory buyOpts = SwapRevertSamplerQuoteOpts({
|
||||
sellToken: opts.buyToken,
|
||||
buyToken: opts.sellToken,
|
||||
bridgeData: opts.buyTokenData,
|
||||
getSwapQuoteCallback: opts.getSwapQuoteCallback
|
||||
});
|
||||
// Inverted, perform a sell of the token the user wants to buy
|
||||
(, sellAmounts) = _sampleSwapQuotesRevert(buyOpts, sellAmounts);
|
||||
if (sellAmounts.length == 0 || sellAmounts[0] == 0) {
|
||||
return (gasUsed, takerTokenAmounts);
|
||||
}
|
||||
|
||||
uint256[] memory buyAmounts;
|
||||
// Sell of the token the user wishes to dispose, see how much we buy
|
||||
(, buyAmounts) = _sampleSwapQuotesRevert(sellOpts, sellAmounts);
|
||||
|
||||
if (buyAmounts.length == 0 || buyAmounts[0] == 0) {
|
||||
return (gasUsed, takerTokenAmounts);
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
|
||||
uint256[] memory _gasUsed;
|
||||
for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) {
|
||||
// adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER
|
||||
sellAmounts[0] = _safeGetPartialAmountCeil(
|
||||
makerTokenAmounts[i],
|
||||
buyAmounts[0],
|
||||
sellAmounts[0]
|
||||
);
|
||||
if (sellAmounts.length == 0 || sellAmounts[0] == 0) {
|
||||
break;
|
||||
}
|
||||
sellAmounts[0] = _safeGetPartialAmountCeil(
|
||||
(ONE_HUNDED_PERCENT_BPS + APPROXIMATE_BUY_TARGET_EPSILON_BPS),
|
||||
ONE_HUNDED_PERCENT_BPS,
|
||||
sellAmounts[0]
|
||||
);
|
||||
if (sellAmounts.length == 0 || sellAmounts[0] == 0) {
|
||||
break;
|
||||
}
|
||||
uint256[] memory _buyAmounts;
|
||||
(_gasUsed, _buyAmounts) = _sampleSwapQuotesRevert(sellOpts, sellAmounts);
|
||||
if (_buyAmounts.length == 0 || _buyAmounts[0] == 0) {
|
||||
break;
|
||||
}
|
||||
// We re-use buyAmount next iteration, only assign if it is
|
||||
// non zero
|
||||
buyAmounts = _buyAmounts;
|
||||
// If we've reached our goal, exit early
|
||||
if (buyAmounts[0] >= makerTokenAmounts[i]) {
|
||||
uint256 eps =
|
||||
(buyAmounts[0] - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS /
|
||||
makerTokenAmounts[i];
|
||||
if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We've encountered reverts, so bail
|
||||
if (_gasUsed.length == 0 || _gasUsed[0] == 0) {
|
||||
return (gasUsed, takerTokenAmounts);
|
||||
}
|
||||
|
||||
if (buyAmounts.length > 0) {
|
||||
gasUsed[i] = _gasUsed[0];
|
||||
// We do our best to close in on the requested amount, but we can either over buy or under buy and exit
|
||||
// if we hit a max iteration limit
|
||||
// We scale the sell amount to get the approximate target
|
||||
takerTokenAmounts[i] = _safeGetPartialAmountCeil(
|
||||
makerTokenAmounts[i],
|
||||
buyAmounts[0],
|
||||
sellAmounts[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _safeGetPartialAmountCeil(
|
||||
uint256 numerator,
|
||||
uint256 denominator,
|
||||
uint256 target
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256 partialAmount)
|
||||
{
|
||||
if (numerator == 0 || target == 0 || denominator == 0) return 0;
|
||||
uint256 c = numerator * target;
|
||||
if (c / numerator != target) return 0;
|
||||
return (c + (denominator - 1)) / denominator;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -40,10 +40,11 @@ contract TwoHopSampler {
|
||||
returns (
|
||||
HopInfo memory firstHop,
|
||||
HopInfo memory secondHop,
|
||||
uint256 intermediateAssetAmount,
|
||||
uint256 buyAmount
|
||||
)
|
||||
{
|
||||
uint256 intermediateAssetAmount = 0;
|
||||
intermediateAssetAmount = 0;
|
||||
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
|
||||
firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, sellAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]);
|
||||
@@ -57,7 +58,7 @@ contract TwoHopSampler {
|
||||
}
|
||||
}
|
||||
if (intermediateAssetAmount == 0) {
|
||||
return (firstHop, secondHop, buyAmount);
|
||||
return (firstHop, secondHop, intermediateAssetAmount, buyAmount);
|
||||
}
|
||||
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
|
||||
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, intermediateAssetAmount);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,32 +20,43 @@
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IUniswapExchangeQuotes.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
interface IUniswapExchangeFactory {
|
||||
|
||||
/// @dev Get the exchange for a token.
|
||||
/// @param tokenAddress The address of the token contract.
|
||||
function getExchange(address tokenAddress)
|
||||
external
|
||||
view
|
||||
returns (address);
|
||||
}
|
||||
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswap.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
contract UniswapSampler is
|
||||
SamplerUtils
|
||||
MixinUniswap,
|
||||
SwapRevertSampler
|
||||
{
|
||||
/// @dev Gas limit for Uniswap calls.
|
||||
uint256 constant private UNISWAP_CALL_GAS = 150e3; // 150k
|
||||
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
MixinUniswap(weth)
|
||||
{ }
|
||||
|
||||
function sampleSwapFromUniswap(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeUniswapInternal(
|
||||
_getNativeWrappedToken(),
|
||||
IERC20TokenV06(sellToken),
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Uniswap.
|
||||
/// @param router Address of the Uniswap Router
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromUniswap(
|
||||
@@ -55,59 +66,24 @@ contract UniswapSampler is
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == address(0) ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, takerToken);
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == address(0) ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, makerToken);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
bool didSucceed = true;
|
||||
if (makerToken == address(0)) {
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
} else if (takerToken == address(0)) {
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
} else {
|
||||
uint256 ethBought;
|
||||
(ethBought, didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
if (ethBought != 0) {
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||
ethBought
|
||||
);
|
||||
} else {
|
||||
makerTokenAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
// Break early if amounts are 0
|
||||
if (!didSucceed || makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
bridgeData: abi.encode(router),
|
||||
getSwapQuoteCallback: this.sampleSwapFromUniswap
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Uniswap.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromUniswap(
|
||||
@@ -117,98 +93,18 @@ contract UniswapSampler is
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == address(0) ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, takerToken);
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == address(0) ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, makerToken);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
bool didSucceed = true;
|
||||
if (makerToken == address(0)) {
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
} else if (takerToken == address(0)) {
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
} else {
|
||||
uint256 ethSold;
|
||||
(ethSold, didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
if (ethSold != 0) {
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||
ethSold
|
||||
);
|
||||
} else {
|
||||
takerTokenAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
// Break early if amounts are 0
|
||||
if (!didSucceed || takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Gracefully calls a Uniswap pricing function.
|
||||
/// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange.
|
||||
/// @param functionSelector Selector of the target function.
|
||||
/// @param inputAmount Quantity parameter particular to the pricing function.
|
||||
/// @return outputAmount The returned amount from the function call. Will be
|
||||
/// zero if the call fails or if `uniswapExchangeAddress` is zero.
|
||||
function _callUniswapExchangePriceFunction(
|
||||
address uniswapExchangeAddress,
|
||||
bytes4 functionSelector,
|
||||
uint256 inputAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 outputAmount, bool didSucceed)
|
||||
{
|
||||
if (uniswapExchangeAddress == address(0)) {
|
||||
return (outputAmount, didSucceed);
|
||||
}
|
||||
bytes memory resultData;
|
||||
(didSucceed, resultData) =
|
||||
uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
functionSelector,
|
||||
inputAmount
|
||||
));
|
||||
if (didSucceed) {
|
||||
outputAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Retrive an existing Uniswap exchange contract.
|
||||
/// Throws if the exchange does not exist.
|
||||
/// @param router Address of the Uniswap router.
|
||||
/// @param tokenAddress Address of the token contract.
|
||||
/// @return exchange `IUniswapExchangeQuotes` for the token.
|
||||
function _getUniswapExchange(address router, address tokenAddress)
|
||||
private
|
||||
view
|
||||
returns (IUniswapExchangeQuotes exchange)
|
||||
{
|
||||
exchange = IUniswapExchangeQuotes(
|
||||
address(IUniswapExchangeFactory(router)
|
||||
.getExchange(tokenAddress))
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: takerToken,
|
||||
buyToken: makerToken,
|
||||
sellTokenData: abi.encode(router),
|
||||
buyTokenData: abi.encode(router),
|
||||
getSwapQuoteCallback: this.sampleSwapFromUniswap
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -21,17 +21,36 @@ pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IUniswapV2Router01.sol";
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswapV2.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
|
||||
contract UniswapV2Sampler
|
||||
contract UniswapV2Sampler is
|
||||
MixinUniswapV2,
|
||||
SwapRevertSampler
|
||||
{
|
||||
/// @dev Gas limit for UniswapV2 calls.
|
||||
uint256 constant private UNISWAPV2_CALL_GAS = 150e3; // 150k
|
||||
|
||||
function sampleSwapFromUniswapV2(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeUniswapV2(
|
||||
IERC20TokenV06(buyToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from UniswapV2.
|
||||
/// @param router Router to look up tokens and amounts
|
||||
/// @param path Token route. Should be takerToken -> makerToken
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return gasUsed gas consumed for each sample amount
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromUniswapV2(
|
||||
@@ -40,34 +59,24 @@ contract UniswapV2Sampler
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IUniswapV2Router01(router).getAmountsOut
|
||||
{gas: UNISWAPV2_CALL_GAS}
|
||||
(takerTokenAmounts[i], path)
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
makerTokenAmounts[i] = amounts[path.length - 1];
|
||||
// Break early if there are 0 amounts
|
||||
if (makerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
(gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: path[0],
|
||||
buyToken: path[path.length - 1],
|
||||
bridgeData: abi.encode(router, path),
|
||||
getSwapQuoteCallback: this.sampleSwapFromUniswapV2
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from UniswapV2.
|
||||
/// @param router Router to look up tokens and amounts
|
||||
/// @param path Token route. Should be takerToken -> makerToken.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return gasUsed gas consumed for each sample amount
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromUniswapV2(
|
||||
@@ -76,27 +85,22 @@ contract UniswapV2Sampler
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
try
|
||||
IUniswapV2Router01(router).getAmountsIn
|
||||
{gas: UNISWAPV2_CALL_GAS}
|
||||
(makerTokenAmounts[i], path)
|
||||
returns (uint256[] memory amounts)
|
||||
{
|
||||
takerTokenAmounts[i] = amounts[0];
|
||||
// Break early if there are 0 amounts
|
||||
if (takerTokenAmounts[i] == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
address[] memory reversedPath = new address[](path.length);
|
||||
for (uint256 i = 0; i < path.length; ++i) {
|
||||
reversedPath[i] = path[path.length - i - 1];
|
||||
}
|
||||
|
||||
(gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: path[0],
|
||||
buyToken: path[path.length - 1],
|
||||
sellTokenData: abi.encode(router, path),
|
||||
buyTokenData: abi.encode(router, reversedPath),
|
||||
getSwapQuoteCallback: this.sampleSwapFromUniswapV2
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -21,18 +21,16 @@ pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
|
||||
import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswapV3.sol";
|
||||
import "./SwapRevertSampler.sol";
|
||||
|
||||
interface IUniswapV3Quoter {
|
||||
function factory()
|
||||
external
|
||||
view
|
||||
returns (IUniswapV3Factory factory);
|
||||
function quoteExactInput(bytes memory path, uint256 amountIn)
|
||||
external
|
||||
returns (uint256 amountOut);
|
||||
function quoteExactOutput(bytes memory path, uint256 amountOut)
|
||||
external
|
||||
returns (uint256 amountIn);
|
||||
}
|
||||
|
||||
interface IUniswapV3Factory {
|
||||
@@ -48,26 +46,47 @@ interface IUniswapV3Pool {
|
||||
function fee() external view returns (uint24);
|
||||
}
|
||||
|
||||
contract UniswapV3Sampler
|
||||
contract UniswapV3Sampler is
|
||||
MixinUniswapV3,
|
||||
SwapRevertSampler
|
||||
{
|
||||
/// @dev Gas limit for UniswapV3 calls. This is 100% a guess.
|
||||
uint256 constant private QUOTE_GAS = 300e3;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
function sampleSwapFromUniswapV3(
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
bytes memory bridgeData,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
external
|
||||
returns (uint256)
|
||||
{
|
||||
return _tradeUniswapV3(
|
||||
IERC20TokenV06(sellToken),
|
||||
takerTokenAmount,
|
||||
bridgeData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from UniswapV3.
|
||||
/// @param quoter UniswapV3 Quoter contract.
|
||||
/// @param router UniswapV3 Router contract.
|
||||
/// @param path Token route. Should be takerToken -> makerToken
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return uniswapPaths The encoded uniswap path for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromUniswapV3(
|
||||
IUniswapV3Quoter quoter,
|
||||
address router,
|
||||
IERC20TokenV06[] memory path,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (
|
||||
bytes[] memory uniswapPaths,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
{
|
||||
@@ -75,50 +94,57 @@ contract UniswapV3Sampler
|
||||
_getValidPoolPaths(quoter.factory(), path, 0);
|
||||
|
||||
makerTokenAmounts = new uint256[](takerTokenAmounts.length);
|
||||
gasUsed = new uint256[](takerTokenAmounts.length);
|
||||
uniswapPaths = new bytes[](takerTokenAmounts.length);
|
||||
|
||||
for (uint256 i = 0; i < takerTokenAmounts.length; ++i) {
|
||||
// Pick the best result from all the paths.
|
||||
bytes memory topUniswapPath;
|
||||
uint256 topBuyAmount = 0;
|
||||
for (uint256 j = 0; j < poolPaths.length; ++j) {
|
||||
bytes memory uniswapPath = _toUniswapPath(path, poolPaths[j]);
|
||||
try
|
||||
quoter.quoteExactInput
|
||||
{ gas: QUOTE_GAS }
|
||||
(uniswapPath, takerTokenAmounts[i])
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
if (topBuyAmount <= buyAmount) {
|
||||
topBuyAmount = buyAmount;
|
||||
topUniswapPath = uniswapPath;
|
||||
}
|
||||
} catch { }
|
||||
for (uint256 i = 0; i < poolPaths.length; ++i) {
|
||||
bytes memory _uniswapPath = _toUniswapPath(path, poolPaths[i]);
|
||||
(
|
||||
uint256[] memory _gasUsed,
|
||||
uint256[] memory _makerTokenAmounts
|
||||
) = _sampleSwapQuotesRevert(
|
||||
SwapRevertSamplerQuoteOpts({
|
||||
sellToken: address(path[0]),
|
||||
buyToken: address(path[path.length - 1]),
|
||||
bridgeData: abi.encode(router, _uniswapPath),
|
||||
getSwapQuoteCallback: this.sampleSwapFromUniswapV3
|
||||
}),
|
||||
takerTokenAmounts
|
||||
);
|
||||
for (uint256 j = 0; j < _makerTokenAmounts.length; ++j) {
|
||||
// Break early if we can't complete the sells.
|
||||
if (_makerTokenAmounts[j] == 0) {
|
||||
break;
|
||||
}
|
||||
// If this is better than what we have found, prefer it
|
||||
if (makerTokenAmounts[j] <= _makerTokenAmounts[j]) {
|
||||
makerTokenAmounts[j] = _makerTokenAmounts[j];
|
||||
gasUsed[j] = _gasUsed[j];
|
||||
uniswapPaths[j] = _uniswapPath;
|
||||
}
|
||||
}
|
||||
// Break early if we can't complete the buys.
|
||||
if (topBuyAmount == 0) {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = topBuyAmount;
|
||||
uniswapPaths[i] = topUniswapPath;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from UniswapV3.
|
||||
/// @param quoter UniswapV3 Quoter contract.
|
||||
/// @param router UniswapV3 Router contract.
|
||||
/// @param path Token route. Should be takerToken -> makerToken.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return uniswapPaths The encoded uniswap path for each sample.
|
||||
/// @return gasUsed gas consumed in each sample sell
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromUniswapV3(
|
||||
IUniswapV3Quoter quoter,
|
||||
address router,
|
||||
IERC20TokenV06[] memory path,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (
|
||||
bytes[] memory uniswapPaths,
|
||||
uint256[] memory gasUsed,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
{
|
||||
@@ -127,37 +153,43 @@ contract UniswapV3Sampler
|
||||
IERC20TokenV06[] memory reversedPath = _reverseTokenPath(path);
|
||||
|
||||
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
|
||||
gasUsed = new uint256[](makerTokenAmounts.length);
|
||||
uniswapPaths = new bytes[](makerTokenAmounts.length);
|
||||
|
||||
for (uint256 i = 0; i < makerTokenAmounts.length; ++i) {
|
||||
// Pick the best result from all the paths.
|
||||
bytes memory topUniswapPath;
|
||||
uint256 topSellAmount = 0;
|
||||
for (uint256 j = 0; j < poolPaths.length; ++j) {
|
||||
// quoter requires path to be reversed for buys.
|
||||
bytes memory uniswapPath = _toUniswapPath(
|
||||
reversedPath,
|
||||
_reversePoolPath(poolPaths[j])
|
||||
);
|
||||
try
|
||||
quoter.quoteExactOutput
|
||||
{ gas: QUOTE_GAS }
|
||||
(uniswapPath, makerTokenAmounts[i])
|
||||
returns (uint256 sellAmount)
|
||||
{
|
||||
if (topSellAmount == 0 || topSellAmount >= sellAmount) {
|
||||
topSellAmount = sellAmount;
|
||||
// But the output path should still be encoded for sells.
|
||||
topUniswapPath = _toUniswapPath(path, poolPaths[j]);
|
||||
}
|
||||
} catch {}
|
||||
for (uint256 i = 0; i < poolPaths.length; ++i) {
|
||||
(
|
||||
uint256[] memory _gasUsed,
|
||||
uint256[] memory _takerTokenAmounts
|
||||
) = _sampleSwapApproximateBuys(
|
||||
SwapRevertSamplerBuyQuoteOpts({
|
||||
sellToken: address(path[0]),
|
||||
buyToken: address(path[path.length - 1]),
|
||||
sellTokenData: abi.encode(router, _toUniswapPath(path, poolPaths[i])),
|
||||
buyTokenData: abi.encode(
|
||||
router,
|
||||
_toUniswapPath(
|
||||
reversedPath,
|
||||
_reversePoolPath(poolPaths[i])
|
||||
)
|
||||
),
|
||||
getSwapQuoteCallback: this.sampleSwapFromUniswapV3
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
|
||||
for (uint256 j = 0; j < _takerTokenAmounts.length; ++j) {
|
||||
// Break early if we can't complete the buys.
|
||||
if (_takerTokenAmounts[j] == 0) {
|
||||
break;
|
||||
}
|
||||
// We can go from high to low here
|
||||
if (takerTokenAmounts[j] == 0 || takerTokenAmounts[j] >= _takerTokenAmounts[j]) {
|
||||
takerTokenAmounts[j] = _takerTokenAmounts[j];
|
||||
gasUsed[j] = _gasUsed[j];
|
||||
// But the output path should still be encoded for sells.
|
||||
uniswapPaths[j] = _toUniswapPath(path, poolPaths[i]);
|
||||
}
|
||||
}
|
||||
// Break early if we can't complete the buys.
|
||||
if (topSellAmount == 0) {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = topSellAmount;
|
||||
uniswapPaths[i] = topUniswapPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +300,7 @@ contract UniswapV3Sampler
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Must have a balance of both tokens.
|
||||
// // Must have a balance of both tokens.
|
||||
if (pool.token0().balanceOf(address(pool)) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
@@ -77,4 +77,24 @@ contract UtilitySampler {
|
||||
assembly { size := extcodesize(account) }
|
||||
return size > 0;
|
||||
}
|
||||
|
||||
function getCode(address addr)
|
||||
public
|
||||
view
|
||||
returns (bytes memory code)
|
||||
{
|
||||
assembly {
|
||||
// retrieve the size of the code, this needs assembly
|
||||
let size := extcodesize(addr)
|
||||
// allocate output byte array - this could also be done without assembly
|
||||
// by using o_code = new bytes(size)
|
||||
code := mload(0x40)
|
||||
// new "memory end" including padding
|
||||
mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
|
||||
// store length in memory
|
||||
mstore(code, size)
|
||||
// actually retrieve the code, this needs assembly
|
||||
extcodecopy(addr, add(code, 0x20), 0, size)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IBalancer {
|
||||
function isBound(address t) external view returns (bool);
|
||||
function getDenormalizedWeight(address token) external view returns (uint256);
|
||||
function getBalance(address token) external view returns (uint256);
|
||||
function getSwapFee() external view returns (uint256);
|
||||
function calcOutGivenIn(
|
||||
uint256 tokenBalanceIn,
|
||||
uint256 tokenWeightIn,
|
||||
uint256 tokenBalanceOut,
|
||||
uint256 tokenWeightOut,
|
||||
uint256 tokenAmountIn,
|
||||
uint256 swapFee
|
||||
) external pure returns (uint256 tokenAmountOut);
|
||||
function calcInGivenOut(
|
||||
uint256 tokenBalanceIn,
|
||||
uint256 tokenWeightIn,
|
||||
uint256 tokenBalanceOut,
|
||||
uint256 tokenWeightOut,
|
||||
uint256 tokenAmountOut,
|
||||
uint256 swapFee
|
||||
) external pure returns (uint256 tokenAmountIn);
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IBancor {}
|
||||
|
||||
interface IBancorNetwork {
|
||||
function conversionPath(address _sourceToken, address _targetToken) external view returns (address[] memory);
|
||||
function rateByPath(address[] memory _path, uint256 _amount) external view returns (uint256);
|
||||
}
|
||||
|
||||
interface IBancorRegistry {
|
||||
function getAddress(bytes32 _contractName) external view returns (address);
|
||||
function BANCOR_NETWORK() external view returns (bytes32);
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
// solhint-disable func-name-mixedcase
|
||||
interface ICurve {
|
||||
|
||||
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
|
||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param sellAmount The amount of token being bought.
|
||||
/// @param minBuyAmount The minimum buy amount of the token being bought.
|
||||
function exchange_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount
|
||||
)
|
||||
external;
|
||||
|
||||
/// @dev Get the amount of `toToken` by selling `sellAmount` of `fromToken`
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param sellAmount The amount of token being bought.
|
||||
function get_dy_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 sellAmount
|
||||
)
|
||||
external
|
||||
returns (uint256 dy);
|
||||
|
||||
/// @dev Get the amount of `fromToken` by buying `buyAmount` of `toToken`
|
||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param buyAmount The amount of token being bought.
|
||||
function get_dx_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 buyAmount
|
||||
)
|
||||
external
|
||||
returns (uint256 dx);
|
||||
|
||||
/// @dev Get the underlying token address from the token index
|
||||
/// @param i The token index.
|
||||
function underlying_coins(
|
||||
int128 i
|
||||
)
|
||||
external
|
||||
returns (address tokenAddress);
|
||||
}
|
@@ -22,22 +22,6 @@ pragma solidity ^0.6;
|
||||
// Keepin everything together
|
||||
interface IKyberNetwork {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
interface IKyberNetworkProxy {
|
||||
|
||||
function getExpectedRateAfterFee(
|
||||
address src,
|
||||
address dest,
|
||||
uint256 srcQty,
|
||||
uint256 platformFeeBps,
|
||||
bytes calldata hint
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 expectedRate);
|
||||
}
|
||||
|
||||
interface IKyberHintHandler {
|
||||
|
@@ -1,33 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IMStable {
|
||||
|
||||
function getSwapOutput(
|
||||
address _input,
|
||||
address _output,
|
||||
uint256 _quantity
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 swapOutput);
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IMooniswapRegistry {
|
||||
|
||||
function pools(address token1, address token2) external view returns(address);
|
||||
}
|
||||
|
||||
interface IMooniswap {
|
||||
|
||||
function getReturn(
|
||||
address fromToken,
|
||||
address destToken,
|
||||
uint256 amount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns(uint256 returnAmount);
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IMultiBridge {
|
||||
|
||||
/// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
|
||||
/// @param tokenAddress The address of the ERC20 token to transfer.
|
||||
/// @param from Address to transfer asset from.
|
||||
/// @param to Address to transfer asset to.
|
||||
/// @param amount Amount of asset to transfer.
|
||||
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
|
||||
/// @return success The magic bytes `0xdc1600f3` if successful.
|
||||
function bridgeTransferFrom(
|
||||
address tokenAddress,
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata bridgeData
|
||||
)
|
||||
external
|
||||
returns (bytes4 success);
|
||||
|
||||
/// @dev Quotes the amount of `makerToken` that would be obtained by
|
||||
/// selling `sellAmount` of `takerToken`.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param intermediateToken The address of the intermediate token to
|
||||
/// use in an indirect route.
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param sellAmount Amount of `takerToken` to sell.
|
||||
/// @return makerTokenAmount Amount of `makerToken` that would be obtained.
|
||||
function getSellQuote(
|
||||
address takerToken,
|
||||
address intermediateToken,
|
||||
address makerToken,
|
||||
uint256 sellAmount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 makerTokenAmount);
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IShell {
|
||||
|
||||
function viewOriginSwap (
|
||||
address from,
|
||||
address to,
|
||||
uint256 fromAmount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 toAmount);
|
||||
|
||||
function viewTargetSwap (
|
||||
address from,
|
||||
address to,
|
||||
uint256 toAmount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 fromAmount);
|
||||
}
|
||||
|
@@ -1,45 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface ISmoothy {
|
||||
|
||||
function getBalance (
|
||||
uint256 tid
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 balance);
|
||||
|
||||
function _yBalances (
|
||||
uint256 tid
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 balance);
|
||||
|
||||
function getTokenStats (
|
||||
uint256 tid
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 softWeight, uint256 hardWeight, uint256 balance, uint256 decimals);
|
||||
}
|
@@ -127,7 +127,6 @@ contract FailTrigger {
|
||||
|
||||
|
||||
contract TestERC20BridgeSamplerUniswapExchange is
|
||||
IUniswapExchangeQuotes,
|
||||
TestDeploymentConstants,
|
||||
FailTrigger
|
||||
{
|
||||
@@ -145,7 +144,6 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
function getEthToTokenInputPrice(
|
||||
uint256 ethSold
|
||||
)
|
||||
override
|
||||
external
|
||||
view
|
||||
returns (uint256 tokensBought)
|
||||
@@ -163,7 +161,6 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
function getEthToTokenOutputPrice(
|
||||
uint256 tokensBought
|
||||
)
|
||||
override
|
||||
external
|
||||
view
|
||||
returns (uint256 ethSold)
|
||||
@@ -181,7 +178,6 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
function getTokenToEthInputPrice(
|
||||
uint256 tokensSold
|
||||
)
|
||||
override
|
||||
external
|
||||
view
|
||||
returns (uint256 ethBought)
|
||||
@@ -199,7 +195,6 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
function getTokenToEthOutputPrice(
|
||||
uint256 ethBought
|
||||
)
|
||||
override
|
||||
external
|
||||
view
|
||||
returns (uint256 tokensSold)
|
||||
@@ -376,6 +371,31 @@ contract TestERC20BridgeSamplerKyberNetwork is
|
||||
toToken
|
||||
);
|
||||
}
|
||||
|
||||
function tradeWithHint(
|
||||
address fromToken,
|
||||
uint256 sellAmount,
|
||||
address toToken,
|
||||
address payable recipientAddress,
|
||||
uint256 maxBuyTokenAmount,
|
||||
uint256 minConversionRate,
|
||||
address payable walletId,
|
||||
bytes calldata hint
|
||||
)
|
||||
external
|
||||
payable
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken;
|
||||
toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken;
|
||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
||||
SALT,
|
||||
fromToken,
|
||||
toToken,
|
||||
sellAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -427,47 +447,17 @@ contract TestERC20BridgeSamplerEth2Dai is
|
||||
}
|
||||
|
||||
|
||||
contract TestERC20BridgeSamplerUniswapExchangeFactory is
|
||||
IUniswapExchangeFactory
|
||||
{
|
||||
mapping (address => IUniswapExchangeQuotes) private _exchangesByToken;
|
||||
|
||||
// Creates Uniswap exchange contracts for tokens.
|
||||
function createTokenExchanges(address[] calldata tokenAddresses)
|
||||
external
|
||||
{
|
||||
for (uint256 i = 0; i < tokenAddresses.length; i++) {
|
||||
address tokenAddress = tokenAddresses[i];
|
||||
_exchangesByToken[tokenAddress] =
|
||||
new TestERC20BridgeSamplerUniswapExchange(tokenAddress);
|
||||
}
|
||||
}
|
||||
|
||||
// `IUniswapExchangeFactory.getExchange()`.
|
||||
function getExchange(address tokenAddress)
|
||||
override
|
||||
external
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
return address(_exchangesByToken[tokenAddress]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contract TestERC20BridgeSampler is
|
||||
ERC20BridgeSampler,
|
||||
FailTrigger
|
||||
{
|
||||
TestERC20BridgeSamplerUniswapExchangeFactory public uniswap;
|
||||
TestERC20BridgeSamplerUniswapV2Router01 public uniswapV2Router;
|
||||
TestERC20BridgeSamplerEth2Dai public eth2Dai;
|
||||
TestERC20BridgeSamplerKyberNetwork public kyber;
|
||||
|
||||
uint8 private constant MAX_ORDER_STATUS = uint8(IExchange.OrderStatus.CANCELLED) + 1;
|
||||
|
||||
constructor() public ERC20BridgeSampler() {
|
||||
uniswap = new TestERC20BridgeSamplerUniswapExchangeFactory();
|
||||
constructor() public ERC20BridgeSampler(IEtherTokenV06(address(0))) {
|
||||
uniswapV2Router = new TestERC20BridgeSamplerUniswapV2Router01();
|
||||
eth2Dai = new TestERC20BridgeSamplerEth2Dai();
|
||||
kyber = new TestERC20BridgeSamplerKyberNetwork();
|
||||
@@ -477,7 +467,6 @@ contract TestERC20BridgeSampler is
|
||||
function createTokenExchanges(address[] calldata tokenAddresses)
|
||||
external
|
||||
{
|
||||
uniswap.createTokenExchanges(tokenAddresses);
|
||||
}
|
||||
|
||||
// Overridden to return deterministic states.
|
||||
@@ -493,14 +482,4 @@ contract TestERC20BridgeSampler is
|
||||
{
|
||||
return uint256(keccak256(abi.encode(order.salt))) % order.takerAmount;
|
||||
}
|
||||
|
||||
// Overriden to return deterministic decimals.
|
||||
function _getTokenDecimals(address tokenAddress)
|
||||
override
|
||||
internal
|
||||
view
|
||||
returns (uint8 decimals)
|
||||
{
|
||||
return LibDeterministicQuotes.getDeterministicTokenDecimals(tokenAddress);
|
||||
}
|
||||
}
|
||||
|
@@ -37,9 +37,9 @@
|
||||
"sampler-size": "jq .compilerOutput.evm.deployedBytecode.object -- test/generated-artifacts/ERC20BridgeSampler.json | echo $(( $(wc -c) / 2 - 1 ))"
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,DelegateHackedERC20,FakeTaker,HackedERC20,GasOverhead",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|IBalancer|IBancor|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json",
|
||||
"abis": "./test/generated-artifacts/@(BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|BoosterSampler|CurveSampler|CurveV2Sampler|DODOSampler|DODOV2Sampler|DelegateHackedERC20|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|GasOverhead|HackedERC20|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|ShellSampler|SwapRevertSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json",
|
||||
"postpublish": {
|
||||
"assets": []
|
||||
}
|
||||
|
@@ -6,10 +6,16 @@
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as BalanceChecker from '../generated-artifacts/BalanceChecker.json';
|
||||
import * as DelegateHackedERC20 from '../generated-artifacts/DelegateHackedERC20.json';
|
||||
import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.json';
|
||||
import * as FakeTaker from '../generated-artifacts/FakeTaker.json';
|
||||
import * as GasOverhead from '../generated-artifacts/GasOverhead.json';
|
||||
import * as HackedERC20 from '../generated-artifacts/HackedERC20.json';
|
||||
export const artifacts = {
|
||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
||||
BalanceChecker: BalanceChecker as ContractArtifact,
|
||||
DelegateHackedERC20: DelegateHackedERC20 as ContractArtifact,
|
||||
FakeTaker: FakeTaker as ContractArtifact,
|
||||
HackedERC20: HackedERC20 as ContractArtifact,
|
||||
GasOverhead: GasOverhead as ContractArtifact,
|
||||
};
|
||||
|
@@ -47,7 +47,7 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||
chainId: ChainId.Mainnet,
|
||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
||||
...DEFAULT_ORDER_PRUNER_OPTS,
|
||||
samplerGasLimit: 500e6,
|
||||
samplerGasLimit: 500e7,
|
||||
ethGasStationUrl: ETH_GAS_STATION_API_URL,
|
||||
rfqt: {
|
||||
takerApiKeyWhitelist: [],
|
||||
@@ -91,8 +91,6 @@ export const DEFAULT_WARNING_LOGGER: LogFunction = (obj, msg) =>
|
||||
const EMPTY_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||
export const INVALID_SIGNATURE = { signatureType: SignatureType.Invalid, v: 1, r: EMPTY_BYTES32, s: EMPTY_BYTES32 };
|
||||
|
||||
export { DEFAULT_FEE_SCHEDULE, DEFAULT_GAS_SCHEDULE } from './utils/market_operation_utils/constants';
|
||||
|
||||
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(30000);
|
||||
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
|
@@ -116,7 +116,6 @@ export {
|
||||
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
||||
export {
|
||||
DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID,
|
||||
DEFAULT_GAS_SCHEDULE,
|
||||
SOURCE_FLAGS,
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
@@ -138,7 +137,6 @@ export {
|
||||
DODOFillData,
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
FeeSchedule,
|
||||
Fill,
|
||||
FillData,
|
||||
GetMarketOrdersRfqOpts,
|
||||
|
@@ -27,13 +27,16 @@ import {
|
||||
import { assert } from './utils/assert';
|
||||
import { MarketOperationUtils } from './utils/market_operation_utils';
|
||||
import { BancorService } from './utils/market_operation_utils/bancor_service';
|
||||
import { SAMPLER_ADDRESS, SOURCE_FLAGS, ZERO_AMOUNT } from './utils/market_operation_utils/constants';
|
||||
import {
|
||||
MAX_UINT256,
|
||||
NATIVE_LIMIT_ORDER_GAS_USED,
|
||||
SOURCE_FLAGS,
|
||||
ZERO_AMOUNT,
|
||||
} from './utils/market_operation_utils/constants';
|
||||
import { DexOrderSampler } from './utils/market_operation_utils/sampler';
|
||||
import { SourceFilters } from './utils/market_operation_utils/source_filters';
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
FeeSchedule,
|
||||
FillData,
|
||||
GetMarketOrdersOpts,
|
||||
MarketDepth,
|
||||
MarketDepthSide,
|
||||
@@ -115,10 +118,13 @@ export class SwapQuoter {
|
||||
// Allow the sampler bytecode to be overwritten using geths override functionality
|
||||
const samplerBytecode = _.get(artifacts.ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object');
|
||||
// Allow address of the Sampler to be overridden, i.e in Ganache where overrides do not work
|
||||
const samplerAddress = (options.samplerOverrides && options.samplerOverrides.to) || SAMPLER_ADDRESS;
|
||||
// We default the sampler address to be FlashWallet to account for allowances being set on tokens
|
||||
const samplerAddress =
|
||||
(options.samplerOverrides && options.samplerOverrides.to) ||
|
||||
this._contractAddresses.exchangeProxyFlashWallet;
|
||||
const defaultCodeOverrides = samplerBytecode
|
||||
? {
|
||||
[samplerAddress]: { code: samplerBytecode },
|
||||
[samplerAddress]: { code: samplerBytecode, balance: MAX_UINT256 },
|
||||
}
|
||||
: {};
|
||||
const samplerOverrides = _.assign(
|
||||
@@ -198,6 +204,7 @@ export class SwapQuoter {
|
||||
const optimizerResults = await this._marketOperationUtils.getBatchMarketBuyOrdersAsync(
|
||||
allOrders,
|
||||
makerTokenBuyAmounts,
|
||||
gasPrice,
|
||||
opts as GetMarketOrdersOpts,
|
||||
);
|
||||
|
||||
@@ -212,7 +219,6 @@ export class SwapQuoter {
|
||||
MarketOperation.Buy,
|
||||
makerTokenBuyAmounts[i],
|
||||
gasPrice,
|
||||
opts.gasSchedule,
|
||||
opts.bridgeSlippage,
|
||||
);
|
||||
} else {
|
||||
@@ -268,6 +274,7 @@ export class SwapQuoter {
|
||||
output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount,
|
||||
fillData: o,
|
||||
source: ERC20BridgeSource.Native,
|
||||
gasUsed: NATIVE_LIMIT_ORDER_GAS_USED,
|
||||
};
|
||||
}),
|
||||
];
|
||||
@@ -359,10 +366,8 @@ export class SwapQuoter {
|
||||
const cloneOpts = _.omit(opts, 'gasPrice') as GetMarketOrdersOpts;
|
||||
const calcOpts: GetMarketOrdersOpts = {
|
||||
...cloneOpts,
|
||||
feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData: FillData) =>
|
||||
gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)),
|
||||
),
|
||||
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
|
||||
gasPrice,
|
||||
};
|
||||
// pass the QuoteRequestor on if rfqt enabled
|
||||
if (calcOpts.rfqt !== undefined) {
|
||||
@@ -391,7 +396,6 @@ export class SwapQuoter {
|
||||
marketOperation,
|
||||
assetFillAmount,
|
||||
gasPrice,
|
||||
opts.gasSchedule,
|
||||
opts.bridgeSlippage,
|
||||
);
|
||||
|
||||
@@ -494,7 +498,6 @@ function createSwapQuote(
|
||||
operation: MarketOperation,
|
||||
assetFillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
gasSchedule: FeeSchedule,
|
||||
slippage: number,
|
||||
): SwapQuote {
|
||||
const {
|
||||
@@ -509,8 +512,8 @@ function createSwapQuote(
|
||||
|
||||
// Calculate quote info
|
||||
const { bestCaseQuoteInfo, worstCaseQuoteInfo, sourceBreakdown } = isTwoHop
|
||||
? calculateTwoHopQuoteInfo(optimizedOrders, operation, gasSchedule, slippage)
|
||||
: calculateQuoteInfo(optimizedOrders, operation, assetFillAmount, gasPrice, gasSchedule, slippage);
|
||||
? calculateTwoHopQuoteInfo(optimizedOrders, operation, slippage)
|
||||
: calculateQuoteInfo(optimizedOrders, operation, assetFillAmount, gasPrice, slippage);
|
||||
|
||||
// Put together the swap quote
|
||||
const { makerTokenDecimals, takerTokenDecimals } = optimizerResult.marketSideLiquidity;
|
||||
@@ -551,7 +554,6 @@ function calculateQuoteInfo(
|
||||
operation: MarketOperation,
|
||||
assetFillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
gasSchedule: FeeSchedule,
|
||||
slippage: number,
|
||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
||||
const bestCaseFillResult = simulateBestCaseFill({
|
||||
@@ -559,7 +561,7 @@ function calculateQuoteInfo(
|
||||
orders: optimizedOrders,
|
||||
side: operation,
|
||||
fillAmount: assetFillAmount,
|
||||
opts: { gasSchedule },
|
||||
opts: {},
|
||||
});
|
||||
|
||||
const worstCaseFillResult = simulateWorstCaseFill({
|
||||
@@ -567,7 +569,7 @@ function calculateQuoteInfo(
|
||||
orders: optimizedOrders,
|
||||
side: operation,
|
||||
fillAmount: assetFillAmount,
|
||||
opts: { gasSchedule, slippage },
|
||||
opts: { slippage },
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -580,18 +582,12 @@ function calculateQuoteInfo(
|
||||
function calculateTwoHopQuoteInfo(
|
||||
optimizedOrders: OptimizedMarketOrder[],
|
||||
operation: MarketOperation,
|
||||
gasSchedule: FeeSchedule,
|
||||
slippage: number,
|
||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
||||
const [firstHopOrder, secondHopOrder] = optimizedOrders;
|
||||
const [firstHopFill] = firstHopOrder.fills;
|
||||
const [secondHopFill] = secondHopOrder.fills;
|
||||
const gas = new BigNumber(
|
||||
gasSchedule[ERC20BridgeSource.MultiHop]!({
|
||||
firstHopSource: _.pick(firstHopFill, 'source', 'fillData'),
|
||||
secondHopSource: _.pick(secondHopFill, 'source', 'fillData'),
|
||||
}),
|
||||
).toNumber();
|
||||
const gas = (firstHopOrder.gasUsed || ZERO_AMOUNT).plus(secondHopFill.gasUsed || ZERO_AMOUNT).toNumber();
|
||||
return {
|
||||
bestCaseQuoteInfo: {
|
||||
makerAmount: operation === MarketOperation.Sell ? secondHopFill.output : secondHopFill.input,
|
||||
|
@@ -256,7 +256,6 @@ export interface RfqRequestOpts {
|
||||
* gasPrice: gas price to determine protocolFee amount, default to ethGasStation fast amount
|
||||
*/
|
||||
export interface SwapQuoteRequestOpts extends GetMarketOrdersOpts {
|
||||
gasPrice?: BigNumber;
|
||||
rfqt?: RfqRequestOpts;
|
||||
}
|
||||
|
||||
|
@@ -1,19 +1,11 @@
|
||||
import { Web3Wrapper } from '@0x/dev-utils';
|
||||
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { MarketOperation } from '../../types';
|
||||
|
||||
import { COMPARISON_PRICE_DECIMALS, SOURCE_FLAGS } from './constants';
|
||||
import {
|
||||
ComparisonPrice,
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
FeeEstimate,
|
||||
FeeSchedule,
|
||||
MarketSideLiquidity,
|
||||
} from './types';
|
||||
import { COMPARISON_PRICE_DECIMALS, NATIVE_RFQT_GAS_USED, SOURCE_FLAGS } from './constants';
|
||||
import { ComparisonPrice, ExchangeProxyOverhead, MarketSideLiquidity } from './types';
|
||||
|
||||
/**
|
||||
* Takes in an optimizer response and returns a price for RFQT MMs to beat
|
||||
@@ -22,42 +14,21 @@ import {
|
||||
* @param adjustedRate the adjusted rate (accounting for fees) from the optimizer, maker/taker
|
||||
* @param amount the amount specified by the client
|
||||
* @param marketSideLiquidity the results from querying liquidity sources
|
||||
* @param feeSchedule the fee schedule passed to the Optimizer
|
||||
* @param gasPrice the gas price used to calculate costs
|
||||
* @return ComparisonPrice object with the prices for RFQ MMs to beat
|
||||
*/
|
||||
export function getComparisonPrices(
|
||||
adjustedRate: BigNumber,
|
||||
amount: BigNumber,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
feeSchedule: FeeSchedule,
|
||||
gasPrice: BigNumber,
|
||||
exchangeProxyOverhead: ExchangeProxyOverhead,
|
||||
): ComparisonPrice {
|
||||
let wholeOrder: BigNumber | undefined;
|
||||
let feeInEth: BigNumber | number;
|
||||
|
||||
// HACK: get the fee penalty of a single 0x native order
|
||||
// The FeeSchedule function takes in a `FillData` object and returns a fee estimate in ETH
|
||||
// We don't have fill data here, we just want the cost of a single native order, so we pass in undefined
|
||||
// This works because the feeSchedule returns a constant for Native orders, this will need
|
||||
// to be tweaked if the feeSchedule for native orders uses the fillData passed in
|
||||
// 2 potential issues: there is no native fee schedule or the fee schedule depends on fill data
|
||||
if (feeSchedule[ERC20BridgeSource.Native] === undefined) {
|
||||
logUtils.warn('ComparisonPrice function did not find native order fee schedule');
|
||||
|
||||
return { wholeOrder };
|
||||
} else {
|
||||
try {
|
||||
const fillFeeInEth = new BigNumber(
|
||||
(feeSchedule[ERC20BridgeSource.Native] as FeeEstimate)({ type: FillQuoteTransformerOrderType.Rfq }),
|
||||
);
|
||||
const exchangeProxyOverheadInEth = new BigNumber(exchangeProxyOverhead(SOURCE_FLAGS.RfqOrder));
|
||||
feeInEth = fillFeeInEth.plus(exchangeProxyOverheadInEth);
|
||||
} catch {
|
||||
logUtils.warn('Native order fee schedule requires fill data');
|
||||
|
||||
return { wholeOrder };
|
||||
}
|
||||
}
|
||||
// Assuming EP overhead has been gas price adjusted
|
||||
feeInEth = NATIVE_RFQT_GAS_USED.times(gasPrice).plus(exchangeProxyOverhead(SOURCE_FLAGS.RfqOrder));
|
||||
|
||||
// Calc native order fee penalty in output unit (maker units for sells, taker unit for buys)
|
||||
const feePenalty = !marketSideLiquidity.outputAmountPerEth.isZero()
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { formatBytes32String } from '@ethersproject/strings';
|
||||
|
||||
@@ -7,25 +6,15 @@ import { TokenAdjacencyGraphBuilder } from '../token_adjacency_graph_builder';
|
||||
|
||||
import { SourceFilters } from './source_filters';
|
||||
import {
|
||||
BancorFillData,
|
||||
CurveFillData,
|
||||
CurveFunctionSelectors,
|
||||
CurveInfo,
|
||||
DODOFillData,
|
||||
ERC20BridgeSource,
|
||||
FeeSchedule,
|
||||
FillData,
|
||||
GetMarketOrdersOpts,
|
||||
KyberSamplerOpts,
|
||||
LidoInfo,
|
||||
LiquidityProviderFillData,
|
||||
LiquidityProviderRegistry,
|
||||
MakerPsmFillData,
|
||||
MultiHopFillData,
|
||||
PsmInfo,
|
||||
TokenAdjacencyGraph,
|
||||
UniswapV2FillData,
|
||||
UniswapV3FillData,
|
||||
} from './types';
|
||||
|
||||
// tslint:disable: custom-no-magic-numbers no-bitwise
|
||||
@@ -41,7 +30,7 @@ export const ONE_HOUR_IN_SECONDS = 60 * 60;
|
||||
export const ONE_SECOND_MS = 1000;
|
||||
export const NULL_BYTES = '0x';
|
||||
export const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||
export const SAMPLER_ADDRESS = '0x5555555555555555555555555555555555555555';
|
||||
export const SAMPLER_ADDRESS = '0x5555555555555555555555555555555555555556';
|
||||
export const COMPARISON_PRICE_DECIMALS = 10;
|
||||
|
||||
// TODO(kimpers): Consolidate this implementation with the one in @0x/token-metadata
|
||||
@@ -146,6 +135,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Polydex,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.FirebirdOneSwap,
|
||||
ERC20BridgeSource.Booster,
|
||||
]),
|
||||
},
|
||||
new SourceFilters([]),
|
||||
@@ -237,6 +227,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Polydex,
|
||||
ERC20BridgeSource.ApeSwap,
|
||||
ERC20BridgeSource.FirebirdOneSwap,
|
||||
ERC20BridgeSource.Booster,
|
||||
]),
|
||||
},
|
||||
new SourceFilters([]),
|
||||
@@ -487,6 +478,22 @@ export const FIREBIRDONESWAP_POLYGON_POOLS = {
|
||||
oneswap: '0x01c9475dbd36e46d1961572c8de24b74616bae9e',
|
||||
};
|
||||
|
||||
export const BOOSTER_POLYGON_POOLS = valueByChainId<Array<{ poolAddress: string; tokens: string[] }>>(
|
||||
{
|
||||
[ChainId.Polygon]: [
|
||||
{
|
||||
poolAddress: '0x7f01a853e857db87aad498ee2b640b5dbcda4fb9',
|
||||
tokens: [POLYGON_TOKENS.USDC, POLYGON_TOKENS.WETH],
|
||||
},
|
||||
{
|
||||
poolAddress: '0x43a8e6954384521d6a6d482f7dce7f849fd95cdc',
|
||||
tokens: [POLYGON_TOKENS.WMATIC, POLYGON_TOKENS.WETH],
|
||||
},
|
||||
],
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
export const DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID = valueByChainId<string[]>(
|
||||
{
|
||||
[ChainId.Mainnet]: [
|
||||
@@ -511,16 +518,16 @@ export const DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID = valueByChainId<string[]>(
|
||||
'0x07865c6e87b9f70255377e024ace6630c1eaa37f', // USDC
|
||||
],
|
||||
[ChainId.Polygon]: [
|
||||
POLYGON_TOKENS.WMATIC,
|
||||
POLYGON_TOKENS.WETH,
|
||||
POLYGON_TOKENS.USDC,
|
||||
POLYGON_TOKENS.DAI,
|
||||
POLYGON_TOKENS.USDT,
|
||||
POLYGON_TOKENS.WBTC,
|
||||
POLYGON_TOKENS.QUICK,
|
||||
POLYGON_TOKENS.DFYN,
|
||||
POLYGON_TOKENS.BANANA,
|
||||
POLYGON_TOKENS.WEXPOLY,
|
||||
// POLYGON_TOKENS.WMATIC,
|
||||
// POLYGON_TOKENS.WETH,
|
||||
// POLYGON_TOKENS.USDC,
|
||||
// POLYGON_TOKENS.DAI,
|
||||
// POLYGON_TOKENS.USDT,
|
||||
// POLYGON_TOKENS.WBTC,
|
||||
// POLYGON_TOKENS.QUICK,
|
||||
// POLYGON_TOKENS.DFYN,
|
||||
// POLYGON_TOKENS.BANANA,
|
||||
// POLYGON_TOKENS.WEXPOLY,
|
||||
],
|
||||
},
|
||||
[],
|
||||
@@ -577,8 +584,6 @@ const CURVE_POLYGON_ATRICRYPTO_TOKENS = [POLYGON_TOKENS.amDAI, POLYGON_TOKENS.am
|
||||
|
||||
const createCurveExchangePool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.exchange,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
tokens: info.tokens,
|
||||
metaTokens: undefined,
|
||||
poolAddress: info.pool,
|
||||
@@ -587,8 +592,6 @@ const createCurveExchangePool = (info: { tokens: string[]; pool: string; gasSche
|
||||
|
||||
const createCurveExchangeUnderlyingPool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
tokens: info.tokens,
|
||||
metaTokens: undefined,
|
||||
poolAddress: info.pool,
|
||||
@@ -597,8 +600,6 @@ const createCurveExchangeUnderlyingPool = (info: { tokens: string[]; pool: strin
|
||||
|
||||
const createCurveMetaTriPool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
tokens: [...info.tokens, ...CURVE_TRI_POOL_MAINNET_TOKENS],
|
||||
metaTokens: info.tokens,
|
||||
poolAddress: info.pool,
|
||||
@@ -607,8 +608,6 @@ const createCurveMetaTriPool = (info: { tokens: string[]; pool: string; gasSched
|
||||
|
||||
const createCurveMetaTriBtcPool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
tokens: [...info.tokens, ...CURVE_TRI_BTC_POOL_TOKEN],
|
||||
metaTokens: info.tokens,
|
||||
poolAddress: info.pool,
|
||||
@@ -617,8 +616,6 @@ const createCurveMetaTriBtcPool = (info: { tokens: string[]; pool: string; gasSc
|
||||
|
||||
const createCurveExchangeV2Pool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.exchange_v2,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_v2,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
tokens: info.tokens,
|
||||
metaTokens: undefined,
|
||||
poolAddress: info.pool,
|
||||
@@ -627,8 +624,6 @@ const createCurveExchangeV2Pool = (info: { tokens: string[]; pool: string; gasSc
|
||||
|
||||
const createCurveV2MetaTriPool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying_v2,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying_v2,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
tokens: [...CURVE_POLYGON_ATRICRYPTO_UNDERLYING_TOKENS, ...info.tokens],
|
||||
metaTokens: info.tokens,
|
||||
poolAddress: info.pool,
|
||||
@@ -910,8 +905,6 @@ export const XSIGMA_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
||||
export const SADDLE_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
||||
[SADDLE_POOLS.stables]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.swap,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
poolAddress: SADDLE_POOLS.stables,
|
||||
tokens: [MAINNET_TOKENS.DAI, MAINNET_TOKENS.USDC, MAINNET_TOKENS.USDT],
|
||||
metaTokens: undefined,
|
||||
@@ -919,8 +912,6 @@ export const SADDLE_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
||||
},
|
||||
[SADDLE_POOLS.bitcoins]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.swap,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
poolAddress: SADDLE_POOLS.bitcoins,
|
||||
tokens: [MAINNET_TOKENS.tBTC, MAINNET_TOKENS.WBTC, MAINNET_TOKENS.RenBTC, MAINNET_TOKENS.sBTC],
|
||||
metaTokens: undefined,
|
||||
@@ -931,8 +922,6 @@ export const SADDLE_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
||||
export const SMOOTHY_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
||||
[SMOOTHY_POOLS.syUSD]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.swap_uint256,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_swap_amount,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
poolAddress: SMOOTHY_POOLS.syUSD,
|
||||
tokens: [
|
||||
MAINNET_TOKENS.USDT,
|
||||
@@ -952,8 +941,6 @@ export const SMOOTHY_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
||||
export const SMOOTHY_BSC_INFOS: { [name: string]: CurveInfo } = {
|
||||
[SMOOTHY_POOLS.syUSD]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.swap_uint256,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_swap_amount,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
poolAddress: SMOOTHY_POOLS.syUSD,
|
||||
tokens: [BSC_TOKENS.BUSD, BSC_TOKENS.USDT, BSC_TOKENS.USDC, BSC_TOKENS.DAI, BSC_TOKENS.PAX, BSC_TOKENS.UST],
|
||||
metaTokens: undefined,
|
||||
@@ -964,8 +951,6 @@ export const SMOOTHY_BSC_INFOS: { [name: string]: CurveInfo } = {
|
||||
export const NERVE_BSC_INFOS: { [name: string]: CurveInfo } = {
|
||||
[NERVE_POOLS.threePool]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.swap,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
poolAddress: NERVE_POOLS.threePool,
|
||||
tokens: [BSC_TOKENS.BUSD, BSC_TOKENS.USDT, BSC_TOKENS.USDC],
|
||||
metaTokens: undefined,
|
||||
@@ -976,8 +961,6 @@ export const NERVE_BSC_INFOS: { [name: string]: CurveInfo } = {
|
||||
export const FIREBIRDONESWAP_BSC_INFOS: { [name: string]: CurveInfo } = {
|
||||
[FIREBIRDONESWAP_BSC_POOLS.oneswap]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.swap,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
poolAddress: FIREBIRDONESWAP_BSC_POOLS.oneswap,
|
||||
tokens: [BSC_TOKENS.BUSD, BSC_TOKENS.USDT, BSC_TOKENS.DAI, BSC_TOKENS.USDC],
|
||||
metaTokens: undefined,
|
||||
@@ -988,8 +971,6 @@ export const FIREBIRDONESWAP_BSC_INFOS: { [name: string]: CurveInfo } = {
|
||||
export const FIREBIRDONESWAP_POLYGON_INFOS: { [name: string]: CurveInfo } = {
|
||||
[FIREBIRDONESWAP_POLYGON_POOLS.oneswap]: {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors.swap,
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap,
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
||||
poolAddress: FIREBIRDONESWAP_POLYGON_POOLS.oneswap,
|
||||
tokens: [POLYGON_TOKENS.DAI, POLYGON_TOKENS.USDC, POLYGON_TOKENS.USDT],
|
||||
metaTokens: undefined,
|
||||
@@ -1141,11 +1122,7 @@ export const KYBER_DMM_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
|
||||
export const MOONISWAP_REGISTRIES_BY_CHAIN_ID = valueByChainId(
|
||||
{
|
||||
[ChainId.Mainnet]: [
|
||||
'0x71CD6666064C3A1354a3B4dca5fA1E2D3ee7D303',
|
||||
'0xc4a8b7e29e3c8ec560cd4945c1cf3461a85a148d',
|
||||
'0xbaf9a5d4b0052359326a6cdab54babaa3a3a9643',
|
||||
],
|
||||
[ChainId.Mainnet]: ['0xbaf9a5d4b0052359326a6cdab54babaa3a3a9643'],
|
||||
[ChainId.BSC]: ['0xd41b24bba51fac0e4827b6f94c0d6ddeb183cd64'],
|
||||
},
|
||||
[] as string[],
|
||||
@@ -1411,132 +1388,8 @@ export const POLYDEX_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
const uniswapV2CloneGasSchedule = (fillData?: FillData) => {
|
||||
// TODO: Different base cost if to/from ETH.
|
||||
let gas = 90e3;
|
||||
const path = (fillData as UniswapV2FillData).tokenAddressPath;
|
||||
if (path.length > 2) {
|
||||
gas += (path.length - 2) * 60e3; // +60k for each hop.
|
||||
}
|
||||
return gas;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculated gross gas cost of the underlying exchange.
|
||||
* The cost of switching from one source to another, assuming
|
||||
* we are in the middle of a transaction.
|
||||
* I.e remove the overhead cost of ExchangeProxy (130k) and
|
||||
* the ethereum transaction cost (21k)
|
||||
*/
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
[ERC20BridgeSource.Native]: fillData => {
|
||||
// TODO jacob re-order imports so there is no circular rependency with SignedNativeOrder
|
||||
const nativeFillData = fillData as { type: FillQuoteTransformerOrderType };
|
||||
return nativeFillData && nativeFillData.type === FillQuoteTransformerOrderType.Limit
|
||||
? PROTOCOL_FEE_MULTIPLIER.plus(100e3).toNumber()
|
||||
: // TODO jacob revisit wth v4 LimitOrders
|
||||
100e3;
|
||||
},
|
||||
[ERC20BridgeSource.Uniswap]: () => 90e3,
|
||||
[ERC20BridgeSource.LiquidityProvider]: fillData => {
|
||||
return (fillData as LiquidityProviderFillData).gasCost || 100e3;
|
||||
},
|
||||
[ERC20BridgeSource.Eth2Dai]: () => 400e3,
|
||||
[ERC20BridgeSource.Kyber]: () => 450e3,
|
||||
[ERC20BridgeSource.Curve]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.CurveV2]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.Swerve]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.SnowSwap]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.Nerve]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.Belt]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.Ellipsis]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.Smoothy]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.Saddle]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.XSigma]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.FirebirdOneSwap]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||
[ERC20BridgeSource.MultiBridge]: () => 350e3,
|
||||
[ERC20BridgeSource.UniswapV2]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.SushiSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.CryptoCom]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Linkswap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Balancer]: () => 120e3,
|
||||
[ERC20BridgeSource.BalancerV2]: () => 100e3,
|
||||
[ERC20BridgeSource.Cream]: () => 120e3,
|
||||
[ERC20BridgeSource.MStable]: () => 200e3,
|
||||
[ERC20BridgeSource.MakerPsm]: (fillData?: FillData) => {
|
||||
const psmFillData = fillData as MakerPsmFillData;
|
||||
return psmFillData.takerToken === psmFillData.gemTokenAddress ? 210e3 : 290e3;
|
||||
},
|
||||
[ERC20BridgeSource.Mooniswap]: () => 130e3,
|
||||
[ERC20BridgeSource.Shell]: () => 170e3,
|
||||
[ERC20BridgeSource.Component]: () => 188e3,
|
||||
[ERC20BridgeSource.MultiHop]: (fillData?: FillData) => {
|
||||
const firstHop = (fillData as MultiHopFillData).firstHopSource;
|
||||
const secondHop = (fillData as MultiHopFillData).secondHopSource;
|
||||
const firstHopGas = DEFAULT_GAS_SCHEDULE[firstHop.source](firstHop.fillData);
|
||||
const secondHopGas = DEFAULT_GAS_SCHEDULE[secondHop.source](secondHop.fillData);
|
||||
return new BigNumber(firstHopGas)
|
||||
.plus(secondHopGas)
|
||||
.plus(30e3)
|
||||
.toNumber();
|
||||
},
|
||||
[ERC20BridgeSource.Dodo]: (fillData?: FillData) => {
|
||||
const isSellBase = (fillData as DODOFillData).isSellBase;
|
||||
// Sell base is cheaper as it is natively supported
|
||||
// sell quote requires additional calculation and overhead
|
||||
return isSellBase ? 180e3 : 300e3;
|
||||
},
|
||||
[ERC20BridgeSource.DodoV2]: (_fillData?: FillData) => 100e3,
|
||||
[ERC20BridgeSource.Bancor]: (fillData?: FillData) => {
|
||||
let gas = 200e3;
|
||||
const path = (fillData as BancorFillData).path;
|
||||
if (path.length > 2) {
|
||||
gas += (path.length - 2) * 60e3; // +60k for each hop.
|
||||
}
|
||||
return gas;
|
||||
},
|
||||
[ERC20BridgeSource.KyberDmm]: (fillData?: FillData) => {
|
||||
// TODO: Different base cost if to/from ETH.
|
||||
let gas = 95e3;
|
||||
const path = (fillData as UniswapV2FillData).tokenAddressPath;
|
||||
if (path.length > 2) {
|
||||
gas += (path.length - 2) * 65e3; // +65k for each hop.
|
||||
}
|
||||
return gas;
|
||||
},
|
||||
[ERC20BridgeSource.UniswapV3]: (fillData?: FillData) => {
|
||||
let gas = 100e3;
|
||||
const path = (fillData as UniswapV3FillData).tokenAddressPath;
|
||||
if (path.length > 2) {
|
||||
gas += (path.length - 2) * 32e3; // +32k for each hop.
|
||||
}
|
||||
return gas;
|
||||
},
|
||||
[ERC20BridgeSource.Lido]: () => 226e3,
|
||||
|
||||
//
|
||||
// BSC
|
||||
//
|
||||
[ERC20BridgeSource.PancakeSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.PancakeSwapV2]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.BakerySwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.ApeSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.CafeSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.CheeseSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.JulSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.WaultSwap]: uniswapV2CloneGasSchedule,
|
||||
|
||||
//
|
||||
// Polygon
|
||||
//
|
||||
[ERC20BridgeSource.QuickSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.ComethSwap]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Dfyn]: uniswapV2CloneGasSchedule,
|
||||
[ERC20BridgeSource.Polydex]: uniswapV2CloneGasSchedule,
|
||||
};
|
||||
|
||||
export const DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = { ...DEFAULT_GAS_SCHEDULE };
|
||||
export const NATIVE_RFQT_GAS_USED = new BigNumber(100e3);
|
||||
export const NATIVE_LIMIT_ORDER_GAS_USED = NATIVE_RFQT_GAS_USED.plus(PROTOCOL_FEE_MULTIPLIER);
|
||||
|
||||
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(20000);
|
||||
|
||||
@@ -1552,11 +1405,10 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
||||
maxFallbackSlippage: 0.05,
|
||||
numSamples: 13,
|
||||
sampleDistributionBase: 1.05,
|
||||
feeSchedule: DEFAULT_FEE_SCHEDULE,
|
||||
gasSchedule: DEFAULT_GAS_SCHEDULE,
|
||||
exchangeProxyOverhead: () => ZERO_AMOUNT,
|
||||
allowFallback: true,
|
||||
shouldGenerateQuoteReport: true,
|
||||
shouldIncludePriceComparisonsReport: false,
|
||||
tokenAdjacencyGraph: { default: [] },
|
||||
gasPrice: new BigNumber(1e9),
|
||||
};
|
||||
|
@@ -3,8 +3,14 @@ import { BigNumber, hexUtils } from '@0x/utils';
|
||||
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||
|
||||
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
|
||||
import {
|
||||
NATIVE_LIMIT_ORDER_GAS_USED,
|
||||
NATIVE_RFQT_GAS_USED,
|
||||
POSITIVE_INF,
|
||||
SOURCE_FLAGS,
|
||||
ZERO_AMOUNT,
|
||||
} from './constants';
|
||||
import { DexSample, ERC20BridgeSource, Fill } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||
|
||||
@@ -13,17 +19,16 @@ import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
|
||||
*/
|
||||
export function createFills(opts: {
|
||||
side: MarketOperation;
|
||||
gasPrice: BigNumber;
|
||||
orders?: NativeOrderWithFillableAmounts[];
|
||||
dexQuotes?: DexSample[][];
|
||||
targetInput?: BigNumber;
|
||||
outputAmountPerEth?: BigNumber;
|
||||
inputAmountPerEth?: BigNumber;
|
||||
excludedSources?: ERC20BridgeSource[];
|
||||
feeSchedule?: FeeSchedule;
|
||||
}): Fill[][] {
|
||||
const { side } = opts;
|
||||
const excludedSources = opts.excludedSources || [];
|
||||
const feeSchedule = opts.feeSchedule || {};
|
||||
const orders = opts.orders || [];
|
||||
const dexQuotes = opts.dexQuotes || [];
|
||||
const outputAmountPerEth = opts.outputAmountPerEth || ZERO_AMOUNT;
|
||||
@@ -35,11 +40,11 @@ export function createFills(opts: {
|
||||
opts.targetInput,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
feeSchedule,
|
||||
opts.gasPrice,
|
||||
);
|
||||
// Create DEX fills.
|
||||
const dexFills = dexQuotes.map(singleSourceSamples =>
|
||||
dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, feeSchedule),
|
||||
dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, opts.gasPrice),
|
||||
);
|
||||
return [...dexFills, nativeFills]
|
||||
.map(p => clipFillsToInput(p, opts.targetInput))
|
||||
@@ -77,7 +82,7 @@ function nativeOrdersToFills(
|
||||
targetInput: BigNumber = POSITIVE_INF,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
gasPrice: BigNumber,
|
||||
): Fill[] {
|
||||
const sourcePathId = hexUtils.random();
|
||||
// Create a single path from all orders.
|
||||
@@ -88,10 +93,12 @@ function nativeOrdersToFills(
|
||||
const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
|
||||
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
||||
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
||||
const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o);
|
||||
const gasUsed =
|
||||
o.type === FillQuoteTransformerOrderType.Limit ? NATIVE_LIMIT_ORDER_GAS_USED : NATIVE_RFQT_GAS_USED;
|
||||
const feeInEth = gasUsed.times(gasPrice);
|
||||
const outputPenalty = !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(fee)
|
||||
: inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input));
|
||||
? outputAmountPerEth.times(feeInEth)
|
||||
: inputAmountPerEth.times(feeInEth).times(output.dividedToIntegerBy(input));
|
||||
// targetInput can be less than the order size
|
||||
// whilst the penalty is constant, it affects the adjusted output
|
||||
// only up until the target has been exhausted.
|
||||
@@ -104,6 +111,7 @@ function nativeOrdersToFills(
|
||||
side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty);
|
||||
const adjustedRate =
|
||||
side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput);
|
||||
|
||||
// Skip orders with rates that are <= 0.
|
||||
if (adjustedRate.lte(0)) {
|
||||
continue;
|
||||
@@ -119,6 +127,7 @@ function nativeOrdersToFills(
|
||||
parent: undefined, // TBD
|
||||
source: ERC20BridgeSource.Native,
|
||||
type,
|
||||
gasUsed,
|
||||
fillData: { ...o },
|
||||
});
|
||||
}
|
||||
@@ -137,7 +146,7 @@ function dexSamplesToFills(
|
||||
samples: DexSample[],
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
gasPrice: BigNumber,
|
||||
): Fill[] {
|
||||
const sourcePathId = hexUtils.random();
|
||||
const fills: Fill[] = [];
|
||||
@@ -152,7 +161,12 @@ function dexSamplesToFills(
|
||||
const { source, fillData } = sample;
|
||||
const input = sample.input.minus(prevSample ? prevSample.input : 0);
|
||||
const output = sample.output.minus(prevSample ? prevSample.output : 0);
|
||||
const fee = fees[source] === undefined ? 0 : fees[source]!(sample.fillData) || 0;
|
||||
|
||||
if (!sample.gasUsed || sample.gasUsed.isZero()) {
|
||||
throw new Error(`${sample.source} gas used missing or 0`);
|
||||
}
|
||||
const fee = gasPrice.times(sample.gasUsed);
|
||||
|
||||
let penalty = ZERO_AMOUNT;
|
||||
if (i === 0) {
|
||||
// Only the first fill in a DEX path incurs a penalty.
|
||||
@@ -173,6 +187,7 @@ function dexSamplesToFills(
|
||||
index: i,
|
||||
parent: i !== 0 ? fills[fills.length - 1] : undefined,
|
||||
flags: SOURCE_FLAGS[source],
|
||||
gasUsed: sample.gasUsed,
|
||||
});
|
||||
}
|
||||
return fills;
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { FillQuoteTransformerOrderType, RfqOrder } from '@0x/protocol-utils';
|
||||
import { BigNumber, NULL_ADDRESS } from '@0x/utils';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts } from '../..';
|
||||
import { DEFAULT_INFO_LOGGER, INVALID_SIGNATURE } from '../../constants';
|
||||
import {
|
||||
AssetSwapperContractAddresses,
|
||||
@@ -36,7 +38,7 @@ import {
|
||||
ZERO_AMOUNT,
|
||||
} from './constants';
|
||||
import { createFills } from './fills';
|
||||
import { getBestTwoHopQuote } from './multihop_utils';
|
||||
import { getBestTwoHopQuote, getIntermediateTokens } from './multihop_utils';
|
||||
import { createOrdersFromTwoHopSample } from './orders';
|
||||
import { PathPenaltyOpts } from './path';
|
||||
import { fillsToSortedPaths, findOptimalPathAsync } from './path_optimizer';
|
||||
@@ -50,11 +52,21 @@ import {
|
||||
GenerateOptimizedOrdersOpts,
|
||||
GetMarketOrdersOpts,
|
||||
MarketSideLiquidity,
|
||||
MultiHopFillData,
|
||||
OptimizerResult,
|
||||
OptimizerResultWithReport,
|
||||
OrderDomain,
|
||||
} from './types';
|
||||
|
||||
const HACKED_ERC20_BYTECODE = _.get(artifacts.HackedERC20, 'compilerOutput.evm.deployedBytecode.object');
|
||||
const GAS_OVERHEAD_BYTECODE = _.get(artifacts.GasOverhead, 'compilerOutput.evm.deployedBytecode.object');
|
||||
const DELEGEATE_HACKED_ERC20_BYTECODE = _.get(
|
||||
artifacts.DelegateHackedERC20,
|
||||
'compilerOutput.evm.deployedBytecode.object',
|
||||
);
|
||||
const HACKED_ERC20_ADDRESS = '0xDEf1000000000000000000000000000000DE7d37';
|
||||
const GAS_OVERHEAD_ADDRESS = '0xDeF1000000000000000000000000000000001337';
|
||||
|
||||
// tslint:disable:boolean-naming
|
||||
|
||||
export class MarketOperationUtils {
|
||||
@@ -63,6 +75,7 @@ export class MarketOperationUtils {
|
||||
private readonly _feeSources: SourceFilters;
|
||||
private readonly _nativeFeeToken: string;
|
||||
private readonly _nativeFeeTokenAmount: BigNumber;
|
||||
private readonly _contractCodeByAddress: { [address: string]: string | undefined } = {};
|
||||
|
||||
private static _computeQuoteReport(
|
||||
quoteRequestor: QuoteRequestor | undefined,
|
||||
@@ -131,34 +144,48 @@ export class MarketOperationUtils {
|
||||
// Used to determine whether the tx origin is an EOA or a contract
|
||||
const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
|
||||
|
||||
const { overrides } = await this._fetchTokenOverridesAsync(takerToken, makerToken);
|
||||
// Call the sampler contract.
|
||||
const samplerPromise = this._sampler.executeAsync(
|
||||
this._sampler.getTokenDecimals([makerToken, takerToken]),
|
||||
// Get native order fillable amounts.
|
||||
this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||
// Get ETH -> maker token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
feeSourceFilters.sources,
|
||||
makerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
),
|
||||
// Get ETH -> taker token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
feeSourceFilters.sources,
|
||||
takerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
),
|
||||
// Get sell quotes for taker -> maker.
|
||||
this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||
this._sampler.getTwoHopSellQuotes(
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerAmount,
|
||||
),
|
||||
this._sampler.isAddressContract(txOrigin),
|
||||
const samplerPromise = this._sampler.executeBatchAsync(
|
||||
[
|
||||
this._sampler.getTokenDecimals([makerToken, takerToken]),
|
||||
// Get native order fillable amounts.
|
||||
this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||
// Get ETH -> maker token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
feeSourceFilters.sources,
|
||||
makerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
),
|
||||
// Get ETH -> taker token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
feeSourceFilters.sources,
|
||||
takerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
),
|
||||
// Get sell quotes for taker -> maker.
|
||||
this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||
this._sampler.isAddressContract(txOrigin),
|
||||
],
|
||||
{
|
||||
overrides,
|
||||
},
|
||||
);
|
||||
// Perform the MultiHop sell quotes in a separate request
|
||||
const multiHopsamplerPromise = this._sampler.executeBatchAsync(
|
||||
[
|
||||
this._sampler.getTwoHopSellQuotes(
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerAmount,
|
||||
),
|
||||
],
|
||||
{
|
||||
overrides,
|
||||
},
|
||||
);
|
||||
|
||||
// Refresh the cached pools asynchronously if required
|
||||
@@ -171,16 +198,25 @@ export class MarketOperationUtils {
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
dexQuotes,
|
||||
rawTwoHopQuotes,
|
||||
isTxOriginContract,
|
||||
],
|
||||
] = await Promise.all([samplerPromise]);
|
||||
[rawTwoHopQuotes],
|
||||
] = await Promise.all([samplerPromise, multiHopsamplerPromise]);
|
||||
|
||||
// Filter out any invalid two hop quotes where we couldn't find a route
|
||||
const twoHopQuotes = rawTwoHopQuotes.filter(
|
||||
q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource,
|
||||
(q: DexSample<MultiHopFillData>) =>
|
||||
q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource,
|
||||
);
|
||||
|
||||
console.log({
|
||||
tokenDecimals,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
dexQuotes,
|
||||
rawTwoHopQuotes,
|
||||
});
|
||||
|
||||
const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
|
||||
|
||||
const isRfqSupported = !!(_opts.rfqt && !isTxOriginContract);
|
||||
@@ -232,35 +268,42 @@ export class MarketOperationUtils {
|
||||
// Used to determine whether the tx origin is an EOA or a contract
|
||||
const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
|
||||
|
||||
const { overrides } = await this._fetchTokenOverridesAsync(takerToken, makerToken);
|
||||
// Call the sampler contract.
|
||||
const samplerPromise = this._sampler.executeAsync(
|
||||
this._sampler.getTokenDecimals([makerToken, takerToken]),
|
||||
// Get native order fillable amounts.
|
||||
this._sampler.getLimitOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||
// Get ETH -> makerToken token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
feeSourceFilters.sources,
|
||||
makerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
),
|
||||
// Get ETH -> taker token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
feeSourceFilters.sources,
|
||||
takerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
),
|
||||
// Get buy quotes for taker -> maker.
|
||||
this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||
const samplerPromise = this._sampler.executeBatchAsync(
|
||||
[
|
||||
this._sampler.getTokenDecimals([makerToken, takerToken]),
|
||||
// Get native order fillable amounts.
|
||||
this._sampler.getLimitOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||
// Get ETH -> makerToken token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
feeSourceFilters.sources,
|
||||
makerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
),
|
||||
// Get ETH -> taker token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
feeSourceFilters.sources,
|
||||
takerToken,
|
||||
this._nativeFeeToken,
|
||||
this._nativeFeeTokenAmount,
|
||||
),
|
||||
// Get buy quotes for taker -> maker.
|
||||
this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||
this._sampler.isAddressContract(txOrigin),
|
||||
],
|
||||
{ overrides },
|
||||
);
|
||||
|
||||
const multiHopPromise = this._sampler.executeBatchAsync([
|
||||
this._sampler.getTwoHopBuyQuotes(
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerAmount,
|
||||
),
|
||||
this._sampler.isAddressContract(txOrigin),
|
||||
);
|
||||
]);
|
||||
|
||||
// Refresh the cached pools asynchronously if required
|
||||
void this._refreshPoolCacheIfRequiredAsync(takerToken, makerToken);
|
||||
@@ -272,14 +315,15 @@ export class MarketOperationUtils {
|
||||
ethToMakerAssetRate,
|
||||
ethToTakerAssetRate,
|
||||
dexQuotes,
|
||||
rawTwoHopQuotes,
|
||||
isTxOriginContract,
|
||||
],
|
||||
] = await Promise.all([samplerPromise]);
|
||||
[rawTwoHopQuotes],
|
||||
] = await Promise.all([samplerPromise, multiHopPromise]);
|
||||
|
||||
// Filter out any invalid two hop quotes where we couldn't find a route
|
||||
const twoHopQuotes = rawTwoHopQuotes.filter(
|
||||
q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource,
|
||||
(q: DexSample<MultiHopFillData>) =>
|
||||
q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource,
|
||||
);
|
||||
|
||||
const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
|
||||
@@ -324,17 +368,17 @@ export class MarketOperationUtils {
|
||||
public async getBatchMarketBuyOrdersAsync(
|
||||
batchNativeOrders: SignedNativeOrder[][],
|
||||
makerAmounts: BigNumber[],
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
gasPrice: BigNumber,
|
||||
opts: GetMarketOrdersOpts,
|
||||
): Promise<Array<OptimizerResult | undefined>> {
|
||||
if (batchNativeOrders.length === 0) {
|
||||
throw new Error(AggregationError.EmptyOrders);
|
||||
}
|
||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
|
||||
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
|
||||
const requestFilters = new SourceFilters().exclude(opts.excludedSources).include(opts.includedSources);
|
||||
const quoteSourceFilters = this._buySources.merge(requestFilters);
|
||||
|
||||
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
|
||||
const feeSourceFilters = this._feeSources.exclude(opts.excludedFeeSources);
|
||||
|
||||
const ops = [
|
||||
...batchNativeOrders.map(orders =>
|
||||
@@ -402,11 +446,11 @@ export class MarketOperationUtils {
|
||||
isRfqSupported: false,
|
||||
},
|
||||
{
|
||||
bridgeSlippage: _opts.bridgeSlippage,
|
||||
maxFallbackSlippage: _opts.maxFallbackSlippage,
|
||||
excludedSources: _opts.excludedSources,
|
||||
feeSchedule: _opts.feeSchedule,
|
||||
allowFallback: _opts.allowFallback,
|
||||
bridgeSlippage: opts.bridgeSlippage,
|
||||
maxFallbackSlippage: opts.maxFallbackSlippage,
|
||||
excludedSources: opts.excludedSources,
|
||||
allowFallback: opts.allowFallback,
|
||||
gasPrice,
|
||||
},
|
||||
);
|
||||
return optimizerResult;
|
||||
@@ -466,7 +510,7 @@ export class MarketOperationUtils {
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
excludedSources: opts.excludedSources,
|
||||
feeSchedule: opts.feeSchedule,
|
||||
gasPrice: opts.gasPrice,
|
||||
});
|
||||
|
||||
// Find the optimal path.
|
||||
@@ -490,7 +534,7 @@ export class MarketOperationUtils {
|
||||
|
||||
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
||||
marketSideLiquidity,
|
||||
opts.feeSchedule,
|
||||
opts.gasPrice,
|
||||
opts.exchangeProxyOverhead,
|
||||
);
|
||||
if (bestTwoHopQuote && bestTwoHopRate.isGreaterThan(optimalPathRate)) {
|
||||
@@ -558,16 +602,15 @@ export class MarketOperationUtils {
|
||||
nativeOrders: SignedNativeOrder[],
|
||||
amount: BigNumber,
|
||||
side: MarketOperation,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
opts: GetMarketOrdersOpts,
|
||||
): Promise<OptimizerResultWithReport> {
|
||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
const optimizerOpts: GenerateOptimizedOrdersOpts = {
|
||||
bridgeSlippage: _opts.bridgeSlippage,
|
||||
maxFallbackSlippage: _opts.maxFallbackSlippage,
|
||||
excludedSources: _opts.excludedSources,
|
||||
feeSchedule: _opts.feeSchedule,
|
||||
allowFallback: _opts.allowFallback,
|
||||
exchangeProxyOverhead: _opts.exchangeProxyOverhead,
|
||||
bridgeSlippage: opts.bridgeSlippage,
|
||||
maxFallbackSlippage: opts.maxFallbackSlippage,
|
||||
excludedSources: opts.excludedSources,
|
||||
allowFallback: opts.allowFallback,
|
||||
exchangeProxyOverhead: opts.exchangeProxyOverhead,
|
||||
gasPrice: opts.gasPrice,
|
||||
};
|
||||
|
||||
if (nativeOrders.length === 0) {
|
||||
@@ -579,7 +622,7 @@ export class MarketOperationUtils {
|
||||
side === MarketOperation.Sell
|
||||
? this.getMarketSellLiquidityAsync.bind(this)
|
||||
: this.getMarketBuyLiquidityAsync.bind(this);
|
||||
const marketSideLiquidity: MarketSideLiquidity = await marketLiquidityFnAsync(nativeOrders, amount, _opts);
|
||||
const marketSideLiquidity: MarketSideLiquidity = await marketLiquidityFnAsync(nativeOrders, amount, opts);
|
||||
let optimizerResult: OptimizerResult | undefined;
|
||||
try {
|
||||
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, optimizerOpts);
|
||||
@@ -600,13 +643,13 @@ export class MarketOperationUtils {
|
||||
optimizerResult.adjustedRate,
|
||||
amount,
|
||||
marketSideLiquidity,
|
||||
_opts.feeSchedule,
|
||||
_opts.exchangeProxyOverhead,
|
||||
opts.gasPrice,
|
||||
opts.exchangeProxyOverhead,
|
||||
).wholeOrder;
|
||||
}
|
||||
|
||||
// If RFQ liquidity is enabled, make a request to check RFQ liquidity against the first optimizer result
|
||||
const { rfqt } = _opts;
|
||||
const { rfqt } = opts;
|
||||
if (
|
||||
marketSideLiquidity.isRfqSupported &&
|
||||
rfqt &&
|
||||
@@ -697,9 +740,9 @@ export class MarketOperationUtils {
|
||||
|
||||
// Compute Quote Report and return the results.
|
||||
let quoteReport: QuoteReport | undefined;
|
||||
if (_opts.shouldGenerateQuoteReport) {
|
||||
if (opts.shouldGenerateQuoteReport) {
|
||||
quoteReport = MarketOperationUtils._computeQuoteReport(
|
||||
_opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
|
||||
opts.rfqt ? opts.rfqt.quoteRequestor : undefined,
|
||||
marketSideLiquidity,
|
||||
optimizerResult,
|
||||
wholeOrderPrice,
|
||||
@@ -707,9 +750,9 @@ export class MarketOperationUtils {
|
||||
}
|
||||
|
||||
let priceComparisonsReport: PriceComparisonsReport | undefined;
|
||||
if (_opts.shouldIncludePriceComparisonsReport) {
|
||||
if (opts.shouldIncludePriceComparisonsReport) {
|
||||
priceComparisonsReport = MarketOperationUtils._computePriceComparisonsReport(
|
||||
_opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
|
||||
opts.rfqt ? opts.rfqt.quoteRequestor : undefined,
|
||||
marketSideLiquidity,
|
||||
wholeOrderPrice,
|
||||
);
|
||||
@@ -727,6 +770,62 @@ export class MarketOperationUtils {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async _fetchTokenOverridesAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
): Promise<{ overrides: { [address: string]: { code: string } } }> {
|
||||
const overrides: { [address: string]: { code: string } } = {};
|
||||
// Set the gas overhead counter to a known address
|
||||
overrides[GAS_OVERHEAD_ADDRESS] = { code: GAS_OVERHEAD_BYTECODE };
|
||||
// Set the fixed impl of a HackedERC20 bytecode, all other tokens use a delegate call
|
||||
overrides[HACKED_ERC20_ADDRESS] = { code: HACKED_ERC20_BYTECODE };
|
||||
|
||||
if (!this._sampler.tokenAdjacencyGraph) {
|
||||
return { overrides };
|
||||
}
|
||||
|
||||
// Allow the tokens bytecode to be overwritten using geths override functionality
|
||||
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, this._sampler.tokenAdjacencyGraph);
|
||||
const tokens = [takerToken, makerToken, ...intermediateTokens].map(t => t.toLowerCase());
|
||||
|
||||
// Fetch all of the missing token codes
|
||||
const missingTokenCodesTokens = tokens.filter(t => !this._contractCodeByAddress[t]);
|
||||
if (missingTokenCodesTokens.length > 0) {
|
||||
const missingTokenCodes = await this._sampler.executeBatchAsync(
|
||||
missingTokenCodesTokens.map(t => this._sampler.getCode(t)),
|
||||
);
|
||||
missingTokenCodes.forEach((code, i) => {
|
||||
this._contractCodeByAddress[missingTokenCodesTokens[i]] = code;
|
||||
});
|
||||
}
|
||||
|
||||
const tokenCodes = tokens.map(t => this._contractCodeByAddress[t]!);
|
||||
|
||||
const nativeWrappedToken = NATIVE_FEE_TOKEN_BY_CHAIN_ID[this._sampler.chainId];
|
||||
tokens.forEach((token, i) => {
|
||||
// Skip overriding WETH like token as this can be used directly with a deposit
|
||||
if (token === nativeWrappedToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenImplAddress = ethjs.bufferToHex(
|
||||
ethjs.setLengthLeft(
|
||||
// tslint:disable-next-line: custom-no-magic-numbers prefer-template
|
||||
ethjs.toBuffer(`0x${new BigNumber(token.toLowerCase()).plus(1).toString(16)}`),
|
||||
// tslint:disable-next-line: custom-no-magic-numbers prefer-template
|
||||
20,
|
||||
),
|
||||
);
|
||||
// Override the original implementation with the HackedERC20 delegate
|
||||
overrides[token] = { code: DELEGEATE_HACKED_ERC20_BYTECODE };
|
||||
// Specify the original implementation at a new address (+1)
|
||||
overrides[tokenImplAddress] = { code: tokenCodes[i] };
|
||||
});
|
||||
console.log({ overrides });
|
||||
|
||||
return { overrides };
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable: max-file-line-count
|
||||
|
@@ -0,0 +1,181 @@
|
||||
import { ContractFunctionObj } from '@0x/base-contract';
|
||||
import { BigNumber, decodeBytesAsRevertError, logUtils } from '@0x/utils';
|
||||
|
||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||
|
||||
import { ERC20BridgeSource, FillData, MeasuredSamplerResult, MeasuredSourceQuoteOperation } from './types';
|
||||
|
||||
export type Parameters<T> = T extends (...args: infer TArgs) => any ? TArgs : never;
|
||||
|
||||
export interface MeasuredSamplerContractCall<
|
||||
TFunc extends (...args: any[]) => ContractFunctionObj<any>,
|
||||
TFillData extends FillData = FillData
|
||||
> {
|
||||
contract: ERC20BridgeSamplerContract;
|
||||
function: TFunc;
|
||||
params: Parameters<TFunc>;
|
||||
callback?: (callResults: string, fillData: TFillData) => MeasuredSamplerResult;
|
||||
}
|
||||
|
||||
class PathDeregister {
|
||||
private static _instance: PathDeregister;
|
||||
// Presence in this registry with a negtive number indicates the Path has been deregistered
|
||||
private readonly _registry: { [key in ERC20BridgeSource]?: { [key: string]: number } } = {};
|
||||
private readonly _MAX_RESULTS = 100;
|
||||
|
||||
public static createKey(args: any[]): string {
|
||||
return args
|
||||
.map(a => {
|
||||
if (typeof a === 'object' && a !== null) {
|
||||
return Object.values(a).join('-');
|
||||
}
|
||||
if (Array.isArray(a)) {
|
||||
return a.join('-');
|
||||
}
|
||||
return a.toString();
|
||||
})
|
||||
.join('-');
|
||||
}
|
||||
|
||||
public static getInstance(): PathDeregister {
|
||||
if (!PathDeregister._instance) {
|
||||
PathDeregister._instance = new PathDeregister();
|
||||
}
|
||||
return PathDeregister._instance;
|
||||
}
|
||||
|
||||
private static _getRandom(): number {
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
return Math.floor(Math.random() * (100 - 0 + 1)) + 0;
|
||||
}
|
||||
|
||||
public isDeregistered(source: ERC20BridgeSource, key: string): boolean {
|
||||
if (!this._registry[source]) {
|
||||
this._registry[source] = {};
|
||||
}
|
||||
// Randomly allow the ops to be re-registered
|
||||
if (PathDeregister._getRandom() === 1) {
|
||||
return false;
|
||||
}
|
||||
return this._registry[source]![key] < 0;
|
||||
}
|
||||
|
||||
// Registers a successful result. Upon having one single success
|
||||
// a Path is no longer deregistered
|
||||
public handleResult(source: ERC20BridgeSource, key: string, result: MeasuredSamplerResult): void {
|
||||
if (!this._registry[source]) {
|
||||
this._registry[source] = {};
|
||||
}
|
||||
|
||||
// Defaults to 0
|
||||
if (!this._registry[source]![key]) {
|
||||
this._registry[source]![key] = 0;
|
||||
}
|
||||
|
||||
if (this._didSucceed(result)) {
|
||||
if (this._registry[source]![key] < 0) {
|
||||
this._registry[source]![key] = 0;
|
||||
}
|
||||
this._registry[source]![key] = Math.min(this._MAX_RESULTS, this._registry[source]![key] + 1);
|
||||
} else {
|
||||
this._registry[source]![key] = Math.max(-this._MAX_RESULTS, this._registry[source]![key] - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: prefer-function-over-method
|
||||
private _didSucceed(result: MeasuredSamplerResult): boolean {
|
||||
const nonZeroSample = result.samples.find(s => s.isGreaterThan(0));
|
||||
return nonZeroSample !== undefined && result.samples.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: max-classes-per-file
|
||||
export class MeasuredSamplerContractOperation<
|
||||
TFunc extends (...args: any[]) => ContractFunctionObj<any>,
|
||||
TFillData extends FillData = FillData
|
||||
> implements MeasuredSourceQuoteOperation<TFillData> {
|
||||
public readonly source: ERC20BridgeSource;
|
||||
public fillData: TFillData;
|
||||
private readonly _samplerContract: ERC20BridgeSamplerContract;
|
||||
private readonly _samplerFunction: TFunc;
|
||||
private readonly _params: Parameters<TFunc>;
|
||||
private readonly _callback?: (callResults: string, fillData: TFillData) => MeasuredSamplerResult;
|
||||
private readonly _deregisterKey: string | undefined;
|
||||
private readonly _deregisterable: boolean;
|
||||
private readonly _log: boolean;
|
||||
|
||||
constructor(
|
||||
opts: {
|
||||
source: ERC20BridgeSource;
|
||||
fillData?: TFillData;
|
||||
deregisterable?: boolean;
|
||||
log?: boolean;
|
||||
} & MeasuredSamplerContractCall<TFunc, TFillData>,
|
||||
) {
|
||||
this.source = opts.source;
|
||||
this.fillData = opts.fillData || ({} as TFillData); // tslint:disable-line:no-object-literal-type-assertion
|
||||
this._samplerContract = opts.contract;
|
||||
this._samplerFunction = opts.function;
|
||||
this._params = opts.params;
|
||||
this._callback = opts.callback;
|
||||
this._deregisterable = opts.deregisterable || false;
|
||||
this._log = opts.log || true;
|
||||
if (this._deregisterable) {
|
||||
this._deregisterKey = PathDeregister.createKey(this._params.slice(0, this._params.length - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public encodeCall(): string {
|
||||
return this._samplerFunction
|
||||
.bind(this._samplerContract)(...this._params)
|
||||
.getABIEncodedTransactionData();
|
||||
}
|
||||
|
||||
public handleCallResults(callResults: string): MeasuredSamplerResult {
|
||||
let result: MeasuredSamplerResult;
|
||||
if (this._callback !== undefined) {
|
||||
result = this._callback(callResults, this.fillData);
|
||||
} else {
|
||||
const [gasUsed, samples] = this._samplerContract.getABIDecodedReturnData<[BigNumber[], BigNumber[]]>(
|
||||
this._samplerFunction.name,
|
||||
callResults,
|
||||
);
|
||||
result = { gasUsed, samples };
|
||||
}
|
||||
if (this._deregisterKey) {
|
||||
PathDeregister.getInstance().handleResult(this.source, this._deregisterKey, result);
|
||||
}
|
||||
if (this._log) {
|
||||
logUtils.log({ source: this.source, fillData: this.fillData, ...result });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public handleRevert(callResults: string): MeasuredSamplerResult {
|
||||
let msg = callResults;
|
||||
try {
|
||||
msg = decodeBytesAsRevertError(callResults).toString();
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
logUtils.warn(
|
||||
`SamplerContractOperation: ${this.source}.${this._samplerFunction.name} reverted ${msg} ${JSON.stringify(
|
||||
this.fillData,
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
const result = { gasUsed: [], samples: [] };
|
||||
if (this._deregisterKey) {
|
||||
PathDeregister.getInstance().handleResult(this.source, this._deregisterKey, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public isDeregistered(): boolean {
|
||||
if (this._deregisterKey) {
|
||||
return PathDeregister.getInstance().isDeregistered(this.source, this._deregisterKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -5,14 +5,7 @@ import { Omit } from '../../types';
|
||||
|
||||
import { ZERO_AMOUNT } from './constants';
|
||||
import { getTwoHopAdjustedRate } from './rate_utils';
|
||||
import {
|
||||
DexSample,
|
||||
ExchangeProxyOverhead,
|
||||
FeeSchedule,
|
||||
MarketSideLiquidity,
|
||||
MultiHopFillData,
|
||||
TokenAdjacencyGraph,
|
||||
} from './types';
|
||||
import { DexSample, ExchangeProxyOverhead, MarketSideLiquidity, MultiHopFillData, TokenAdjacencyGraph } from './types';
|
||||
|
||||
/**
|
||||
* Given a token pair, returns the intermediate tokens to consider for two-hop routes.
|
||||
@@ -36,7 +29,7 @@ export function getIntermediateTokens(
|
||||
*/
|
||||
export function getBestTwoHopQuote(
|
||||
marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>,
|
||||
feeSchedule?: FeeSchedule,
|
||||
gasPrice: BigNumber,
|
||||
exchangeProxyOverhead?: ExchangeProxyOverhead,
|
||||
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
|
||||
const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity;
|
||||
@@ -57,7 +50,7 @@ export function getBestTwoHopQuote(
|
||||
}
|
||||
const best = filteredQuotes
|
||||
.map(quote =>
|
||||
getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, feeSchedule, exchangeProxyOverhead),
|
||||
getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, gasPrice, exchangeProxyOverhead),
|
||||
)
|
||||
.reduce(
|
||||
(prev, curr, i) =>
|
||||
@@ -68,7 +61,7 @@ export function getBestTwoHopQuote(
|
||||
filteredQuotes[0],
|
||||
inputAmount,
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
gasPrice,
|
||||
exchangeProxyOverhead,
|
||||
),
|
||||
quote: filteredQuotes[0],
|
||||
|
@@ -3,12 +3,13 @@ import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
|
||||
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
|
||||
|
||||
import { MAX_UINT256, ZERO_AMOUNT } from './constants';
|
||||
import { MAX_UINT256, NATIVE_LIMIT_ORDER_GAS_USED, NATIVE_RFQT_GAS_USED, ZERO_AMOUNT } from './constants';
|
||||
import {
|
||||
AggregationError,
|
||||
BalancerFillData,
|
||||
BalancerV2FillData,
|
||||
BancorFillData,
|
||||
BoosterFillData,
|
||||
CollapsedFill,
|
||||
CurveFillData,
|
||||
DexSample,
|
||||
@@ -61,6 +62,7 @@ export function createOrdersFromTwoHopSample(
|
||||
output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
|
||||
subFills: [],
|
||||
fillData: firstHopSource.fillData,
|
||||
gasUsed: ZERO_AMOUNT,
|
||||
};
|
||||
const secondHopFill: CollapsedFill = {
|
||||
sourcePathId: '',
|
||||
@@ -70,6 +72,7 @@ export function createOrdersFromTwoHopSample(
|
||||
output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
|
||||
subFills: [],
|
||||
fillData: secondHopSource.fillData,
|
||||
gasUsed: sample.gasUsed,
|
||||
};
|
||||
return [
|
||||
createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts.side),
|
||||
@@ -172,6 +175,8 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'FirebirdOneSwap');
|
||||
case ERC20BridgeSource.Lido:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Lido, 'Lido');
|
||||
case ERC20BridgeSource.Booster:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Booster, 'Booster');
|
||||
default:
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
@@ -306,6 +311,10 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
||||
const lidoFillData = (order as OptimizedMarketBridgeOrder<LidoFillData>).fillData;
|
||||
bridgeData = encoder.encode([lidoFillData.stEthTokenAddress]);
|
||||
break;
|
||||
case ERC20BridgeSource.Booster:
|
||||
const boosterFillData = (order as OptimizedMarketBridgeOrder<BoosterFillData>).fillData;
|
||||
bridgeData = encoder.encode([boosterFillData.poolAddress]);
|
||||
break;
|
||||
default:
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
@@ -329,6 +338,7 @@ export function createBridgeOrder(
|
||||
sourcePathId: fill.sourcePathId,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
fills: [fill],
|
||||
gasUsed: fill.gasUsed,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -459,6 +469,7 @@ export const BRIDGE_ENCODERS: {
|
||||
]),
|
||||
[ERC20BridgeSource.KyberDmm]: AbiEncoder.create('(address,address[],address[])'),
|
||||
[ERC20BridgeSource.Lido]: AbiEncoder.create('(address)'),
|
||||
[ERC20BridgeSource.Booster]: poolEncoder,
|
||||
};
|
||||
|
||||
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {
|
||||
@@ -487,6 +498,16 @@ export function createNativeOptimizedOrder(
|
||||
fillData,
|
||||
};
|
||||
return fill.type === FillQuoteTransformerOrderType.Rfq
|
||||
? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
|
||||
: { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
|
||||
? {
|
||||
...base,
|
||||
type: FillQuoteTransformerOrderType.Rfq,
|
||||
fillData: fillData as NativeRfqOrderFillData,
|
||||
gasUsed: NATIVE_RFQT_GAS_USED,
|
||||
}
|
||||
: {
|
||||
...base,
|
||||
type: FillQuoteTransformerOrderType.Limit,
|
||||
fillData: fillData as NativeLimitOrderFillData,
|
||||
gasUsed: NATIVE_LIMIT_ORDER_GAS_USED,
|
||||
};
|
||||
}
|
||||
|
@@ -241,6 +241,8 @@ export class Path {
|
||||
prevFill.input = prevFill.input.plus(fill.input);
|
||||
prevFill.output = prevFill.output.plus(fill.output);
|
||||
prevFill.fillData = fill.fillData;
|
||||
// Gas Used is always increasing
|
||||
prevFill.gasUsed = fill.gasUsed;
|
||||
prevFill.subFills.push(fill);
|
||||
continue;
|
||||
}
|
||||
@@ -252,6 +254,7 @@ export class Path {
|
||||
fillData: fill.fillData,
|
||||
input: fill.input,
|
||||
output: fill.output,
|
||||
gasUsed: fill.gasUsed,
|
||||
subFills: [fill],
|
||||
});
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import { BigNumber } from '@0x/utils';
|
||||
import { MarketOperation } from '../../types';
|
||||
|
||||
import { SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
||||
import { DexSample, ERC20BridgeSource, ExchangeProxyOverhead, FeeSchedule, MultiHopFillData } from './types';
|
||||
import { DexSample, ExchangeProxyOverhead, MultiHopFillData } from './types';
|
||||
|
||||
// tslint:disable:no-bitwise
|
||||
|
||||
@@ -16,20 +16,19 @@ export function getTwoHopAdjustedRate(
|
||||
twoHopQuote: DexSample<MultiHopFillData>,
|
||||
targetInput: BigNumber,
|
||||
outputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule = {},
|
||||
gasPrice: BigNumber,
|
||||
exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT,
|
||||
): BigNumber {
|
||||
const { output, input, fillData } = twoHopQuote;
|
||||
if (input.isLessThan(targetInput) || output.isZero()) {
|
||||
return ZERO_AMOUNT;
|
||||
}
|
||||
const penalty = outputAmountPerEth.times(
|
||||
exchangeProxyOverhead(
|
||||
SOURCE_FLAGS.MultiHop |
|
||||
SOURCE_FLAGS[fillData.firstHopSource.source] |
|
||||
SOURCE_FLAGS[fillData.secondHopSource.source],
|
||||
).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)),
|
||||
);
|
||||
const costInEth = exchangeProxyOverhead(
|
||||
SOURCE_FLAGS.MultiHop |
|
||||
SOURCE_FLAGS[fillData.firstHopSource.source] |
|
||||
SOURCE_FLAGS[fillData.secondHopSource.source],
|
||||
).plus(twoHopQuote.gasUsed.times(gasPrice));
|
||||
const penalty = outputAmountPerEth.times(costInEth);
|
||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||
return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput);
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { BigNumber, NULL_BYTES } from '@0x/utils';
|
||||
import _ = require('lodash');
|
||||
|
||||
import { SamplerOverrides } from '../../types';
|
||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||
@@ -141,27 +142,36 @@ export class DexOrderSampler extends SamplerOperations {
|
||||
* Run a series of operations from `DexOrderSampler.ops` in a single transaction.
|
||||
* Takes an arbitrary length array, but is not typesafe.
|
||||
*/
|
||||
public async executeBatchAsync<T extends Array<BatchedOperation<any>>>(ops: T): Promise<any[]> {
|
||||
public async executeBatchAsync<T extends Array<BatchedOperation<any>>>(
|
||||
ops: T,
|
||||
opts: Partial<SamplerOverrides> = {},
|
||||
): Promise<any[]> {
|
||||
const callDatas = ops.map(o => o.encodeCall());
|
||||
const { overrides, block } = this._samplerOverrides
|
||||
? this._samplerOverrides
|
||||
: { overrides: undefined, block: undefined };
|
||||
const { overrides, block } = _.merge(opts, this._samplerOverrides);
|
||||
|
||||
// All operations are NOOPs
|
||||
if (callDatas.every(cd => cd === NULL_BYTES)) {
|
||||
return callDatas.map((_callData, i) => ops[i].handleCallResults(NULL_BYTES));
|
||||
}
|
||||
// Execute all non-empty calldatas.
|
||||
const rawCallResults = await this._samplerContract
|
||||
.batchCall(callDatas.filter(cd => cd !== NULL_BYTES))
|
||||
.callAsync({ overrides }, block);
|
||||
let rawCallResults: any;
|
||||
try {
|
||||
rawCallResults = await this._samplerContract
|
||||
.batchCall(callDatas.filter(cd => cd !== NULL_BYTES))
|
||||
.callAsync({ overrides }, block);
|
||||
console.log({ rawCallResults, ops: ops.map(o => (o as any).fillData) });
|
||||
} catch (e) {
|
||||
console.log('error', e);
|
||||
throw e;
|
||||
}
|
||||
// Return the parsed results.
|
||||
let rawCallResultsIdx = 0;
|
||||
return callDatas.map((callData, i) => {
|
||||
const results = callDatas.map((callData, i) => {
|
||||
// tslint:disable-next-line:boolean-naming
|
||||
const { data, success } =
|
||||
callData !== NULL_BYTES ? rawCallResults[rawCallResultsIdx++] : { success: true, data: NULL_BYTES };
|
||||
return success ? ops[i].handleCallResults(data) : ops[i].handleRevert(data);
|
||||
});
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
@@ -56,7 +56,13 @@ export class SamplerContractOperation<
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
logUtils.warn(`SamplerContractOperation: ${this.source}.${this._samplerFunction.name} reverted ${msg}`);
|
||||
logUtils.warn(
|
||||
`SamplerContractOperation: ${this.source}.${this._samplerFunction.name} reverted ${msg} ${JSON.stringify(
|
||||
this.fillData,
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -86,6 +86,7 @@ export enum ERC20BridgeSource {
|
||||
WaultSwap = 'WaultSwap',
|
||||
Polydex = 'Polydex',
|
||||
FirebirdOneSwap = 'FirebirdOneSwap',
|
||||
Booster = 'Booster',
|
||||
}
|
||||
export type SourcesWithPoolsCache = ERC20BridgeSource.Balancer | ERC20BridgeSource.BalancerV2 | ERC20BridgeSource.Cream;
|
||||
|
||||
@@ -97,21 +98,13 @@ export enum CurveFunctionSelectors {
|
||||
None = '0x00000000',
|
||||
exchange = '0x3df02124',
|
||||
exchange_underlying = '0xa6417ed6',
|
||||
get_dy_underlying = '0x07211ef7',
|
||||
get_dx_underlying = '0x0e71d1b9',
|
||||
get_dy = '0x5e0d443f',
|
||||
get_dx = '0x67df02ca',
|
||||
// Curve V2
|
||||
exchange_v2 = '0x5b41b908',
|
||||
exchange_underlying_v2 = '0x65b2489b',
|
||||
get_dy_v2 = '0x556d6e9f',
|
||||
get_dy_underlying_v2 = '0x85f11d1e',
|
||||
// Smoothy
|
||||
swap_uint256 = '0x5673b02d', // swap(uint256,uint256,uint256,uint256)
|
||||
get_swap_amount = '0x45cf2ef6', // getSwapAmount(uint256,uint256,uint256)
|
||||
// Nerve BSC, Saddle Mainnet
|
||||
swap = '0x91695586', // swap(uint8,uint8,uint256,uint256,uint256)
|
||||
calculateSwap = '0xa95b089f', // calculateSwap(uint8,uint8,uint256)
|
||||
}
|
||||
// tslint:enable: enum-naming
|
||||
|
||||
@@ -120,8 +113,6 @@ export enum CurveFunctionSelectors {
|
||||
*/
|
||||
export interface CurveInfo {
|
||||
exchangeFunctionSelector: CurveFunctionSelectors;
|
||||
sellQuoteFunctionSelector: CurveFunctionSelectors;
|
||||
buyQuoteFunctionSelector: CurveFunctionSelectors;
|
||||
poolAddress: string;
|
||||
tokens: string[];
|
||||
metaTokens: string[] | undefined;
|
||||
@@ -167,7 +158,9 @@ export interface DexSample<TFillData extends FillData = FillData> {
|
||||
fillData: TFillData;
|
||||
input: BigNumber;
|
||||
output: BigNumber;
|
||||
gasUsed: BigNumber;
|
||||
}
|
||||
|
||||
export interface CurveFillData extends FillData {
|
||||
fromTokenIdx: number;
|
||||
toTokenIdx: number;
|
||||
@@ -223,8 +216,8 @@ export interface GenericRouterFillData extends FillData {
|
||||
}
|
||||
|
||||
export interface MultiHopFillData extends FillData {
|
||||
firstHopSource: SourceQuoteOperation;
|
||||
secondHopSource: SourceQuoteOperation;
|
||||
firstHopSource: MeasuredSourceQuoteOperation;
|
||||
secondHopSource: MeasuredSourceQuoteOperation;
|
||||
intermediateToken: string;
|
||||
}
|
||||
|
||||
@@ -260,6 +253,10 @@ export interface LidoFillData extends FillData {
|
||||
takerToken: string;
|
||||
}
|
||||
|
||||
export interface BoosterFillData extends FillData {
|
||||
poolAddress: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a node on a fill path.
|
||||
*/
|
||||
@@ -285,6 +282,7 @@ export interface Fill<TFillData extends FillData = FillData> {
|
||||
parent?: Fill;
|
||||
// The index of the fill in the original path.
|
||||
index: number;
|
||||
gasUsed: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,6 +311,7 @@ export interface CollapsedFill<TFillData extends FillData = FillData> {
|
||||
input: BigNumber;
|
||||
output: BigNumber;
|
||||
}>;
|
||||
gasUsed: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,6 +328,7 @@ export interface OptimizedMarketOrderBase<TFillData extends FillData = FillData>
|
||||
makerAmount: BigNumber; // The amount we wish to buy from this order, e.g inclusive of any previous partial fill
|
||||
takerAmount: BigNumber; // The amount we wish to fill this for, e.g inclusive of any previous partial fill
|
||||
fills: CollapsedFill[];
|
||||
gasUsed: BigNumber;
|
||||
}
|
||||
|
||||
export interface OptimizedMarketBridgeOrder<TFillData extends FillData = FillData>
|
||||
@@ -336,6 +336,7 @@ export interface OptimizedMarketBridgeOrder<TFillData extends FillData = FillDat
|
||||
type: FillQuoteTransformerOrderType.Bridge;
|
||||
fillData: TFillData;
|
||||
sourcePathId: string;
|
||||
gasUsed: BigNumber;
|
||||
}
|
||||
|
||||
export interface OptimizedLimitOrder extends OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
||||
@@ -361,8 +362,6 @@ export interface GetMarketOrdersRfqOpts extends RfqRequestOpts {
|
||||
firmQuoteValidator?: RfqFirmQuoteValidator;
|
||||
}
|
||||
|
||||
export type FeeEstimate = (fillData: FillData) => number | BigNumber;
|
||||
export type FeeSchedule = Partial<{ [key in ERC20BridgeSource]: FeeEstimate }>;
|
||||
export type ExchangeProxyOverhead = (sourceFlags: bigint) => BigNumber;
|
||||
|
||||
/**
|
||||
@@ -416,13 +415,9 @@ export interface GetMarketOrdersOpts {
|
||||
*/
|
||||
sampleDistributionBase: number;
|
||||
/**
|
||||
* Fees for each liquidity source, expressed in gas.
|
||||
* The gas overhead of various execution paths
|
||||
* E.g Uniswap VIP overhead, FlashWallet overhead
|
||||
*/
|
||||
feeSchedule: FeeSchedule;
|
||||
/**
|
||||
* Estimated gas consumed by each liquidity source.
|
||||
*/
|
||||
gasSchedule: FeeSchedule;
|
||||
exchangeProxyOverhead: ExchangeProxyOverhead;
|
||||
/**
|
||||
* Whether to pad the quote with a redundant fallback quote using different
|
||||
@@ -447,6 +442,10 @@ export interface GetMarketOrdersOpts {
|
||||
* hopping to. E.g DAI->USDC via an adjacent token WETH
|
||||
*/
|
||||
tokenAdjacencyGraph: TokenAdjacencyGraph;
|
||||
/**
|
||||
* The current gas price. Used to adjust pricing by the cost of a DEX
|
||||
*/
|
||||
gasPrice: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -458,6 +457,18 @@ export interface BatchedOperation<TResult> {
|
||||
handleRevert(callResults: string): TResult;
|
||||
}
|
||||
|
||||
export interface MeasuredSamplerResult {
|
||||
gasUsed: BigNumber[];
|
||||
samples: BigNumber[];
|
||||
}
|
||||
|
||||
export interface MeasuredSourceQuoteOperation<TFillData extends FillData = FillData>
|
||||
extends BatchedOperation<MeasuredSamplerResult> {
|
||||
readonly source: ERC20BridgeSource;
|
||||
fillData: TFillData;
|
||||
isDeregistered?: () => boolean;
|
||||
}
|
||||
|
||||
export interface SourceQuoteOperation<TFillData extends FillData = FillData> extends BatchedOperation<BigNumber[]> {
|
||||
readonly source: ERC20BridgeSource;
|
||||
fillData: TFillData;
|
||||
@@ -526,10 +537,10 @@ export interface GenerateOptimizedOrdersOpts {
|
||||
bridgeSlippage?: number;
|
||||
maxFallbackSlippage?: number;
|
||||
excludedSources?: ERC20BridgeSource[];
|
||||
feeSchedule?: FeeSchedule;
|
||||
exchangeProxyOverhead?: ExchangeProxyOverhead;
|
||||
allowFallback?: boolean;
|
||||
shouldBatchBridgeOrders?: boolean;
|
||||
gasPrice: BigNumber;
|
||||
}
|
||||
|
||||
export interface ComparisonPrice {
|
||||
|
@@ -4,6 +4,7 @@ import _ = require('lodash');
|
||||
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../types';
|
||||
|
||||
import { NATIVE_LIMIT_ORDER_GAS_USED, NATIVE_RFQT_GAS_USED } from './market_operation_utils/constants';
|
||||
import {
|
||||
CollapsedFill,
|
||||
DexSample,
|
||||
@@ -22,6 +23,7 @@ export interface QuoteReportEntryBase {
|
||||
makerAmount: BigNumber;
|
||||
takerAmount: BigNumber;
|
||||
fillData: FillData;
|
||||
gasUsed: BigNumber;
|
||||
}
|
||||
export interface BridgeQuoteReportEntry extends QuoteReportEntryBase {
|
||||
liquiditySource: Exclude<ERC20BridgeSource, ERC20BridgeSource.Native>;
|
||||
@@ -140,6 +142,7 @@ export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOp
|
||||
takerAmount: ds.output,
|
||||
liquiditySource,
|
||||
fillData: ds.fillData,
|
||||
gasUsed: ds.gasUsed,
|
||||
};
|
||||
} else if (marketOperation === MarketOperation.Sell) {
|
||||
return {
|
||||
@@ -147,6 +150,7 @@ export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOp
|
||||
takerAmount: ds.input,
|
||||
liquiditySource,
|
||||
fillData: ds.fillData,
|
||||
gasUsed: ds.gasUsed,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unexpected marketOperation ${marketOperation}`);
|
||||
@@ -171,6 +175,7 @@ export function multiHopSampleToReportSource(
|
||||
takerAmount: ds.output,
|
||||
fillData: ds.fillData,
|
||||
hopSources: [firstHop.source, secondHop.source],
|
||||
gasUsed: ds.gasUsed,
|
||||
};
|
||||
} else if (marketOperation === MarketOperation.Sell) {
|
||||
return {
|
||||
@@ -179,6 +184,7 @@ export function multiHopSampleToReportSource(
|
||||
takerAmount: ds.input,
|
||||
fillData: ds.fillData,
|
||||
hopSources: [firstHop.source, secondHop.source],
|
||||
gasUsed: ds.gasUsed,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unexpected marketOperation ${marketOperation}`);
|
||||
@@ -223,6 +229,7 @@ export function nativeOrderToReportEntry(
|
||||
...(comparisonPrice ? { comparisonPrice: comparisonPrice.toNumber() } : {}),
|
||||
nativeOrder,
|
||||
fillData,
|
||||
gasUsed: NATIVE_RFQT_GAS_USED,
|
||||
};
|
||||
} else {
|
||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
||||
@@ -231,6 +238,7 @@ export function nativeOrderToReportEntry(
|
||||
...nativeOrderBase,
|
||||
isRfqt: false,
|
||||
fillData,
|
||||
gasUsed: NATIVE_LIMIT_ORDER_GAS_USED,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import { BigNumber } from '@0x/utils';
|
||||
import { constants } from '../constants';
|
||||
import { MarketOperation } from '../types';
|
||||
|
||||
import { FeeSchedule, NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types';
|
||||
import { NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types';
|
||||
import { getNativeAdjustedTakerFeeAmount } from './utils';
|
||||
|
||||
const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants;
|
||||
@@ -72,13 +72,11 @@ export interface QuoteFillInfo {
|
||||
}
|
||||
|
||||
export interface QuoteFillInfoOpts {
|
||||
gasSchedule: FeeSchedule;
|
||||
protocolFeeMultiplier: BigNumber;
|
||||
slippage: number;
|
||||
}
|
||||
|
||||
const DEFAULT_SIMULATED_FILL_QUOTE_INFO_OPTS: QuoteFillInfoOpts = {
|
||||
gasSchedule: {},
|
||||
protocolFeeMultiplier: PROTOCOL_FEE_MULTIPLIER,
|
||||
slippage: 0,
|
||||
};
|
||||
@@ -108,7 +106,6 @@ export function simulateBestCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult
|
||||
createBestCaseFillOrderCalls(quoteInfo),
|
||||
quoteInfo.fillAmount,
|
||||
protocolFeePerFillOrder,
|
||||
opts.gasSchedule,
|
||||
);
|
||||
return fromIntermediateQuoteFillResult(result, quoteInfo);
|
||||
}
|
||||
@@ -122,9 +119,9 @@ export function simulateWorstCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult
|
||||
const protocolFeePerFillOrder = quoteInfo.gasPrice.times(opts.protocolFeeMultiplier);
|
||||
const bestCase = createBestCaseFillOrderCalls(quoteInfo);
|
||||
const result = {
|
||||
...fillQuoteOrders(bestCase, quoteInfo.fillAmount, protocolFeePerFillOrder, opts.gasSchedule),
|
||||
...fillQuoteOrders(bestCase, quoteInfo.fillAmount, protocolFeePerFillOrder),
|
||||
// Worst case gas and protocol fee is hitting all orders.
|
||||
gas: getTotalGasUsedByFills(quoteInfo.orders, opts.gasSchedule),
|
||||
gas: getTotalGasUsedByFills(quoteInfo.orders),
|
||||
protocolFee: protocolFeePerFillOrder.times(quoteInfo.orders.filter(o => hasProtocolFee(o)).length),
|
||||
};
|
||||
// Adjust the output by 1-slippage for the worst case if it is a sell
|
||||
@@ -140,7 +137,6 @@ export function fillQuoteOrders(
|
||||
fillOrders: QuoteFillOrderCall[],
|
||||
inputAmount: BigNumber,
|
||||
protocolFeePerFillOrder: BigNumber,
|
||||
gasSchedule: FeeSchedule,
|
||||
): IntermediateQuoteFillResult {
|
||||
const result: IntermediateQuoteFillResult = {
|
||||
...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT,
|
||||
@@ -155,9 +151,8 @@ export function fillQuoteOrders(
|
||||
if (remainingInput.lte(0)) {
|
||||
break;
|
||||
}
|
||||
const { source, fillData } = fill;
|
||||
const gas = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(fillData);
|
||||
result.gas += new BigNumber(gas).toNumber();
|
||||
const { source, gasUsed } = fill;
|
||||
result.gas += new BigNumber(gasUsed).toNumber();
|
||||
result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT;
|
||||
|
||||
// Actual rates are rarely linear, so fill subfills individually to
|
||||
@@ -314,11 +309,10 @@ function fromIntermediateQuoteFillResult(ir: IntermediateQuoteFillResult, quoteI
|
||||
};
|
||||
}
|
||||
|
||||
function getTotalGasUsedByFills(fills: OptimizedMarketOrder[], gasSchedule: FeeSchedule): number {
|
||||
function getTotalGasUsedByFills(fills: OptimizedMarketOrder[]): number {
|
||||
let gasUsed = 0;
|
||||
for (const f of fills) {
|
||||
const fee = gasSchedule[f.source] === undefined ? 0 : gasSchedule[f.source]!(f.fillData);
|
||||
gasUsed += new BigNumber(fee).toNumber();
|
||||
gasUsed += new BigNumber(f.gasUsed).toNumber();
|
||||
}
|
||||
return gasUsed;
|
||||
}
|
||||
|
@@ -4,5 +4,8 @@
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../generated-wrappers/balance_checker';
|
||||
export * from '../generated-wrappers/delegate_hacked_erc20';
|
||||
export * from '../generated-wrappers/erc20_bridge_sampler';
|
||||
export * from '../generated-wrappers/fake_taker';
|
||||
export * from '../generated-wrappers/gas_overhead';
|
||||
export * from '../generated-wrappers/hacked_erc20';
|
||||
|
@@ -5,28 +5,24 @@
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json';
|
||||
import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json';
|
||||
import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json';
|
||||
import * as BalancerV2Sampler from '../test/generated-artifacts/BalancerV2Sampler.json';
|
||||
import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json';
|
||||
import * as BoosterSampler from '../test/generated-artifacts/BoosterSampler.json';
|
||||
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
|
||||
import * as CurveV2Sampler from '../test/generated-artifacts/CurveV2Sampler.json';
|
||||
import * as DelegateHackedERC20 from '../test/generated-artifacts/DelegateHackedERC20.json';
|
||||
import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json';
|
||||
import * as DODOV2Sampler from '../test/generated-artifacts/DODOV2Sampler.json';
|
||||
import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json';
|
||||
import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
|
||||
import * as Eth2DaiSampler from '../test/generated-artifacts/Eth2DaiSampler.json';
|
||||
import * as FakeTaker from '../test/generated-artifacts/FakeTaker.json';
|
||||
import * as IBalancer from '../test/generated-artifacts/IBalancer.json';
|
||||
import * as IBancor from '../test/generated-artifacts/IBancor.json';
|
||||
import * as ICurve from '../test/generated-artifacts/ICurve.json';
|
||||
import * as GasOverhead from '../test/generated-artifacts/GasOverhead.json';
|
||||
import * as HackedERC20 from '../test/generated-artifacts/HackedERC20.json';
|
||||
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
|
||||
import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
|
||||
import * as IMooniswap from '../test/generated-artifacts/IMooniswap.json';
|
||||
import * as IMStable from '../test/generated-artifacts/IMStable.json';
|
||||
import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json';
|
||||
import * as IShell from '../test/generated-artifacts/IShell.json';
|
||||
import * as ISmoothy from '../test/generated-artifacts/ISmoothy.json';
|
||||
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
|
||||
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
|
||||
import * as KyberDmmSampler from '../test/generated-artifacts/KyberDmmSampler.json';
|
||||
@@ -36,11 +32,9 @@ import * as LiquidityProviderSampler from '../test/generated-artifacts/Liquidity
|
||||
import * as MakerPSMSampler from '../test/generated-artifacts/MakerPSMSampler.json';
|
||||
import * as MooniswapSampler from '../test/generated-artifacts/MooniswapSampler.json';
|
||||
import * as MStableSampler from '../test/generated-artifacts/MStableSampler.json';
|
||||
import * as MultiBridgeSampler from '../test/generated-artifacts/MultiBridgeSampler.json';
|
||||
import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSampler.json';
|
||||
import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json';
|
||||
import * as ShellSampler from '../test/generated-artifacts/ShellSampler.json';
|
||||
import * as SmoothySampler from '../test/generated-artifacts/SmoothySampler.json';
|
||||
import * as SwapRevertSampler from '../test/generated-artifacts/SwapRevertSampler.json';
|
||||
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
|
||||
import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json';
|
||||
import * as TwoHopSampler from '../test/generated-artifacts/TwoHopSampler.json';
|
||||
@@ -49,17 +43,21 @@ import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.
|
||||
import * as UniswapV3Sampler from '../test/generated-artifacts/UniswapV3Sampler.json';
|
||||
import * as UtilitySampler from '../test/generated-artifacts/UtilitySampler.json';
|
||||
export const artifacts = {
|
||||
ApproximateBuys: ApproximateBuys as ContractArtifact,
|
||||
BalanceChecker: BalanceChecker as ContractArtifact,
|
||||
BalancerSampler: BalancerSampler as ContractArtifact,
|
||||
BalancerV2Sampler: BalancerV2Sampler as ContractArtifact,
|
||||
BancorSampler: BancorSampler as ContractArtifact,
|
||||
BoosterSampler: BoosterSampler as ContractArtifact,
|
||||
CurveSampler: CurveSampler as ContractArtifact,
|
||||
CurveV2Sampler: CurveV2Sampler as ContractArtifact,
|
||||
DODOSampler: DODOSampler as ContractArtifact,
|
||||
DODOV2Sampler: DODOV2Sampler as ContractArtifact,
|
||||
DelegateHackedERC20: DelegateHackedERC20 as ContractArtifact,
|
||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
||||
Eth2DaiSampler: Eth2DaiSampler as ContractArtifact,
|
||||
FakeTaker: FakeTaker as ContractArtifact,
|
||||
GasOverhead: GasOverhead as ContractArtifact,
|
||||
HackedERC20: HackedERC20 as ContractArtifact,
|
||||
KyberDmmSampler: KyberDmmSampler as ContractArtifact,
|
||||
KyberSampler: KyberSampler as ContractArtifact,
|
||||
LidoSampler: LidoSampler as ContractArtifact,
|
||||
@@ -67,26 +65,16 @@ export const artifacts = {
|
||||
MStableSampler: MStableSampler as ContractArtifact,
|
||||
MakerPSMSampler: MakerPSMSampler as ContractArtifact,
|
||||
MooniswapSampler: MooniswapSampler as ContractArtifact,
|
||||
MultiBridgeSampler: MultiBridgeSampler as ContractArtifact,
|
||||
NativeOrderSampler: NativeOrderSampler as ContractArtifact,
|
||||
SamplerUtils: SamplerUtils as ContractArtifact,
|
||||
ShellSampler: ShellSampler as ContractArtifact,
|
||||
SmoothySampler: SmoothySampler as ContractArtifact,
|
||||
SwapRevertSampler: SwapRevertSampler as ContractArtifact,
|
||||
TwoHopSampler: TwoHopSampler as ContractArtifact,
|
||||
UniswapSampler: UniswapSampler as ContractArtifact,
|
||||
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,
|
||||
UniswapV3Sampler: UniswapV3Sampler as ContractArtifact,
|
||||
UtilitySampler: UtilitySampler as ContractArtifact,
|
||||
IBalancer: IBalancer as ContractArtifact,
|
||||
IBancor: IBancor as ContractArtifact,
|
||||
ICurve: ICurve as ContractArtifact,
|
||||
IEth2Dai: IEth2Dai as ContractArtifact,
|
||||
IKyberNetwork: IKyberNetwork as ContractArtifact,
|
||||
IMStable: IMStable as ContractArtifact,
|
||||
IMooniswap: IMooniswap as ContractArtifact,
|
||||
IMultiBridge: IMultiBridge as ContractArtifact,
|
||||
IShell: IShell as ContractArtifact,
|
||||
ISmoothy: ISmoothy as ContractArtifact,
|
||||
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
|
||||
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
|
||||
DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact,
|
||||
|
@@ -18,7 +18,6 @@ const expect = chai.expect;
|
||||
const DAI_TOKEN = '0x6b175474e89094c44da98b954eedeac495271d0f';
|
||||
const ETH_TOKEN = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
||||
const GAS_PRICE = new BigNumber(50e9); // 50 gwei
|
||||
const NATIVE_ORDER_FEE = new BigNumber(220e3); // 220K gas
|
||||
|
||||
// DEX samples to fill in MarketSideLiquidity
|
||||
const kyberSample1: DexSample = {
|
||||
@@ -26,19 +25,18 @@ const kyberSample1: DexSample = {
|
||||
input: new BigNumber(10000),
|
||||
output: new BigNumber(10001),
|
||||
fillData: {},
|
||||
gasUsed: new BigNumber(450000),
|
||||
};
|
||||
const uniswapSample1: DexSample = {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: new BigNumber(10003),
|
||||
output: new BigNumber(10004),
|
||||
fillData: {},
|
||||
gasUsed: new BigNumber(90000),
|
||||
};
|
||||
const dexQuotes: DexSample[] = [kyberSample1, uniswapSample1];
|
||||
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Native]: _.constant(GAS_PRICE.times(NATIVE_ORDER_FEE)),
|
||||
};
|
||||
|
||||
// At this stage it has been gas price adjusted
|
||||
const exchangeProxyOverhead = (sourceFlags: bigint) => {
|
||||
if ([SOURCE_FLAGS.RfqOrder].includes(sourceFlags)) {
|
||||
return new BigNumber(20e3).times(GAS_PRICE);
|
||||
@@ -102,14 +100,14 @@ describe('getComparisonPrices', async () => {
|
||||
adjustedRate,
|
||||
AMOUNT,
|
||||
sellMarketSideLiquidity,
|
||||
feeSchedule,
|
||||
GAS_PRICE,
|
||||
exchangeProxyOverhead,
|
||||
);
|
||||
|
||||
// expected outcome
|
||||
const EXPECTED_PRICE = new BigNumber('500.6');
|
||||
const EXPECTED_PRICE = new BigNumber('500.30');
|
||||
|
||||
expect(comparisonPrices.wholeOrder).to.deep.eq(EXPECTED_PRICE);
|
||||
expect(comparisonPrices.wholeOrder).to.be.bignumber.eq(EXPECTED_PRICE);
|
||||
});
|
||||
it('should create a proper comparison price for Buys', () => {
|
||||
// test buying 10 ETH with DAI
|
||||
@@ -124,14 +122,14 @@ describe('getComparisonPrices', async () => {
|
||||
adjustedRate,
|
||||
AMOUNT,
|
||||
buyMarketSideLiquidity,
|
||||
feeSchedule,
|
||||
GAS_PRICE,
|
||||
exchangeProxyOverhead,
|
||||
);
|
||||
|
||||
// expected outcome
|
||||
const EXPECTED_PRICE = new BigNumber('0.0020024029');
|
||||
const EXPECTED_PRICE = new BigNumber('0.0020012007');
|
||||
|
||||
expect(comparisonPrices.wholeOrder).to.deep.eq(EXPECTED_PRICE);
|
||||
expect(comparisonPrices.wholeOrder).to.be.bignumber.eq(EXPECTED_PRICE);
|
||||
});
|
||||
it('should not return a price if takerAmount is < 0', () => {
|
||||
// test selling 0.00001 ETH for DAI
|
||||
@@ -144,7 +142,7 @@ describe('getComparisonPrices', async () => {
|
||||
adjustedRate,
|
||||
AMOUNT,
|
||||
sellMarketSideLiquidity,
|
||||
feeSchedule,
|
||||
GAS_PRICE,
|
||||
exchangeProxyOverhead,
|
||||
);
|
||||
|
||||
|
@@ -3,6 +3,8 @@ import { blockchainTests, describe, expect, toBaseUnitAmount, Web3ProviderEngine
|
||||
import { RPCSubprovider } from '@0x/subproviders';
|
||||
import { BigNumber, NULL_BYTES, providerUtils } from '@0x/utils';
|
||||
|
||||
import { ERC20BridgeSource } from '../../src';
|
||||
import { getCurveLikeInfosForPair } from '../../src/utils/market_operation_utils/bridge_source_utils';
|
||||
import { KYBER_CONFIG_BY_CHAIN_ID, MAINNET_TOKENS } from '../../src/utils/market_operation_utils/constants';
|
||||
import { artifacts } from '../artifacts';
|
||||
import { ERC20BridgeSamplerContract } from '../wrappers';
|
||||
@@ -30,27 +32,50 @@ blockchainTests.skip('Mainnet Sampler Tests', env => {
|
||||
});
|
||||
});
|
||||
describe('Curve', () => {
|
||||
const CURVE_ADDRESS = '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51';
|
||||
const DAI_TOKEN_INDEX = new BigNumber(0);
|
||||
const USDC_TOKEN_INDEX = new BigNumber(1);
|
||||
const CURVE_INFO = {
|
||||
poolAddress: CURVE_ADDRESS,
|
||||
sellQuoteFunctionSelector: '0x07211ef7',
|
||||
buyQuoteFunctionSelector: '0x0e71d1b9',
|
||||
};
|
||||
|
||||
describe('sampleSellsFromCurve()', () => {
|
||||
it('samples sells from Curve DAI->USDC', async () => {
|
||||
const CURVE_INFO = getCurveLikeInfosForPair(
|
||||
ChainId.Mainnet,
|
||||
MAINNET_TOKENS.DAI,
|
||||
MAINNET_TOKENS.USDC,
|
||||
ERC20BridgeSource.Curve,
|
||||
)[0];
|
||||
const samples = await testContract
|
||||
.sampleSellsFromCurve(CURVE_INFO, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1)])
|
||||
.sampleSellsFromCurve(
|
||||
{
|
||||
curveAddress: CURVE_INFO.poolAddress,
|
||||
fromCoinIdx: new BigNumber(CURVE_INFO.takerTokenIdx),
|
||||
toCoinIdx: new BigNumber(CURVE_INFO.makerTokenIdx),
|
||||
exchangeFunctionSelector: CURVE_INFO.exchangeFunctionSelector,
|
||||
},
|
||||
MAINNET_TOKENS.DAI,
|
||||
MAINNET_TOKENS.USDC,
|
||||
[toBaseUnitAmount(1)],
|
||||
)
|
||||
.callAsync({ overrides });
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
});
|
||||
|
||||
it('samples sells from Curve USDC->DAI', async () => {
|
||||
const CURVE_INFO = getCurveLikeInfosForPair(
|
||||
ChainId.Mainnet,
|
||||
MAINNET_TOKENS.USDC,
|
||||
MAINNET_TOKENS.DAI,
|
||||
ERC20BridgeSource.Curve,
|
||||
)[0];
|
||||
const samples = await testContract
|
||||
.sampleSellsFromCurve(CURVE_INFO, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1, 6)])
|
||||
.sampleSellsFromCurve(
|
||||
{
|
||||
curveAddress: CURVE_INFO.poolAddress,
|
||||
fromCoinIdx: new BigNumber(CURVE_INFO.takerTokenIdx),
|
||||
toCoinIdx: new BigNumber(CURVE_INFO.makerTokenIdx),
|
||||
exchangeFunctionSelector: CURVE_INFO.exchangeFunctionSelector,
|
||||
},
|
||||
MAINNET_TOKENS.USDC,
|
||||
MAINNET_TOKENS.DAI,
|
||||
[toBaseUnitAmount(1, 6)],
|
||||
)
|
||||
.callAsync({ overrides });
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
@@ -58,11 +83,27 @@ blockchainTests.skip('Mainnet Sampler Tests', env => {
|
||||
});
|
||||
|
||||
describe('sampleBuysFromCurve()', () => {
|
||||
const CURVE_INFO = getCurveLikeInfosForPair(
|
||||
ChainId.Mainnet,
|
||||
MAINNET_TOKENS.DAI,
|
||||
MAINNET_TOKENS.USDC,
|
||||
ERC20BridgeSource.Curve,
|
||||
)[0];
|
||||
it('samples buys from Curve DAI->USDC', async () => {
|
||||
// From DAI to USDC
|
||||
// I want to buy 1 USDC
|
||||
const samples = await testContract
|
||||
.sampleBuysFromCurve(CURVE_INFO, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1, 6)])
|
||||
.sampleBuysFromCurve(
|
||||
{
|
||||
curveAddress: CURVE_INFO.poolAddress,
|
||||
fromCoinIdx: new BigNumber(CURVE_INFO.takerTokenIdx),
|
||||
toCoinIdx: new BigNumber(CURVE_INFO.makerTokenIdx),
|
||||
exchangeFunctionSelector: CURVE_INFO.exchangeFunctionSelector,
|
||||
},
|
||||
MAINNET_TOKENS.DAI,
|
||||
MAINNET_TOKENS.USDC,
|
||||
[toBaseUnitAmount(1, 6)],
|
||||
)
|
||||
.callAsync({ overrides });
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
@@ -72,7 +113,17 @@ blockchainTests.skip('Mainnet Sampler Tests', env => {
|
||||
// From USDC to DAI
|
||||
// I want to buy 1 DAI
|
||||
const samples = await testContract
|
||||
.sampleBuysFromCurve(CURVE_INFO, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1)])
|
||||
.sampleBuysFromCurve(
|
||||
{
|
||||
curveAddress: CURVE_INFO.poolAddress,
|
||||
fromCoinIdx: new BigNumber(CURVE_INFO.takerTokenIdx),
|
||||
toCoinIdx: new BigNumber(CURVE_INFO.makerTokenIdx),
|
||||
exchangeFunctionSelector: CURVE_INFO.exchangeFunctionSelector,
|
||||
},
|
||||
MAINNET_TOKENS.USDC,
|
||||
MAINNET_TOKENS.DAI,
|
||||
[toBaseUnitAmount(1)],
|
||||
)
|
||||
.callAsync({ overrides });
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
|
@@ -18,7 +18,8 @@ import { DummyLiquidityProviderContract, TestERC20BridgeSamplerContract } from '
|
||||
// tslint:disable: custom-no-magic-numbers
|
||||
|
||||
const { NULL_ADDRESS } = constants;
|
||||
blockchainTests('erc20-bridge-sampler', env => {
|
||||
// jacob: Skip until we can override in Ganache
|
||||
blockchainTests.skip('erc20-bridge-sampler', env => {
|
||||
let testContract: TestERC20BridgeSamplerContract;
|
||||
const RATE_DENOMINATOR = constants.ONE_ETHER;
|
||||
const MIN_RATE = new BigNumber('0.01');
|
||||
@@ -37,7 +38,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const KYBER_RESERVE_OFFSET = new BigNumber(0);
|
||||
let KYBER_ADDRESS = '';
|
||||
let ETH2DAI_ADDRESS = '';
|
||||
let UNISWAP_ADDRESS = '';
|
||||
let UNISWAP_V2_ROUTER = '';
|
||||
|
||||
before(async () => {
|
||||
@@ -50,7 +50,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
UNISWAP_V2_ROUTER = await testContract.uniswapV2Router().callAsync();
|
||||
KYBER_ADDRESS = await testContract.kyber().callAsync();
|
||||
ETH2DAI_ADDRESS = await testContract.eth2Dai().callAsync();
|
||||
UNISWAP_ADDRESS = await testContract.uniswap().callAsync();
|
||||
});
|
||||
|
||||
function getPackedHash(...args: string[]): string {
|
||||
@@ -660,198 +659,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
});
|
||||
|
||||
blockchainTests.resets('sampleSellsFromUniswap()', () => {
|
||||
const UNISWAP_ETH_ADDRESS = NULL_ADDRESS;
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('throws if tokens are the same', async () => {
|
||||
const tx = testContract.sampleSellsFromUniswap(UNISWAP_ADDRESS, MAKER_TOKEN, MAKER_TOKEN, []).callAsync();
|
||||
return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR);
|
||||
});
|
||||
|
||||
it('can return no quotes', async () => {
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, [])
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if no exchange exists for the maker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, nonExistantToken, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if no exchange exists for the taker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const sampleAmounts = getSampleAmounts(nonExistantToken);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(UNISWAP_ADDRESS, nonExistantToken, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
blockchainTests.resets('sampleBuysFromUniswap()', () => {
|
||||
const UNISWAP_ETH_ADDRESS = NULL_ADDRESS;
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('throws if tokens are the same', async () => {
|
||||
const tx = testContract.sampleBuysFromUniswap(UNISWAP_ADDRESS, MAKER_TOKEN, MAKER_TOKEN, []).callAsync();
|
||||
return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR);
|
||||
});
|
||||
|
||||
it('can return no quotes', async () => {
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, [])
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if no exchange exists for the maker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const sampleAmounts = getSampleAmounts(nonExistantToken);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, nonExistantToken, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if no exchange exists for the taker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(UNISWAP_ADDRESS, nonExistantToken, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('liquidity provider', () => {
|
||||
const xAsset = randomAddress();
|
||||
const yAsset = randomAddress();
|
||||
|
@@ -152,7 +152,7 @@ describe('DexSampler tests', () => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return ['0x', '0x', expectedMakerFillAmounts];
|
||||
return ['0x', '0x', [], expectedMakerFillAmounts];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
@@ -164,7 +164,7 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
const [results] = await dexOrderSampler.executeAsync(
|
||||
dexOrderSampler.getKyberSellQuotes(
|
||||
{ hintHandler: randomAddress(), networkProxy: randomAddress(), weth: randomAddress() },
|
||||
new BigNumber(0),
|
||||
@@ -173,7 +173,7 @@ describe('DexSampler tests', () => {
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
expect(results.samples).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getLiquidityProviderSellQuotes()', async () => {
|
||||
@@ -186,7 +186,7 @@ describe('DexSampler tests', () => {
|
||||
expect(providerAddress).to.eq(poolAddress);
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
return [toBaseUnitAmount(1001)];
|
||||
return [[new BigNumber(gasCost)], [toBaseUnitAmount(1001)]];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
@@ -215,6 +215,7 @@ describe('DexSampler tests', () => {
|
||||
output: toBaseUnitAmount(1001),
|
||||
input: toBaseUnitAmount(1000),
|
||||
fillData: { poolAddress, gasCost },
|
||||
gasUsed: new BigNumber(gasCost),
|
||||
},
|
||||
],
|
||||
]);
|
||||
@@ -230,7 +231,7 @@ describe('DexSampler tests', () => {
|
||||
expect(providerAddress).to.eq(poolAddress);
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
return [toBaseUnitAmount(999)];
|
||||
return [[new BigNumber(gasCost)], [toBaseUnitAmount(999)]];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
@@ -259,6 +260,7 @@ describe('DexSampler tests', () => {
|
||||
output: toBaseUnitAmount(999),
|
||||
input: toBaseUnitAmount(1000),
|
||||
fillData: { poolAddress, gasCost },
|
||||
gasUsed: new BigNumber(gasCost),
|
||||
},
|
||||
],
|
||||
]);
|
||||
@@ -274,7 +276,7 @@ describe('DexSampler tests', () => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return expectedMakerFillAmounts;
|
||||
return [[], expectedMakerFillAmounts];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
@@ -286,7 +288,7 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
const [result] = await dexOrderSampler.executeAsync(
|
||||
dexOrderSampler.getEth2DaiSellQuotes(
|
||||
randomAddress(),
|
||||
expectedMakerToken,
|
||||
@@ -294,7 +296,7 @@ describe('DexSampler tests', () => {
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
expect(result.samples).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getUniswapSellQuotes()', async () => {
|
||||
@@ -307,7 +309,7 @@ describe('DexSampler tests', () => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return expectedMakerFillAmounts;
|
||||
return [[], expectedMakerFillAmounts];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
@@ -319,7 +321,7 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
const [results] = await dexOrderSampler.executeAsync(
|
||||
dexOrderSampler.getUniswapSellQuotes(
|
||||
randomAddress(),
|
||||
expectedMakerToken,
|
||||
@@ -327,7 +329,7 @@ describe('DexSampler tests', () => {
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
expect(results.samples).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getUniswapV2SellQuotes()', async () => {
|
||||
@@ -339,7 +341,7 @@ describe('DexSampler tests', () => {
|
||||
sampleSellsFromUniswapV2: (_router, path, fillAmounts) => {
|
||||
expect(path).to.deep.eq([expectedMakerToken, expectedTakerToken]);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return expectedMakerFillAmounts;
|
||||
return [[], expectedMakerFillAmounts];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
@@ -351,14 +353,14 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
const [results] = await dexOrderSampler.executeAsync(
|
||||
dexOrderSampler.getUniswapV2SellQuotes(
|
||||
NULL_ADDRESS,
|
||||
[expectedMakerToken, expectedTakerToken],
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
expect(results.samples).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getEth2DaiBuyQuotes()', async () => {
|
||||
@@ -371,7 +373,7 @@ describe('DexSampler tests', () => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
return expectedTakerFillAmounts;
|
||||
return [[], expectedMakerFillAmounts];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
@@ -383,7 +385,7 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
const [results] = await dexOrderSampler.executeAsync(
|
||||
dexOrderSampler.getEth2DaiBuyQuotes(
|
||||
randomAddress(),
|
||||
expectedMakerToken,
|
||||
@@ -391,7 +393,7 @@ describe('DexSampler tests', () => {
|
||||
expectedMakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
expect(results.samples).to.deep.eq(expectedTakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getUniswapBuyQuotes()', async () => {
|
||||
@@ -404,7 +406,7 @@ describe('DexSampler tests', () => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
return expectedTakerFillAmounts;
|
||||
return [[], expectedMakerFillAmounts];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
@@ -416,7 +418,7 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
const [results] = await dexOrderSampler.executeAsync(
|
||||
dexOrderSampler.getUniswapBuyQuotes(
|
||||
randomAddress(),
|
||||
expectedMakerToken,
|
||||
@@ -424,7 +426,7 @@ describe('DexSampler tests', () => {
|
||||
expectedMakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
expect(results.samples).to.deep.eq(expectedTakerFillAmounts);
|
||||
});
|
||||
|
||||
interface RatesBySource {
|
||||
@@ -445,20 +447,27 @@ describe('DexSampler tests', () => {
|
||||
let uniswapRouter: string;
|
||||
let uniswapV2Router: string;
|
||||
let eth2DaiRouter: string;
|
||||
const gasUsed = new BigNumber(123);
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleSellsFromUniswap: (router, takerToken, makerToken, fillAmounts) => {
|
||||
uniswapRouter = router;
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue());
|
||||
return [
|
||||
fillAmounts.map(_a => gasUsed),
|
||||
fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue()),
|
||||
];
|
||||
},
|
||||
sampleSellsFromEth2Dai: (router, takerToken, makerToken, fillAmounts) => {
|
||||
eth2DaiRouter = router;
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue());
|
||||
return [
|
||||
fillAmounts.map(_a => gasUsed),
|
||||
fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()),
|
||||
];
|
||||
},
|
||||
sampleSellsFromUniswapV2: (router, path, fillAmounts) => {
|
||||
uniswapV2Router = router;
|
||||
@@ -470,7 +479,10 @@ describe('DexSampler tests', () => {
|
||||
expect(path).to.have.lengthOf.within(2, 3);
|
||||
}
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue());
|
||||
return [
|
||||
fillAmounts.map(_a => gasUsed),
|
||||
fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue()),
|
||||
];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
@@ -495,6 +507,7 @@ describe('DexSampler tests', () => {
|
||||
source: s,
|
||||
input: a,
|
||||
output: a.times(ratesBySource[s]).integerValue(),
|
||||
gasUsed,
|
||||
fillData: (() => {
|
||||
if (s === ERC20BridgeSource.UniswapV2) {
|
||||
return {
|
||||
@@ -518,6 +531,7 @@ describe('DexSampler tests', () => {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: a,
|
||||
output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(),
|
||||
gasUsed,
|
||||
fillData: {
|
||||
router: uniswapV2Router,
|
||||
tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken],
|
||||
@@ -539,6 +553,7 @@ describe('DexSampler tests', () => {
|
||||
[ERC20BridgeSource.UniswapV2]: getRandomFloat(0, 100),
|
||||
};
|
||||
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
|
||||
const gasUsed = new BigNumber(123);
|
||||
let uniswapRouter: string;
|
||||
let uniswapV2Router: string;
|
||||
let eth2DaiRouter: string;
|
||||
@@ -548,14 +563,20 @@ describe('DexSampler tests', () => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue());
|
||||
return [
|
||||
fillAmounts.map(_a => gasUsed),
|
||||
fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue()),
|
||||
];
|
||||
},
|
||||
sampleBuysFromEth2Dai: (router, takerToken, makerToken, fillAmounts) => {
|
||||
eth2DaiRouter = router;
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue());
|
||||
return [
|
||||
fillAmounts.map(_a => gasUsed),
|
||||
fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()),
|
||||
];
|
||||
},
|
||||
sampleBuysFromUniswapV2: (router, path, fillAmounts) => {
|
||||
uniswapV2Router = router;
|
||||
@@ -567,7 +588,10 @@ describe('DexSampler tests', () => {
|
||||
expect(path).to.have.lengthOf.within(2, 3);
|
||||
}
|
||||
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue());
|
||||
return [
|
||||
fillAmounts.map(_a => gasUsed),
|
||||
fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue()),
|
||||
];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
@@ -587,6 +611,7 @@ describe('DexSampler tests', () => {
|
||||
source: s,
|
||||
input: a,
|
||||
output: a.times(ratesBySource[s]).integerValue(),
|
||||
gasUsed,
|
||||
fillData: (() => {
|
||||
if (s === ERC20BridgeSource.UniswapV2) {
|
||||
return {
|
||||
@@ -609,6 +634,7 @@ describe('DexSampler tests', () => {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: a,
|
||||
output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(),
|
||||
gasUsed,
|
||||
fillData: {
|
||||
router: uniswapV2Router,
|
||||
tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken],
|
||||
|
@@ -102,6 +102,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
takerAmount: order.takerAmount,
|
||||
fills: [],
|
||||
...optimizerFields,
|
||||
gasUsed: new BigNumber(1),
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,7 @@ import { NativeOrderWithFillableAmounts } from '../src/types';
|
||||
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
||||
import {
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
POSITIVE_INF,
|
||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
SOURCE_FLAGS,
|
||||
@@ -62,6 +63,7 @@ const SELL_SOURCES = SELL_SOURCE_FILTER_BY_CHAIN_ID[ChainId.Mainnet].sources;
|
||||
const TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = { default: [] };
|
||||
|
||||
const SIGNATURE = { v: 1, r: NULL_BYTES, s: NULL_BYTES, signatureType: SignatureType.EthSign };
|
||||
const GAS_PRICE = new BigNumber(1);
|
||||
|
||||
/**
|
||||
* gets the orders required for a market sell operation by (potentially) merging native orders with
|
||||
@@ -75,7 +77,7 @@ async function getMarketSellOrdersAsync(
|
||||
utils: MarketOperationUtils,
|
||||
nativeOrders: SignedNativeOrder[],
|
||||
takerAmount: BigNumber,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
opts: GetMarketOrdersOpts,
|
||||
): Promise<OptimizerResultWithReport> {
|
||||
return utils.getOptimizerResultAsync(nativeOrders, takerAmount, MarketOperation.Sell, opts);
|
||||
}
|
||||
@@ -92,7 +94,7 @@ async function getMarketBuyOrdersAsync(
|
||||
utils: MarketOperationUtils,
|
||||
nativeOrders: SignedNativeOrder[],
|
||||
makerAmount: BigNumber,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
opts: GetMarketOrdersOpts,
|
||||
): Promise<OptimizerResultWithReport> {
|
||||
return utils.getOptimizerResultAsync(nativeOrders, makerAmount, MarketOperation.Buy, opts);
|
||||
}
|
||||
@@ -129,6 +131,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
const contractAddresses = {
|
||||
...getContractAddressesForChainOrThrow(CHAIN_ID),
|
||||
};
|
||||
let DEFAULT_DEX_GAS_USED = new BigNumber(100000);
|
||||
|
||||
function getMockedQuoteRequestor(
|
||||
type: 'indicative' | 'firm',
|
||||
@@ -205,6 +208,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
inputs: Numberish[],
|
||||
rates: Numberish[],
|
||||
fillData?: FillData,
|
||||
gasUsed?: BigNumber,
|
||||
): DexSample[] {
|
||||
const samples: DexSample[] = [];
|
||||
inputs.forEach((input, i) => {
|
||||
@@ -218,6 +222,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
.times(rate)
|
||||
.plus(i === 0 ? 0 : samples[i - 1].output)
|
||||
.integerValue(),
|
||||
gasUsed: gasUsed || DEFAULT_DEX_GAS_USED,
|
||||
});
|
||||
});
|
||||
return samples;
|
||||
@@ -233,7 +238,10 @@ describe('MarketOperationUtils tests', () => {
|
||||
liquidityProviderAddress?: string,
|
||||
) => DexSample[][];
|
||||
|
||||
function createGetMultipleSellQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
|
||||
function createGetMultipleSellQuotesOperationFromRates(
|
||||
rates: RatesBySource,
|
||||
gasSchedule: Partial<{ [key in ERC20BridgeSource]: BigNumber }> = {},
|
||||
): GetMultipleQuotesOperation {
|
||||
return (
|
||||
sources: ERC20BridgeSource[],
|
||||
_makerToken: string,
|
||||
@@ -241,11 +249,16 @@ describe('MarketOperationUtils tests', () => {
|
||||
fillAmounts: BigNumber[],
|
||||
_wethAddress: string,
|
||||
) => {
|
||||
return BATCH_SOURCE_FILTERS.getAllowed(sources).map(s => createSamplesFromRates(s, fillAmounts, rates[s]));
|
||||
return BATCH_SOURCE_FILTERS.getAllowed(sources).map(s =>
|
||||
createSamplesFromRates(s, fillAmounts, rates[s], undefined, gasSchedule[s]),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function createGetMultipleBuyQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
|
||||
function createGetMultipleBuyQuotesOperationFromRates(
|
||||
rates: RatesBySource,
|
||||
gasSchedule: Partial<{ [key in ERC20BridgeSource]: BigNumber }> = {},
|
||||
): GetMultipleQuotesOperation {
|
||||
return (
|
||||
sources: ERC20BridgeSource[],
|
||||
_makerToken: string,
|
||||
@@ -258,6 +271,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
s,
|
||||
fillAmounts,
|
||||
rates[s].map(r => new BigNumber(1).div(r)),
|
||||
gasSchedule[s],
|
||||
),
|
||||
);
|
||||
};
|
||||
@@ -334,8 +348,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
poolAddress: randomAddress(),
|
||||
tokens: [TAKER_TOKEN, MAKER_TOKEN],
|
||||
exchangeFunctionSelector: hexUtils.random(4),
|
||||
sellQuoteFunctionSelector: hexUtils.random(4),
|
||||
buyQuoteFunctionSelector: hexUtils.random(4),
|
||||
},
|
||||
fromTokenIdx: 0,
|
||||
toTokenIdx: 1,
|
||||
@@ -345,8 +357,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
poolAddress: randomAddress(),
|
||||
tokens: [TAKER_TOKEN, MAKER_TOKEN],
|
||||
exchangeFunctionSelector: hexUtils.random(4),
|
||||
sellQuoteFunctionSelector: hexUtils.random(4),
|
||||
buyQuoteFunctionSelector: hexUtils.random(4),
|
||||
},
|
||||
fromTokenIdx: 0,
|
||||
toTokenIdx: 1,
|
||||
@@ -356,8 +366,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
poolAddress: randomAddress(),
|
||||
tokens: [TAKER_TOKEN, MAKER_TOKEN],
|
||||
exchangeFunctionSelector: hexUtils.random(4),
|
||||
sellQuoteFunctionSelector: hexUtils.random(4),
|
||||
buyQuoteFunctionSelector: hexUtils.random(4),
|
||||
},
|
||||
fromTokenIdx: 0,
|
||||
toTokenIdx: 1,
|
||||
@@ -367,8 +375,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
poolAddress: randomAddress(),
|
||||
tokens: [TAKER_TOKEN, MAKER_TOKEN],
|
||||
exchangeFunctionSelector: hexUtils.random(4),
|
||||
sellQuoteFunctionSelector: hexUtils.random(4),
|
||||
buyQuoteFunctionSelector: hexUtils.random(4),
|
||||
},
|
||||
fromTokenIdx: 0,
|
||||
toTokenIdx: 1,
|
||||
@@ -378,8 +384,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
poolAddress: randomAddress(),
|
||||
tokens: [TAKER_TOKEN, MAKER_TOKEN],
|
||||
exchangeFunctionSelector: hexUtils.random(4),
|
||||
sellQuoteFunctionSelector: hexUtils.random(4),
|
||||
buyQuoteFunctionSelector: hexUtils.random(4),
|
||||
},
|
||||
fromTokenIdx: 0,
|
||||
toTokenIdx: 1,
|
||||
@@ -455,15 +459,15 @@ describe('MarketOperationUtils tests', () => {
|
||||
FILL_AMOUNT,
|
||||
_.times(NUM_SAMPLES, i => DEFAULT_RATES[ERC20BridgeSource.Native][i]),
|
||||
);
|
||||
const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> = {
|
||||
const DEFAULT_OPTS: GetMarketOrdersOpts = {
|
||||
...DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
numSamples: NUM_SAMPLES,
|
||||
sampleDistributionBase: 1,
|
||||
bridgeSlippage: 0,
|
||||
maxFallbackSlippage: 100,
|
||||
excludedSources: DEFAULT_EXCLUDED,
|
||||
allowFallback: false,
|
||||
gasSchedule: {},
|
||||
feeSchedule: {},
|
||||
gasPrice: GAS_PRICE,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -641,10 +645,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
|
||||
let requestedComparisonPrice: BigNumber | undefined;
|
||||
|
||||
// to get a comparisonPrice, you need a feeschedule for a native order
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Native]: _.constant(new BigNumber(1)),
|
||||
};
|
||||
mockedQuoteRequestor
|
||||
.setup(mqr => mqr.getMakerUriForSignature(TypeMoq.It.isValue(SIGNATURE)))
|
||||
.returns(() => 'https://foo.bar');
|
||||
@@ -703,13 +703,13 @@ describe('MarketOperationUtils tests', () => {
|
||||
mou.getMarketSellLiquidityAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()),
|
||||
)
|
||||
.returns(async () => {
|
||||
return {
|
||||
const marketSellLiquidity: MarketSideLiquidity = {
|
||||
side: MarketOperation.Sell,
|
||||
inputAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
|
||||
inputToken: MAKER_TOKEN,
|
||||
outputToken: TAKER_TOKEN,
|
||||
inputAmountPerEth: Web3Wrapper.toBaseUnitAmount(1, 18),
|
||||
outputAmountPerEth: Web3Wrapper.toBaseUnitAmount(1, 6),
|
||||
inputAmountPerEth: new BigNumber(1), // selling ETH so 1:1
|
||||
outputAmountPerEth: new BigNumber(0.00000000241319391), // buying USDC (6 decimal)
|
||||
quoteSourceFilters: new SourceFilters(),
|
||||
makerTokenDecimals: 6,
|
||||
takerTokenDecimals: 18,
|
||||
@@ -735,14 +735,15 @@ describe('MarketOperationUtils tests', () => {
|
||||
},
|
||||
isRfqSupported: true,
|
||||
};
|
||||
|
||||
return marketSellLiquidity;
|
||||
});
|
||||
const result = await mockedMarketOpUtils.object.getOptimizerResultAsync(
|
||||
ORDERS,
|
||||
Web3Wrapper.toBaseUnitAmount(1, 18),
|
||||
Web3Wrapper.toBaseUnitAmount(10, 18),
|
||||
MarketOperation.Sell,
|
||||
{
|
||||
...DEFAULT_OPTS,
|
||||
feeSchedule,
|
||||
rfqt: {
|
||||
isIndicative: false,
|
||||
apiKey: 'foo',
|
||||
@@ -1066,19 +1067,13 @@ describe('MarketOperationUtils tests', () => {
|
||||
it('factors in fees for native orders', async () => {
|
||||
// Native orders will have the best rates but have fees,
|
||||
// dropping their effective rates.
|
||||
const nativeFeeRate = 0.06;
|
||||
const gasPrice = new BigNumber(1000e9);
|
||||
DEFAULT_DEX_GAS_USED = new BigNumber(100000);
|
||||
const rates: RatesBySource = {
|
||||
[ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, 0.93, 0.92, 0.91]
|
||||
[ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1],
|
||||
[ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1],
|
||||
[ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1],
|
||||
};
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Native]: _.constant(
|
||||
FILL_AMOUNT.div(4)
|
||||
.times(nativeFeeRate)
|
||||
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
|
||||
),
|
||||
[ERC20BridgeSource.Native]: [1, 0.96, 0.94, 0.92], // Effectively [0.98, 0.94, 0.92, 0.90]
|
||||
[ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1], // [0.95]
|
||||
[ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], // [0.94]
|
||||
[ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], // [0.094]
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
@@ -1088,7 +1083,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
marketOperationUtils,
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
FILL_AMOUNT,
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, gasPrice },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
@@ -1104,7 +1099,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
it('factors in fees for dexes', async () => {
|
||||
// Kyber will have the best rates but will have fees,
|
||||
// dropping its effective rates.
|
||||
const uniswapFeeRate = 0.2;
|
||||
const rates: RatesBySource = {
|
||||
[ERC20BridgeSource.Native]: [0.95, 0.1, 0.1, 0.1],
|
||||
[ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1],
|
||||
@@ -1112,13 +1106,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
// Effectively [0.8, ~0.5, ~0, ~0]
|
||||
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
|
||||
};
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Uniswap]: _.constant(
|
||||
FILL_AMOUNT.div(4)
|
||||
.times(uniswapFeeRate)
|
||||
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
|
||||
),
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
@@ -1127,7 +1114,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
marketOperationUtils,
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
FILL_AMOUNT,
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
@@ -1240,10 +1227,12 @@ describe('MarketOperationUtils tests', () => {
|
||||
FILL_AMOUNT,
|
||||
MarketOperation.Sell,
|
||||
{
|
||||
...DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
includedSources: [ERC20BridgeSource.LiquidityProvider],
|
||||
excludedSources: [],
|
||||
numSamples: 4,
|
||||
bridgeSlippage: 0,
|
||||
gasPrice: GAS_PRICE,
|
||||
},
|
||||
);
|
||||
const result = ordersAndReport.optimizedOrders;
|
||||
@@ -1313,15 +1302,14 @@ describe('MarketOperationUtils tests', () => {
|
||||
FILL_AMOUNT,
|
||||
_.times(NUM_SAMPLES, () => DEFAULT_RATES[ERC20BridgeSource.Native][0]),
|
||||
);
|
||||
const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> = {
|
||||
const DEFAULT_OPTS: GetMarketOrdersOpts = {
|
||||
...DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
numSamples: NUM_SAMPLES,
|
||||
sampleDistributionBase: 1,
|
||||
bridgeSlippage: 0,
|
||||
maxFallbackSlippage: 100,
|
||||
excludedSources: DEFAULT_EXCLUDED,
|
||||
allowFallback: false,
|
||||
gasSchedule: {},
|
||||
feeSchedule: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -1528,20 +1516,13 @@ describe('MarketOperationUtils tests', () => {
|
||||
it('factors in fees for native orders', async () => {
|
||||
// Native orders will have the best rates but have fees,
|
||||
// dropping their effective rates.
|
||||
const nativeFeeRate = 0.06;
|
||||
const gasPrice = new BigNumber(1000e9);
|
||||
DEFAULT_DEX_GAS_USED = new BigNumber(100000);
|
||||
const rates: RatesBySource = {
|
||||
...ZERO_RATES,
|
||||
[ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, ~0.93, ~0.92, ~0.91]
|
||||
[ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1],
|
||||
[ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1],
|
||||
[ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1],
|
||||
};
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Native]: _.constant(
|
||||
FILL_AMOUNT.div(4)
|
||||
.times(nativeFeeRate)
|
||||
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
|
||||
),
|
||||
[ERC20BridgeSource.Native]: [1, 0.96, 0.94, 0.92], // Effectively [0.98, 0.94, 0.92, 0.90]
|
||||
[ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1], // [0.95]
|
||||
[ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], // [0.94]
|
||||
[ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], // [0.094]
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
@@ -1551,7 +1532,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
marketOperationUtils,
|
||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
FILL_AMOUNT,
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, gasPrice },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
@@ -1567,7 +1548,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
it('factors in fees for dexes', async () => {
|
||||
// Uniswap will have the best rates but will have fees,
|
||||
// dropping its effective rates.
|
||||
const uniswapFeeRate = 0.2;
|
||||
const rates: RatesBySource = {
|
||||
...ZERO_RATES,
|
||||
[ERC20BridgeSource.Native]: [0.95, 0.1, 0.1, 0.1],
|
||||
@@ -1575,13 +1555,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
|
||||
[ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1],
|
||||
};
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Uniswap]: _.constant(
|
||||
FILL_AMOUNT.div(4)
|
||||
.times(uniswapFeeRate)
|
||||
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
|
||||
),
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||
@@ -1590,7 +1563,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
marketOperationUtils,
|
||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
FILL_AMOUNT,
|
||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||
);
|
||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
@@ -1726,9 +1699,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
signature: SIGNATURE,
|
||||
};
|
||||
const orders = [smallOrder, largeOrder];
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Native]: _.constant(2e5),
|
||||
};
|
||||
|
||||
it('penalizes native fill based on target amount when target is smaller', () => {
|
||||
const path = createFills({
|
||||
@@ -1737,7 +1707,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
dexQuotes: [],
|
||||
targetInput: takerAmount.minus(1),
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
gasPrice: GAS_PRICE,
|
||||
});
|
||||
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker);
|
||||
expect(path[0][0].input).to.be.bignumber.eq(takerAmount.minus(1));
|
||||
@@ -1750,7 +1720,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
dexQuotes: [],
|
||||
targetInput: POSITIVE_INF,
|
||||
outputAmountPerEth,
|
||||
feeSchedule,
|
||||
gasPrice: GAS_PRICE,
|
||||
});
|
||||
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(largeOrder.order.maker);
|
||||
expect((path[0][1].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker);
|
||||
|
@@ -8,6 +8,7 @@ import 'mocha';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../src/types';
|
||||
import { NATIVE_LIMIT_ORDER_GAS_USED, NATIVE_RFQT_GAS_USED } from '../src/utils/market_operation_utils/constants';
|
||||
import {
|
||||
CollapsedFill,
|
||||
DexSample,
|
||||
@@ -34,6 +35,9 @@ import { getRandomAmount, getRandomSignature } from './utils/utils';
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
const ONE = new BigNumber(1);
|
||||
const GAS_USED = ONE;
|
||||
|
||||
function collapsedFillFromNativeOrder(order: NativeOrderWithFillableAmounts): NativeCollapsedFill {
|
||||
const fillData = {
|
||||
order: order.order,
|
||||
@@ -51,6 +55,8 @@ function collapsedFillFromNativeOrder(order: NativeOrderWithFillableAmounts): Na
|
||||
? (fillData as NativeLimitOrderFillData)
|
||||
: (fillData as NativeRfqOrderFillData),
|
||||
subFills: [],
|
||||
gasUsed:
|
||||
order.type === FillQuoteTransformerOrderType.Limit ? NATIVE_LIMIT_ORDER_GAS_USED : NATIVE_RFQT_GAS_USED,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,12 +69,14 @@ describe('generateQuoteReport', async () => {
|
||||
input: new BigNumber(10003),
|
||||
output: new BigNumber(10004),
|
||||
fillData: {},
|
||||
gasUsed: GAS_USED,
|
||||
};
|
||||
const uniswapSample2: DexSample = {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: new BigNumber(10005),
|
||||
output: new BigNumber(10006),
|
||||
fillData: {},
|
||||
gasUsed: GAS_USED,
|
||||
};
|
||||
const orderbookOrder1: NativeOrderWithFillableAmounts = {
|
||||
order: new LimitOrder({ takerAmount: new BigNumber(1000) }),
|
||||
@@ -161,6 +169,7 @@ describe('generateQuoteReport', async () => {
|
||||
fillData: {
|
||||
order: rfqtOrder1.order,
|
||||
} as NativeRfqOrderFillData,
|
||||
gasUsed: NATIVE_RFQT_GAS_USED,
|
||||
};
|
||||
const rfqtOrder2Source: NativeRfqOrderQuoteReportEntry = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
@@ -173,6 +182,7 @@ describe('generateQuoteReport', async () => {
|
||||
fillData: {
|
||||
order: rfqtOrder2.order,
|
||||
} as NativeRfqOrderFillData,
|
||||
gasUsed: NATIVE_RFQT_GAS_USED,
|
||||
};
|
||||
const orderbookOrder2Source: NativeLimitOrderQuoteReportEntry = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
@@ -183,18 +193,21 @@ describe('generateQuoteReport', async () => {
|
||||
fillData: {
|
||||
order: orderbookOrder2.order,
|
||||
} as NativeLimitOrderFillData,
|
||||
gasUsed: NATIVE_LIMIT_ORDER_GAS_USED,
|
||||
};
|
||||
const uniswap2Source: BridgeQuoteReportEntry = {
|
||||
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||
makerAmount: uniswapSample2.output,
|
||||
takerAmount: uniswapSample2.input,
|
||||
fillData: {},
|
||||
gasUsed: GAS_USED,
|
||||
};
|
||||
const kyber2Source: BridgeQuoteReportEntry = {
|
||||
liquiditySource: ERC20BridgeSource.Kyber,
|
||||
makerAmount: kyberSample2.output,
|
||||
takerAmount: kyberSample2.input,
|
||||
fillData: {},
|
||||
gasUsed: GAS_USED,
|
||||
};
|
||||
|
||||
const expectedSourcesConsidered: QuoteReportEntry[] = [rfqtOrder1Source, rfqtOrder2Source];
|
||||
@@ -215,12 +228,14 @@ describe('generateQuoteReport', async () => {
|
||||
input: new BigNumber(10000),
|
||||
output: new BigNumber(10001),
|
||||
fillData: {},
|
||||
gasUsed: GAS_USED,
|
||||
};
|
||||
const uniswapSample1: DexSample = {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: new BigNumber(10003),
|
||||
output: new BigNumber(10004),
|
||||
fillData: {},
|
||||
gasUsed: GAS_USED,
|
||||
};
|
||||
const orderbookOrder1: NativeOrderWithFillableAmounts = {
|
||||
order: new LimitOrder({ takerAmount: new BigNumber(1101) }),
|
||||
@@ -267,18 +282,21 @@ describe('generateQuoteReport', async () => {
|
||||
fillData: {
|
||||
order: orderbookOrder1.order,
|
||||
} as NativeLimitOrderFillData,
|
||||
gasUsed: NATIVE_LIMIT_ORDER_GAS_USED,
|
||||
};
|
||||
const uniswap1Source: BridgeQuoteReportEntry = {
|
||||
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||
makerAmount: uniswapSample1.input,
|
||||
takerAmount: uniswapSample1.output,
|
||||
fillData: {},
|
||||
gasUsed: GAS_USED,
|
||||
};
|
||||
const kyber1Source: BridgeQuoteReportEntry = {
|
||||
liquiditySource: ERC20BridgeSource.Kyber,
|
||||
makerAmount: kyberSample1.input,
|
||||
takerAmount: kyberSample1.output,
|
||||
fillData: {},
|
||||
gasUsed: GAS_USED,
|
||||
};
|
||||
|
||||
// No order is considered here because only Native RFQ orders are considered.
|
||||
@@ -303,15 +321,15 @@ describe('generateQuoteReport', async () => {
|
||||
source: ERC20BridgeSource.Balancer,
|
||||
fillData: {},
|
||||
encodeCall: () => '',
|
||||
handleCallResults: _callResults => [new BigNumber(1337)],
|
||||
handleRevert: _c => [],
|
||||
handleCallResults: _callResults => ({ gasUsed: [], samples: [new BigNumber(1337)] }),
|
||||
handleRevert: _c => ({ gasUsed: [], samples: [] }),
|
||||
},
|
||||
secondHopSource: {
|
||||
source: ERC20BridgeSource.Curve,
|
||||
fillData: {},
|
||||
encodeCall: () => '',
|
||||
handleCallResults: _callResults => [new BigNumber(1337)],
|
||||
handleRevert: _c => [],
|
||||
handleCallResults: _callResults => ({ gasUsed: [], samples: [new BigNumber(1337)] }),
|
||||
handleRevert: _c => ({ gasUsed: [], samples: [] }),
|
||||
},
|
||||
};
|
||||
const twoHopSample: DexSample<MultiHopFillData> = {
|
||||
@@ -319,6 +337,7 @@ describe('generateQuoteReport', async () => {
|
||||
input: new BigNumber(3005),
|
||||
output: new BigNumber(3006),
|
||||
fillData: twoHopFillData,
|
||||
gasUsed: GAS_USED,
|
||||
};
|
||||
|
||||
const orderReport = generateQuoteReport(marketOperation, [orderbookOrder1], twoHopSample);
|
||||
@@ -328,6 +347,7 @@ describe('generateQuoteReport', async () => {
|
||||
takerAmount: twoHopSample.input,
|
||||
hopSources: [ERC20BridgeSource.Balancer, ERC20BridgeSource.Curve],
|
||||
fillData: twoHopFillData,
|
||||
gasUsed: GAS_USED,
|
||||
};
|
||||
|
||||
// No entry is present in considered because No RFQ orders were reported.
|
||||
@@ -356,12 +376,12 @@ function expectEqualQuoteReportEntries(
|
||||
expect(actualEntry.fillData.order).to.eql(
|
||||
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||
(expectedEntry.fillData as NativeFillData).order,
|
||||
`${variableName} incorrect at index ${idx}`,
|
||||
`${actualEntry.liquiditySource} ${variableName} incorrect at index ${idx}`,
|
||||
);
|
||||
}
|
||||
expect(_.omit(actualEntry, 'fillData')).to.eql(
|
||||
_.omit(expectedEntry, 'fillData'),
|
||||
`${variableName} incorrect at index ${idx}`,
|
||||
`${actualEntry.liquiditySource} ${variableName} incorrect at index ${idx}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import { BigNumber, hexUtils, NULL_BYTES } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { MarketOperation } from '../src/types';
|
||||
import { NATIVE_LIMIT_ORDER_GAS_USED } from '../src/utils/market_operation_utils/constants';
|
||||
import {
|
||||
CollapsedFill,
|
||||
ERC20BridgeSource,
|
||||
@@ -26,8 +27,6 @@ describe('quote_simulation tests', async () => {
|
||||
const ONE = new BigNumber(1);
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const GAS_SCHEDULE = { [ERC20BridgeSource.Uniswap]: _.constant(1), [ERC20BridgeSource.Native]: _.constant(1) };
|
||||
|
||||
// Check if two numbers are within `maxError` error rate within each other.
|
||||
function assertRoughlyEquals(n1: BigNumber, n2: BigNumber, maxError: BigNumber | number = 1e-10): void {
|
||||
// |n2-n1| / max(|n1|, |n2|)
|
||||
@@ -161,6 +160,7 @@ describe('quote_simulation tests', async () => {
|
||||
},
|
||||
type,
|
||||
fills: createOrderCollapsedFills(fillableInput, fillableOutput, fillsCount),
|
||||
gasUsed: NATIVE_LIMIT_ORDER_GAS_USED,
|
||||
};
|
||||
return order;
|
||||
}
|
||||
@@ -182,6 +182,7 @@ describe('quote_simulation tests', async () => {
|
||||
input: subFillInputs[j],
|
||||
output: subFillOutputs[j],
|
||||
})),
|
||||
gasUsed: new BigNumber(1),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -247,7 +248,7 @@ describe('quote_simulation tests', async () => {
|
||||
fillsCount,
|
||||
count: 1,
|
||||
});
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||
@@ -269,7 +270,7 @@ describe('quote_simulation tests', async () => {
|
||||
count: 1,
|
||||
});
|
||||
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
expect(totalFilledInput).to.bignumber.eq(inputFillAmount);
|
||||
@@ -295,7 +296,7 @@ describe('quote_simulation tests', async () => {
|
||||
count: 1,
|
||||
});
|
||||
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
expect(totalFilledInput).to.bignumber.eq(inputFillAmount);
|
||||
@@ -318,7 +319,7 @@ describe('quote_simulation tests', async () => {
|
||||
count: 1,
|
||||
});
|
||||
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||
@@ -343,7 +344,7 @@ describe('quote_simulation tests', async () => {
|
||||
});
|
||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, totalFillableInput, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, totalFillableInput, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, totalFillableInput);
|
||||
@@ -370,7 +371,7 @@ describe('quote_simulation tests', async () => {
|
||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
||||
const inputFillAmount = totalFillableInput.times(2 / 3).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, inputFillAmount);
|
||||
@@ -397,7 +398,7 @@ describe('quote_simulation tests', async () => {
|
||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
||||
const inputFillAmount = totalFillableInput.times(3 / 2).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, totalFillableInput);
|
||||
@@ -423,7 +424,7 @@ describe('quote_simulation tests', async () => {
|
||||
});
|
||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, fillableInput);
|
||||
@@ -450,7 +451,7 @@ describe('quote_simulation tests', async () => {
|
||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
||||
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, inputFillAmount);
|
||||
@@ -477,7 +478,7 @@ describe('quote_simulation tests', async () => {
|
||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
||||
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, fillableInput);
|
||||
@@ -500,7 +501,7 @@ describe('quote_simulation tests', async () => {
|
||||
count: 1,
|
||||
type: FillQuoteTransformerOrderType.Rfq,
|
||||
});
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||
@@ -516,7 +517,7 @@ describe('quote_simulation tests', async () => {
|
||||
const fillableInput = getRandomOrderSize();
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const fillOrders = createQuoteFillOrders({ fillableInput, fillableOutput, side });
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||
@@ -531,7 +532,7 @@ describe('quote_simulation tests', async () => {
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
||||
const fillOrders = createQuoteFillOrders({ fillableInput, fillableOutput, side });
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
expect(totalFilledInput).to.bignumber.eq(inputFillAmount);
|
||||
@@ -545,7 +546,7 @@ describe('quote_simulation tests', async () => {
|
||||
const fillableOutput = getRandomOrderSize();
|
||||
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
||||
const fillOrders = createQuoteFillOrders({ fillableInput, fillableOutput, side });
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||
@@ -567,7 +568,7 @@ describe('quote_simulation tests', async () => {
|
||||
});
|
||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, totalFillableInput, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, totalFillableInput, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, totalFillableInput);
|
||||
@@ -591,7 +592,7 @@ describe('quote_simulation tests', async () => {
|
||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
||||
const inputFillAmount = totalFillableInput.times(2 / 3).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, inputFillAmount);
|
||||
@@ -615,7 +616,7 @@ describe('quote_simulation tests', async () => {
|
||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
||||
const inputFillAmount = totalFillableInput.times(3 / 2).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, totalFillableInput);
|
||||
@@ -638,7 +639,7 @@ describe('quote_simulation tests', async () => {
|
||||
});
|
||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, fillableInput);
|
||||
@@ -662,7 +663,7 @@ describe('quote_simulation tests', async () => {
|
||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
||||
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, inputFillAmount);
|
||||
@@ -686,7 +687,7 @@ describe('quote_simulation tests', async () => {
|
||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
||||
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
||||
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE);
|
||||
const totalFilledInput = result.input.plus(result.inputFee);
|
||||
const totalFilledOutput = result.output.plus(result.outputFee);
|
||||
assertRoughlyEquals(totalFilledInput, fillableInput);
|
||||
@@ -744,7 +745,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: {},
|
||||
});
|
||||
if (side === MarketOperation.Sell) {
|
||||
expect(result.totalMakerAssetAmount).to.be.bignumber.eq(fillableOutput);
|
||||
@@ -769,7 +770,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: {},
|
||||
});
|
||||
expect(result.gas).to.eq(countCollapsedFills(orders));
|
||||
expect(result.protocolFeeAmount).to.bignumber.gt(orders.length);
|
||||
@@ -801,7 +802,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: inputFillAmount,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: {},
|
||||
});
|
||||
expect(result.gas).to.gt(0);
|
||||
expect(result.protocolFeeAmount).to.bignumber.gt(0);
|
||||
@@ -835,7 +836,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: totalFillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: {},
|
||||
});
|
||||
|
||||
assertRoughlyEquals(result.takerAssetAmount, fillableInput);
|
||||
@@ -865,7 +866,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: inputFillAmount,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: {},
|
||||
});
|
||||
expect(result.gas).to.gt(0);
|
||||
expect(result.protocolFeeAmount).to.bignumber.gt(0);
|
||||
@@ -893,7 +894,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: {},
|
||||
});
|
||||
expect(result.gas).to.eq(countCollapsedFills(orders));
|
||||
expect(result.protocolFeeAmount).to.bignumber.gt(orders.length);
|
||||
@@ -923,7 +924,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: inputFillAmount,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: {},
|
||||
});
|
||||
expect(result.gas).to.gt(0);
|
||||
expect(result.protocolFeeAmount).to.bignumber.gt(0);
|
||||
@@ -951,7 +952,7 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE, slippage },
|
||||
opts: { slippage },
|
||||
});
|
||||
if (side === MarketOperation.Sell) {
|
||||
const slippedOutput = fillableOutput.times(1 - slippage).integerValue();
|
||||
@@ -982,14 +983,14 @@ describe('quote_simulation tests', async () => {
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE },
|
||||
opts: {},
|
||||
});
|
||||
const worstCase = simulateWorstCaseFill({
|
||||
orders,
|
||||
side,
|
||||
fillAmount: fillableInput,
|
||||
gasPrice: ONE,
|
||||
opts: { gasSchedule: GAS_SCHEDULE, slippage: orderSlippage },
|
||||
opts: { slippage: orderSlippage },
|
||||
});
|
||||
const bestPrice = bestCase.makerAssetAmount.div(bestCase.totalTakerAssetAmount);
|
||||
const worstPrice = worstCase.makerAssetAmount.div(worstCase.totalTakerAssetAmount);
|
||||
|
@@ -14,7 +14,7 @@ export type GetOrderFillableAssetAmountHandler = (
|
||||
devUtilsAddress: string,
|
||||
) => GetOrderFillableAssetAmountResult;
|
||||
|
||||
export type SampleResults = BigNumber[];
|
||||
export type SampleResults = [BigNumber[], BigNumber[]];
|
||||
export type SampleSellsUniswapHandler = (
|
||||
router: string,
|
||||
takerToken: string,
|
||||
@@ -44,22 +44,22 @@ export type SampleSellsKyberHandler = (
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerTokenAmounts: BigNumber[],
|
||||
) => [string, string, SampleResults];
|
||||
) => [string, string, BigNumber[], BigNumber[]];
|
||||
export type SampleBuysKyberHandler = (
|
||||
reserveId: string,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerTokenAmounts: BigNumber[],
|
||||
) => [string, SampleResults];
|
||||
) => [string, string, BigNumber[], BigNumber[]];
|
||||
export type SampleUniswapV2Handler = (router: string, path: string[], assetAmounts: BigNumber[]) => SampleResults;
|
||||
export type SampleBuysMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
|
||||
export type SampleBuysMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => BigNumber[];
|
||||
export type SampleSellsLPHandler = (
|
||||
providerAddress: string,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerTokenAmounts: BigNumber[],
|
||||
) => SampleResults;
|
||||
export type SampleSellsMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
|
||||
export type SampleSellsMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => BigNumber[];
|
||||
|
||||
const DUMMY_PROVIDER = {
|
||||
sendAsync: (..._args: any[]): any => {
|
||||
@@ -130,7 +130,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractTxFunctionObj<[string, string, BigNumber[]]> {
|
||||
): ContractTxFunctionObj<[string, string, BigNumber[], BigNumber[]]> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromKyberNetwork,
|
||||
this._handlers.sampleSellsFromKyberNetwork,
|
||||
@@ -146,7 +146,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractTxFunctionObj<BigNumber[]> {
|
||||
): ContractTxFunctionObj<SampleResults> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromEth2Dai,
|
||||
this._handlers.sampleSellsFromEth2Dai,
|
||||
@@ -162,7 +162,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractTxFunctionObj<BigNumber[]> {
|
||||
): ContractTxFunctionObj<SampleResults> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromUniswap,
|
||||
this._handlers.sampleSellsFromUniswap,
|
||||
@@ -177,7 +177,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
router: string,
|
||||
path: string[],
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractTxFunctionObj<BigNumber[]> {
|
||||
): ContractTxFunctionObj<SampleResults> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromUniswapV2,
|
||||
this._handlers.sampleSellsFromUniswapV2,
|
||||
@@ -192,7 +192,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractTxFunctionObj<BigNumber[]> {
|
||||
): ContractTxFunctionObj<SampleResults> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromLiquidityProvider,
|
||||
this._handlers.sampleSellsFromLiquidityProvider,
|
||||
@@ -208,7 +208,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerAssetAmounts: BigNumber[],
|
||||
): ContractTxFunctionObj<BigNumber[]> {
|
||||
): ContractTxFunctionObj<SampleResults> {
|
||||
return this._wrapCall(
|
||||
super.sampleBuysFromEth2Dai,
|
||||
this._handlers.sampleBuysFromEth2Dai,
|
||||
@@ -224,7 +224,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerAssetAmounts: BigNumber[],
|
||||
): ContractTxFunctionObj<BigNumber[]> {
|
||||
): ContractTxFunctionObj<SampleResults> {
|
||||
return this._wrapCall(
|
||||
super.sampleBuysFromUniswap,
|
||||
this._handlers.sampleBuysFromUniswap,
|
||||
@@ -239,7 +239,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
router: string,
|
||||
path: string[],
|
||||
makerAssetAmounts: BigNumber[],
|
||||
): ContractTxFunctionObj<BigNumber[]> {
|
||||
): ContractTxFunctionObj<SampleResults> {
|
||||
return this._wrapCall(
|
||||
super.sampleBuysFromUniswapV2,
|
||||
this._handlers.sampleBuysFromUniswapV2,
|
||||
|
@@ -3,28 +3,24 @@
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../test/generated-wrappers/approximate_buys';
|
||||
export * from '../test/generated-wrappers/balance_checker';
|
||||
export * from '../test/generated-wrappers/balancer_sampler';
|
||||
export * from '../test/generated-wrappers/balancer_v2_sampler';
|
||||
export * from '../test/generated-wrappers/bancor_sampler';
|
||||
export * from '../test/generated-wrappers/booster_sampler';
|
||||
export * from '../test/generated-wrappers/curve_sampler';
|
||||
export * from '../test/generated-wrappers/curve_v2_sampler';
|
||||
export * from '../test/generated-wrappers/d_o_d_o_sampler';
|
||||
export * from '../test/generated-wrappers/d_o_d_o_v2_sampler';
|
||||
export * from '../test/generated-wrappers/delegate_hacked_erc20';
|
||||
export * from '../test/generated-wrappers/dummy_liquidity_provider';
|
||||
export * from '../test/generated-wrappers/erc20_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/eth2_dai_sampler';
|
||||
export * from '../test/generated-wrappers/fake_taker';
|
||||
export * from '../test/generated-wrappers/i_balancer';
|
||||
export * from '../test/generated-wrappers/i_bancor';
|
||||
export * from '../test/generated-wrappers/i_curve';
|
||||
export * from '../test/generated-wrappers/gas_overhead';
|
||||
export * from '../test/generated-wrappers/hacked_erc20';
|
||||
export * from '../test/generated-wrappers/i_eth2_dai';
|
||||
export * from '../test/generated-wrappers/i_kyber_network';
|
||||
export * from '../test/generated-wrappers/i_m_stable';
|
||||
export * from '../test/generated-wrappers/i_mooniswap';
|
||||
export * from '../test/generated-wrappers/i_multi_bridge';
|
||||
export * from '../test/generated-wrappers/i_shell';
|
||||
export * from '../test/generated-wrappers/i_smoothy';
|
||||
export * from '../test/generated-wrappers/i_uniswap_exchange_quotes';
|
||||
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
|
||||
export * from '../test/generated-wrappers/kyber_dmm_sampler';
|
||||
@@ -34,11 +30,9 @@ export * from '../test/generated-wrappers/liquidity_provider_sampler';
|
||||
export * from '../test/generated-wrappers/m_stable_sampler';
|
||||
export * from '../test/generated-wrappers/maker_p_s_m_sampler';
|
||||
export * from '../test/generated-wrappers/mooniswap_sampler';
|
||||
export * from '../test/generated-wrappers/multi_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/native_order_sampler';
|
||||
export * from '../test/generated-wrappers/sampler_utils';
|
||||
export * from '../test/generated-wrappers/shell_sampler';
|
||||
export * from '../test/generated-wrappers/smoothy_sampler';
|
||||
export * from '../test/generated-wrappers/swap_revert_sampler';
|
||||
export * from '../test/generated-wrappers/test_erc20_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/test_native_order_sampler';
|
||||
export * from '../test/generated-wrappers/two_hop_sampler';
|
||||
|
@@ -4,30 +4,29 @@
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||
"files": [
|
||||
"generated-artifacts/BalanceChecker.json",
|
||||
"generated-artifacts/DelegateHackedERC20.json",
|
||||
"generated-artifacts/ERC20BridgeSampler.json",
|
||||
"generated-artifacts/FakeTaker.json",
|
||||
"test/generated-artifacts/ApproximateBuys.json",
|
||||
"generated-artifacts/GasOverhead.json",
|
||||
"generated-artifacts/HackedERC20.json",
|
||||
"test/generated-artifacts/BalanceChecker.json",
|
||||
"test/generated-artifacts/BalancerSampler.json",
|
||||
"test/generated-artifacts/BalancerV2Sampler.json",
|
||||
"test/generated-artifacts/BancorSampler.json",
|
||||
"test/generated-artifacts/BoosterSampler.json",
|
||||
"test/generated-artifacts/CurveSampler.json",
|
||||
"test/generated-artifacts/CurveV2Sampler.json",
|
||||
"test/generated-artifacts/DODOSampler.json",
|
||||
"test/generated-artifacts/DODOV2Sampler.json",
|
||||
"test/generated-artifacts/DelegateHackedERC20.json",
|
||||
"test/generated-artifacts/DummyLiquidityProvider.json",
|
||||
"test/generated-artifacts/ERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/Eth2DaiSampler.json",
|
||||
"test/generated-artifacts/FakeTaker.json",
|
||||
"test/generated-artifacts/IBalancer.json",
|
||||
"test/generated-artifacts/IBancor.json",
|
||||
"test/generated-artifacts/ICurve.json",
|
||||
"test/generated-artifacts/GasOverhead.json",
|
||||
"test/generated-artifacts/HackedERC20.json",
|
||||
"test/generated-artifacts/IEth2Dai.json",
|
||||
"test/generated-artifacts/IKyberNetwork.json",
|
||||
"test/generated-artifacts/IMStable.json",
|
||||
"test/generated-artifacts/IMooniswap.json",
|
||||
"test/generated-artifacts/IMultiBridge.json",
|
||||
"test/generated-artifacts/IShell.json",
|
||||
"test/generated-artifacts/ISmoothy.json",
|
||||
"test/generated-artifacts/IUniswapExchangeQuotes.json",
|
||||
"test/generated-artifacts/IUniswapV2Router01.json",
|
||||
"test/generated-artifacts/KyberDmmSampler.json",
|
||||
@@ -37,11 +36,9 @@
|
||||
"test/generated-artifacts/MStableSampler.json",
|
||||
"test/generated-artifacts/MakerPSMSampler.json",
|
||||
"test/generated-artifacts/MooniswapSampler.json",
|
||||
"test/generated-artifacts/MultiBridgeSampler.json",
|
||||
"test/generated-artifacts/NativeOrderSampler.json",
|
||||
"test/generated-artifacts/SamplerUtils.json",
|
||||
"test/generated-artifacts/ShellSampler.json",
|
||||
"test/generated-artifacts/SmoothySampler.json",
|
||||
"test/generated-artifacts/SwapRevertSampler.json",
|
||||
"test/generated-artifacts/TestERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/TestNativeOrderSampler.json",
|
||||
"test/generated-artifacts/TwoHopSampler.json",
|
||||
|
@@ -131,6 +131,7 @@ export enum BridgeProtocol {
|
||||
KyberDmm,
|
||||
CurveV2,
|
||||
Lido,
|
||||
Booster,
|
||||
}
|
||||
// tslint:enable: enum-naming
|
||||
|
||||
|
Reference in New Issue
Block a user