Compare commits

...

6 Commits

Author SHA1 Message Date
Jacob Evans
292ce6ea84 poc: rust router 2021-07-03 13:28:36 +10:00
Jacob Evans
7972c2ce4e Fix buys exit early 2021-06-25 19:43:04 +10:00
Jacob Evans
173d4ce648 CHANGELOGs 2021-06-25 18:52:34 +10:00
Jacob Evans
59542f0585 Fix test which should be gas price adjusted 2021-06-25 18:06:56 +10:00
Jacob Evans
446ef9660e Rebased off development 2021-06-25 17:57:26 +10:00
Jacob Evans
9de1f0263a Swap Sampler
- Uses the settlement function (via Mixin) to perform sampling
- Removed the Gas schedule as gasUsed is recorded when sampling
- Remove most function selectors for Curve pools (only exchange is required)
- Allow banning of routes if they often return 0
- Introduce CurveV2Sampler required for SwapRevert sampler
2021-06-25 17:57:26 +10:00
85 changed files with 3384 additions and 3633 deletions

View File

@@ -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": [

View File

@@ -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;

View File

@@ -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);

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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).

View File

@@ -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");

View File

@@ -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}();
}
}
}

View File

@@ -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");

View File

@@ -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",

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
);
}
}

View File

@@ -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)
{
}

View File

@@ -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
);
}
}

View 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
);
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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,

View 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)
{
}
}

View File

@@ -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.
@@ -24,6 +24,7 @@ import "./BalancerSampler.sol";
import "./BalancerV2Sampler.sol";
import "./BancorSampler.sol";
import "./CurveSampler.sol";
import "./CurveV2Sampler.sol";
import "./DODOSampler.sol";
import "./DODOV2Sampler.sol";
import "./Eth2DaiSampler.sol";
@@ -32,24 +33,24 @@ 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,
CurveSampler,
CurveV2Sampler,
DODOSampler,
DODOV2Sampler,
Eth2DaiSampler,
@@ -60,10 +61,8 @@ contract ERC20BridgeSampler is
MakerPSMSampler,
MStableSampler,
MooniswapSampler,
MultiBridgeSampler,
NativeOrderSampler,
ShellSampler,
SmoothySampler,
TwoHopSampler,
UniswapSampler,
UniswapV2Sampler,
@@ -76,11 +75,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);

View File

@@ -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
);
}
}

View 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;
}
}

View 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);
}
}

View File

@@ -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(

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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 { }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}
}

View File

@@ -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];
}
}

View 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;
}
}

View File

@@ -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);

View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

View File

@@ -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;
}

View File

@@ -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)
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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|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": []
}

View File

@@ -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,
};

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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
@@ -577,8 +566,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 +574,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 +582,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 +590,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 +598,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 +606,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 +887,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 +894,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 +904,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 +923,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 +933,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 +943,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 +953,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 +1104,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 +1370,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 +1387,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),
};

View File

@@ -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,10 +161,15 @@ 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`);
}
let penalty = ZERO_AMOUNT;
if (i === 0) {
// Only the first fill in a DEX path incurs a penalty.
const fee = gasPrice.times(sample.gasUsed);
penalty = !outputAmountPerEth.isZero()
? outputAmountPerEth.times(fee)
: inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input));
@@ -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;

View File

@@ -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,14 +198,15 @@ 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,
);
const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
@@ -232,35 +260,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 +307,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 +360,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 +438,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 +502,7 @@ export class MarketOperationUtils {
outputAmountPerEth,
inputAmountPerEth,
excludedSources: opts.excludedSources,
feeSchedule: opts.feeSchedule,
gasPrice: opts.gasPrice,
});
// Find the optimal path.
@@ -490,7 +526,7 @@ export class MarketOperationUtils {
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
marketSideLiquidity,
opts.feeSchedule,
opts.gasPrice,
opts.exchangeProxyOverhead,
);
if (bestTwoHopQuote && bestTwoHopRate.isGreaterThan(optimalPathRate)) {
@@ -558,16 +594,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 +614,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 +635,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 +732,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 +742,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 +762,61 @@ 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] };
});
return { overrides };
}
}
// tslint:disable: max-file-line-count

View File

@@ -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 || false;
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, params: this._params, ...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;
}
}

View File

@@ -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],

View File

@@ -3,7 +3,7 @@ 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,
@@ -61,6 +61,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 +71,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),
@@ -329,6 +331,7 @@ export function createBridgeOrder(
sourcePathId: fill.sourcePathId,
type: FillQuoteTransformerOrderType.Bridge,
fills: [fill],
gasUsed: fill.gasUsed,
};
}
@@ -487,6 +490,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,
};
}

View File

@@ -230,7 +230,7 @@ export class Path {
return true;
}
private _collapseFills(): ReadonlyArray<CollapsedFill> {
public _collapseFills(): ReadonlyArray<CollapsedFill> {
this.collapsedFills = [];
for (const fill of this.fills) {
const source = fill.source;
@@ -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],
});
}

View File

@@ -1,5 +1,6 @@
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { performance } from 'perf_hooks';
import { MarketOperation } from '../../types';
@@ -10,6 +11,180 @@ import { ERC20BridgeSource, Fill } from './types';
const RUN_LIMIT_DECAY_FACTOR = 0.5;
const util = require('util');
const { route } = require('neon-router');
const inputAccum = (f: Fill): BigNumber => (f.parent ? f.input.plus(inputAccum(f.parent)) : f.input);
const adjustedOutputAccum = (f: Fill): BigNumber => {
if (!f.parent) {
return f.adjustedOutput;
}
const adjustedOutputParentAcc = adjustedOutputAccum(f.parent);
const feePerc = f.parent.adjustedOutput.dividedBy(f.parent.output);
return feePerc.times(f.output).plus(adjustedOutputParentAcc);
};
export function findOptimalRustPath(input: BigNumber, allFills: Fill[][], optimal: Path): Path {
// Track sample id's to integers (required by rust router)
const sampleIdLookup: { [key: string]: number } = {};
let sampleIdCounter = 0;
const fillToSampleId = (s: { source: string; sourcePathId: string; index: number }): number => {
const key = `${s.source}-${s.sourcePathId}-${s.index}`;
if (sampleIdLookup[key]) {
return sampleIdLookup[key];
} else {
sampleIdLookup[key] = ++sampleIdCounter;
return sampleIdLookup[key];
}
};
const adjustedParsedFills = allFills.map(fills => {
const adjustedFills: Fill[] = [];
// Samples are turned into Fills
// Fills are dependent on their parent and have their parents information "subtracted" from them
// e.g a samples for [1,10,100] => [5,50,500] look like [1, 9, 91] => [5, 40, 400]
for (let i = 0; i < fills.length; i++) {
const parsedFill: Fill = { ...fills[i] };
if (parsedFill.index !== 0) {
const parent = adjustedFills[i - 1];
parsedFill.parent = parent;
parsedFill.input = parsedFill.input.plus(parent.input);
parsedFill.output = parsedFill.output.plus(parent.output);
// Adjusted output is only modified for the first fill
const feePerc = parent.adjustedOutput.dividedBy(parent.output);
parsedFill.adjustedOutput = feePerc.times(parsedFill.output);
}
adjustedFills.push(parsedFill);
}
return adjustedFills;
});
const pathsIn = adjustedParsedFills.map((fs: any) => ({
ids: fs.map((f: any) => fillToSampleId(f)),
inputs: fs.map((f: any) => parseInt(f.input.toString())),
outputs: fs.map((f: any, i: number) => parseInt(f.adjustedOutput.toString())),
}));
const pathOut = {
ids: optimal.fills.map(s => fillToSampleId(s)),
inputs: optimal.fills.map(s => parseInt(inputAccum(s).toString())),
outputs: optimal.fills.map(s => parseInt(adjustedOutputAccum(s).toString())),
};
const rustArgs = {
side: 'Sell',
// HACK: There can be off by 1 errors, somewhere...
targetInput: parseInt(input.plus(1).toString()),
pathsIn,
pathOut,
};
// console.log(util.inspect({ rustArgs }, { depth: null }));
const before = performance.now();
const rustRoute: number[] = route(rustArgs);
console.log('Rust perf (real):', performance.now() - before);
// Our route as input and perc
const optimalFillsByPathId = _.groupBy(optimal._collapseFills(), o => o.sourcePathId);
const fillsByPathId = _.groupBy(_.flatten(adjustedParsedFills), o => o.sourcePathId);
const out: BigNumber[] = [];
const outPerc: BigNumber[] = [];
for (const sourcePathId of Object.keys(fillsByPathId)) {
const fs = optimalFillsByPathId[sourcePathId];
if (fs) {
outPerc.push(
fs
.reduce((prev, curr) => prev.plus(curr.input), new BigNumber(0))
.dividedBy(input)
.times(100),
);
out.push(fs.reduce((prev, curr) => prev.plus(curr.input), new BigNumber(0)));
} else {
outPerc.push(new BigNumber(0));
out.push(new BigNumber(0));
}
}
const sourcePathKeys = Object.keys(fillsByPathId);
const fakeFills: Fill[] = [];
const totalInputs = BigNumber.sum(...rustRoute);
for (let i = 0; i < rustRoute.length; i++) {
if (rustRoute[i] === 0) {
continue;
}
const rustInput = new BigNumber(rustRoute[i]);
// HACK: Handle the case where the router can under quote the input
// Set the first fill just a tad higher
const adjInput =
totalInputs.lt(input) && fakeFills.length === 0 ? rustInput.plus(input.minus(totalInputs)) : rustInput;
// Rust router has chosen this source;
const sourcePathKey = sourcePathKeys[i];
const fills = fillsByPathId[sourcePathKey];
let fill = fills[fills.length - 1];
// Descend to approach a closer fill for fillData which may not be consistent
// throughout the path (UniswapV3) and for a closer guesstimate at
// gas used
for (let k = fills.length - 1; k >= 0; k--) {
if (k === 0) {
fill = fills[0];
}
if (rustInput.isGreaterThan(fills[k].input)) {
// Between here and the previous fill
// HACK: Use the midpoint between the two
const left = fills[k];
const right = fills[k + 1];
if (left && right) {
const midPrice = left.output
.dividedBy(left.input)
.plus(right.output.dividedBy(right.input))
.dividedBy(2);
fill = {
...right, // default to the greater (for gas used)
input: rustInput,
output: midPrice.times(rustInput).decimalPlaces(0),
};
} else {
fill = left || right;
}
break;
}
}
const adjustedOutput = fill.output
.dividedBy(fill.input)
.times(adjInput)
.decimalPlaces(0);
fakeFills.push({
...fill,
input: adjInput,
output: adjustedOutput,
index: 0,
parent: undefined,
});
}
const fakePath = Path.create(MarketOperation.Sell, fakeFills, input);
console.log(
util.inspect(
{
// fillsByPathId: Object.keys(fillsByPathId).map(k => `${fillsByPathId[k][0].source}-${k}`),
rustPerc: rustRoute.map(n =>
new BigNumber(n)
.dividedBy(input)
.times(100)
.decimalPlaces(2),
),
ourPerc: outPerc.map(o => o.decimalPlaces(2)),
// rustRoute,
// fakePath: fakePath._collapseFills(),
},
{ depth: null },
),
);
return fakePath;
}
/**
* Find the optimal mixture of fills that maximizes (for sells) or minimizes
* (for buys) output, while meeting the input requirement.
@@ -21,6 +196,7 @@ export async function findOptimalPathAsync(
runLimit: number = 2 ** 8,
opts: PathPenaltyOpts = DEFAULT_PATH_PENALTY_OPTS,
): Promise<Path | undefined> {
let before = performance.now();
// Sort fill arrays by descending adjusted completed rate.
// Remove any paths which cannot impact the optimal path
const sortedPaths = reducePaths(fillsToSortedPaths(fills, side, targetInput, opts), side);
@@ -34,6 +210,15 @@ export async function findOptimalPathAsync(
// Yield to event loop.
await Promise.resolve();
}
console.log('TS perf:', performance.now() - before);
before = performance.now();
const rustPath = findOptimalRustPath(targetInput, fills, optimalPath);
console.log('Rust perf:', performance.now() - before);
if (process.env.RUST === 'true') {
return rustPath;
}
return optimalPath.isComplete() ? optimalPath : undefined;
}

View File

@@ -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);
}

View File

@@ -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,11 +142,12 @@ 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)) {
@@ -157,11 +159,12 @@ export class DexOrderSampler extends SamplerOperations {
.callAsync({ overrides }, block);
// 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;
}
}

View File

@@ -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 [];
}
}

View File

@@ -97,21 +97,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 +112,6 @@ export enum CurveFunctionSelectors {
*/
export interface CurveInfo {
exchangeFunctionSelector: CurveFunctionSelectors;
sellQuoteFunctionSelector: CurveFunctionSelectors;
buyQuoteFunctionSelector: CurveFunctionSelectors;
poolAddress: string;
tokens: string[];
metaTokens: string[] | undefined;
@@ -167,7 +157,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 +215,8 @@ export interface GenericRouterFillData extends FillData {
}
export interface MultiHopFillData extends FillData {
firstHopSource: SourceQuoteOperation;
secondHopSource: SourceQuoteOperation;
firstHopSource: MeasuredSourceQuoteOperation;
secondHopSource: MeasuredSourceQuoteOperation;
intermediateToken: string;
}
@@ -285,6 +277,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 +306,7 @@ export interface CollapsedFill<TFillData extends FillData = FillData> {
input: BigNumber;
output: BigNumber;
}>;
gasUsed: BigNumber;
}
/**
@@ -329,6 +323,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 +331,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 +357,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 +410,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 +437,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 +452,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 +532,10 @@ export interface GenerateOptimizedOrdersOpts {
bridgeSlippage?: number;
maxFallbackSlippage?: number;
excludedSources?: ERC20BridgeSource[];
feeSchedule?: FeeSchedule;
exchangeProxyOverhead?: ExchangeProxyOverhead;
allowFallback?: boolean;
shouldBatchBridgeOrders?: boolean;
gasPrice: BigNumber;
}
export interface ComparisonPrice {

View File

@@ -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,
};
}
}

View File

@@ -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;
}

View File

@@ -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';

View File

@@ -5,28 +5,23 @@
*/
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 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 +31,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 +42,20 @@ 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,
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 +63,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,

View File

@@ -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,
);

View File

@@ -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);

View File

@@ -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();

View File

@@ -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],

View File

@@ -102,6 +102,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
takerAmount: order.takerAmount,
fills: [],
...optimizerFields,
gasUsed: new BigNumber(1),
};
}

View File

@@ -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);

View File

@@ -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}`,
);
});
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -3,28 +3,23 @@
* 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/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 +29,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';

View File

@@ -4,30 +4,28 @@
"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/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 +35,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",