Compare commits

...

13 Commits

Author SHA1 Message Date
Lawrence Forman
99d90093fc multihop buys seem ok now.
fetch less samples for multihop legs
2022-02-07 14:15:10 -05:00
Lawrence Forman
ec65bf56f9 plz work multihop buys 2022-02-07 00:21:52 -05:00
Lawrence Forman
98e9e24b54 fix multihop buys 2021-12-27 22:02:48 -05:00
Lawrence Forman
f732d3eb9b remove unused functions 2021-11-30 22:49:01 -05:00
Lawrence Forman
5c57efe8a8 get rfq working 2021-11-30 22:47:08 -05:00
Lawrence Forman
da240653f4 get multiplex working 2021-11-30 00:06:47 -05:00
Lawrence Forman
22ec626870 get vips working 2021-11-29 16:57:58 -05:00
Lawrence Forman
2f60eb1c79 multihop with nice breakdowns 2021-11-23 17:10:26 -05:00
Lawrence Forman
313420473a getting multihop back up + refactors for treating all quotes as n-hop 2021-11-19 00:20:37 -05:00
Lawrence Forman
89a9424ae1 @0x/asset-swapper: Handle per-fill gas cost correctly in more places 2021-11-08 15:12:53 -05:00
Lawrence Forman
eabca7a2ee cleanup 2021-11-06 23:27:08 -04:00
Lawrence Forman
a5912c293e @0x/asset-swapper: use gasCost from sampler service 2021-11-06 00:23:15 -04:00
Lawrence Forman
83cae575fa @0x/asset-swapper: hack together basic sampler service integration 2021-11-03 15:18:31 -04:00
82 changed files with 2129 additions and 11972 deletions

View File

@@ -1,148 +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++) {
uint256 eps = 0;
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]) {
eps =
(buyAmount - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS /
makerTokenAmounts[i];
if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
break;
}
}
}
if (eps == 0 || 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,197 +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/IBalancer.sol";
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;
}
/// @dev Sample sell quotes from Balancer.
/// @param poolAddress Address of the Balancer pool to query.
/// @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 makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromBalancer(
address poolAddress,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (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;
}
}
}
/// @dev Sample buy quotes from Balancer.
/// @param poolAddress Address of the Balancer pool to query.
/// @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 takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromBalancer(
address poolAddress,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
IBalancer pool = IBalancer(poolAddress);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
return takerTokenAmounts;
}
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)
{
// Handles this revert scenario:
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443
if (amount > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) {
break;
}
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

@@ -1,189 +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;
pragma experimental ABIEncoderV2;
import "./SamplerUtils.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 }
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;
}
/// @dev Sample sell quotes from Balancer V2.
/// @param poolInfo Struct with pool related data
/// @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 makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromBalancerV2(
BalancerV2PoolInfo memory poolInfo,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
returns (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;
}
}
}
/// @dev Sample buy quotes from Balancer V2.
/// @param poolInfo Struct with pool related data
/// @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 takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromBalancerV2(
BalancerV2PoolInfo memory poolInfo,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
returns (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
});
}
}

View File

@@ -1,142 +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/IBancor.sol";
contract CompilerHack {}
contract BancorSampler is CompilerHack {
/// @dev Base gas limit for Bancor calls.
uint256 constant private BANCOR_CALL_GAS = 300e3; // 300k
struct BancorSamplerOpts {
IBancorRegistry registry;
address[][] paths;
}
/// @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).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return bancorNetwork the Bancor Network address
/// @return path the selected conversion path from bancor
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromBancor(
BancorSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (address bancorNetwork, address[] memory path, uint256[] memory makerTokenAmounts)
{
if (opts.paths.length == 0) {
return (bancorNetwork, path, 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);
}
/// @dev Sample buy quotes from Bancor. Unimplemented
/// @param opts BancorSamplerOpts The Bancor registry contract address and paths
/// @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 bancorNetwork the Bancor Network address
/// @return path the selected conversion path from bancor
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromBancor(
BancorSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (address bancorNetwork, address[] memory path, uint256[] memory takerTokenAmounts)
{
}
function _findBestPath(
BancorSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
internal
view
returns (address bancorNetwork, address[] memory path)
{
bancorNetwork = opts.registry.getAddress(opts.registry.BANCOR_NETWORK());
if (opts.paths.length == 0) {
return (bancorNetwork, path);
}
uint256 maxBoughtAmount = 0;
// Find the best path by selling the largest taker amount
for (uint256 i = 0; i < opts.paths.length; i++) {
if (opts.paths[i].length < 2) {
continue;
}
try
IBancorNetwork(bancorNetwork)
.rateByPath
{gas: BANCOR_CALL_GAS}
(opts.paths[i], takerTokenAmounts[takerTokenAmounts.length-1])
returns (uint256 amount)
{
if (amount > maxBoughtAmount) {
maxBoughtAmount = amount;
path = opts.paths[i];
}
} catch {
// Swallow failures, leaving all results as zero.
continue;
}
}
}
}

View File

@@ -1,161 +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/ICurve.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract CurveSampler is
SamplerUtils,
ApproximateBuys
{
/// @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+
/// @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 takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromCurve(
CurveInfo memory curveInfo,
int128 fromTokenIdx,
int128 toTokenIdx,
uint256[] memory takerTokenAmounts
)
public
view
returns (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;
}
}
}
/// @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 makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromCurve(
CurveInfo memory curveInfo,
int128 fromTokenIdx,
int128 toTokenIdx,
uint256[] memory makerTokenAmounts
)
public
view
returns (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];
}
}

View File

@@ -1,211 +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 "./ApproximateBuys.sol";
import "./SamplerUtils.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
{
/// @dev Gas limit for DODO calls.
uint256 constant private DODO_CALL_GAS = 300e3; // 300k
struct DODOSamplerOpts {
address registry;
address helper;
}
/// @dev Sample sell quotes from DODO.
/// @param opts DODOSamplerOpts DODO Registry and helper addresses
/// @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 sellBase whether the bridge needs to sell the base token
/// @return pool the DODO pool address
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromDODO(
DODOSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (bool sellBase, address pool, 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
if (pool != address(0)) {
baseToken = takerToken;
sellBase = true;
} else {
pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken);
// No pool either direction
if (address(pool) == address(0)) {
return (sellBase, pool, 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;
}
}
}
/// @dev Sample buy quotes from DODO.
/// @param opts DODOSamplerOpts DODO Registry and helper addresses
/// @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 sellBase whether the bridge needs to sell the base token
/// @return pool the DODO pool address
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromDODO(
DODOSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (bool sellBase, address pool, 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);
address baseToken;
// If pool exists we have the correct order of Base/Quote
if (pool != address(0)) {
baseToken = takerToken;
sellBase = true;
} else {
// Look up the pool from the maker/taker combination
pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken);
// No pool either direction
if (address(pool) == address(0)) {
return (sellBase, pool, 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
}),
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

@@ -1,204 +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;
pragma experimental ABIEncoderV2;
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
interface IDODOV2Registry {
function getDODOPool(address baseToken, address quoteToken)
external
view
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
{
/// @dev Gas limit for DODO V2 calls.
uint256 constant private DODO_V2_CALL_GAS = 300e3; // 300k
/// @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.
/// @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 sellBase whether the bridge needs to sell the base token
/// @return pool the DODO pool address
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromDODOV2(
address registry,
uint256 offset,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (bool sellBase, address pool, 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);
}
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;
}
}
}
/// @dev Sample buy quotes from DODO.
/// @param registry Address of the registry to look up.
/// @param offset offset index for the pool in the registry.
/// @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 sellBase whether the bridge needs to sell the base token
/// @return pool the DODO pool address
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromDODOV2(
address registry,
uint256 offset,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
if (pool == address(0)) {
return (sellBase, pool, 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
}),
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,
address takerToken,
address makerToken
)
internal
view
returns (address machine, bool sellBase)
{
// Query in base -> quote direction, if a pool is found then we are selling the base
address[] memory machines = IDODOV2Registry(registry).getDODOPool(takerToken, makerToken);
sellBase = true;
if (machines.length == 0) {
// Query in quote -> base direction, if a pool is found then we are selling the quote
machines = IDODOV2Registry(registry).getDODOPool(makerToken, takerToken);
sellBase = false;
}
if (offset >= machines.length) {
return (address(0), false);
}
machine = machines[offset];
}
}

View File

@@ -1,93 +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 "./BalancerSampler.sol";
import "./BalancerV2Sampler.sol";
import "./BancorSampler.sol";
import "./CurveSampler.sol";
import "./DODOSampler.sol";
import "./DODOV2Sampler.sol";
import "./KyberSampler.sol";
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";
contract ERC20BridgeSampler is
BalancerSampler,
BalancerV2Sampler,
BancorSampler,
CurveSampler,
DODOSampler,
DODOV2Sampler,
KyberSampler,
KyberDmmSampler,
LidoSampler,
LiquidityProviderSampler,
MakerPSMSampler,
MStableSampler,
MooniswapSampler,
MultiBridgeSampler,
NativeOrderSampler,
ShellSampler,
SmoothySampler,
TwoHopSampler,
UniswapSampler,
UniswapV2Sampler,
UniswapV3Sampler,
UtilitySampler
{
struct CallResults {
bytes data;
bool success;
}
/// @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
returns (CallResults[] memory callResults)
{
callResults = new CallResults[](callDatas.length);
for (uint256 i = 0; i != callDatas.length; ++i) {
callResults[i].success = true;
if (callDatas[i].length == 0) {
continue;
}
(callResults[i].success, callResults[i].data) = address(this).call(callDatas[i]);
}
}
}

View File

@@ -1,176 +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;
interface IKyberDmmPool {
function totalSupply()
external
view
returns (uint256);
}
interface IKyberDmmFactory {
function getPools(address token0, address token1)
external
view
returns (address[] memory _tokenPools);
}
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
{
/// @dev Gas limit for KyberDmm calls.
uint256 constant private KYBER_DMM_CALL_GAS = 150e3; // 150k
/// @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 makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromKyberDmm(
address router,
address[] memory path,
uint256[] memory takerTokenAmounts
)
public
view
returns (address[] memory pools, 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;
}
}
}
/// @dev Sample buy quotes from KyberDmm.
/// @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 pools The pool addresses involved in the multi path trade
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromKyberDmm(
address router,
address[] memory path,
uint256[] memory makerTokenAmounts
)
public
view
returns (address[] memory pools, uint256[] memory takerTokenAmounts)
{
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
pools = _getKyberDmmPools(router, path);
if (pools.length == 0) {
return (pools, 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;
}
}
}
function _getKyberDmmPools(
address router,
address[] memory path
)
private
view
returns (address[] memory pools)
{
IKyberDmmFactory factory = IKyberDmmFactory(IKyberDmmRouter(router).factory());
pools = new address[](path.length - 1);
for (uint256 i = 0; i < pools.length; i++) {
// find the best pool
address[] memory allPools;
try
factory.getPools
{gas: KYBER_DMM_CALL_GAS}
(path[i], path[i + 1])
returns (address[] memory allPools)
{
uint256 maxSupply = 0;
require(allPools.length >= 1, "KyberDMMSampler/NO_POOLS_FOUND");
for (uint256 j = 0; j < allPools.length; j++) {
uint256 totalSupply = IKyberDmmPool(allPools[j]).totalSupply();
if (totalSupply > maxSupply) {
maxSupply = totalSupply;
pools[i] = allPools[j];
}
}
} catch (bytes memory) {
return new address[](0);
}
}
}
}

View File

@@ -1,301 +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/IKyberNetwork.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract KyberSampler is
SamplerUtils,
ApproximateBuys
{
/// @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;
struct KyberSamplerOpts {
uint256 reserveOffset;
address hintHandler;
address networkProxy;
address weth;
bytes hint;
}
/// @dev Sample sell quotes from Kyber.
/// @param opts KyberSamplerOpts The nth reserve
/// @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 reserveId The id of the reserve found at reserveOffset
/// @return hint The hint for the selected reserve
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
function sampleSellsFromKyberNetwork(
KyberSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (bytes32 reserveId, bytes memory hint, uint256[] memory makerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
reserveId = _getNextReserveId(opts, takerToken, makerToken);
if (reserveId == 0x0) {
return (reserveId, hint, 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;
}
}
}
/// @dev Sample buy quotes from Kyber.
/// @param opts KyberSamplerOpts The nth reserve
/// @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 reserveId The id of the reserve found at reserveOffset
/// @return hint The hint for the selected reserve
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
function sampleBuysFromKyberNetwork(
KyberSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (bytes32 reserveId, bytes memory hint, uint256[] memory takerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
reserveId = _getNextReserveId(opts, takerToken, makerToken);
if (reserveId == 0x0) {
return (reserveId, hint, 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
}),
makerTokenAmounts
);
return (reserveId, hint, takerTokenAmounts);
}
function encodeKyberHint(
KyberSamplerOpts memory opts,
bytes32 reserveId,
address takerToken,
address makerToken
)
public
view
returns (bytes memory hint)
{
// Build a hint selecting the single reserve
IKyberHintHandler kyberHint = IKyberHintHandler(opts.hintHandler);
// All other reserves should be ignored with this hint
bytes32[] memory selectedReserves = new bytes32[](1);
selectedReserves[0] = reserveId;
uint256[] memory emptySplits = new uint256[](0);
if (takerToken == opts.weth) {
// ETH to Token
try
kyberHint.buildEthToTokenHint
{gas: KYBER_CALL_GAS}
(
makerToken,
IKyberHintHandler.TradeType.MaskIn,
selectedReserves,
emptySplits
)
returns (bytes memory result)
{
return result;
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
}
} else if (makerToken == opts.weth) {
// Token to ETH
try
kyberHint.buildTokenToEthHint
{gas: KYBER_CALL_GAS}
(
takerToken,
IKyberHintHandler.TradeType.MaskIn,
selectedReserves,
emptySplits
)
returns (bytes memory result)
{
return result;
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
}
} else {
// Token to Token
// We use the same reserve both ways
try
kyberHint.buildTokenToTokenHint
{gas: KYBER_CALL_GAS}
(
takerToken,
IKyberHintHandler.TradeType.MaskIn,
selectedReserves,
emptySplits,
makerToken,
IKyberHintHandler.TradeType.MaskIn,
selectedReserves,
emptySplits
)
returns (bytes memory result)
{
return result;
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
}
}
}
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,
address makerToken
)
internal
view
returns (bytes32 reserveId)
{
// Fetch the registered reserves for this pair
IKyberHintHandler kyberHint = IKyberHintHandler(opts.hintHandler);
(bytes32[] memory reserveIds, ,) = kyberHint.getTradingReserves(
takerToken == opts.weth ? KYBER_ETH_ADDRESS : takerToken,
makerToken == opts.weth ? KYBER_ETH_ADDRESS : makerToken,
true,
new bytes(0) // empty hint
);
if (opts.reserveOffset >= reserveIds.length) {
return 0x0;
}
reserveId = reserveIds[opts.reserveOffset];
// Ignore Kyber Bridged Reserves (0xbb)
if (uint256(reserveId >> 248) == 0xbb) {
return 0x0;
}
return reserveId;
}
}

View File

@@ -1,91 +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;
pragma experimental ABIEncoderV2;
import "./SamplerUtils.sol";
contract LidoSampler is SamplerUtils {
struct LidoInfo {
address stEthToken;
address wethToken;
}
/// @dev Sample sell quotes from Lido
/// @param lidoInfo Info regarding a specific Lido deployment
/// @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 makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromLido(
LidoInfo memory lidoInfo,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
pure
returns (uint256[] memory)
{
_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;
}
/// @dev Sample buy quotes from Lido.
/// @param lidoInfo Info regarding a specific Lido deployment
/// @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 takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromLido(
LidoInfo memory lidoInfo,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
pure
returns (uint256[] memory)
{
_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;
}
}

View File

@@ -1,132 +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/LibBytesV06.sol";
import "@0x/contracts-zero-ex/contracts/src/vendor/ILiquidityProvider.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract LiquidityProviderSampler is
SamplerUtils,
ApproximateBuys
{
/// @dev Default gas limit for liquidity provider calls.
uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k
/// @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 makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromLiquidityProvider(
address providerAddress,
address takerToken,
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);
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;
}
}
}
/// @dev Sample buy 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 makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromLiquidityProvider(
address providerAddress,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken, providerAddress),
takerTokenData: abi.encode(takerToken, providerAddress),
getSellQuoteCallback: _sampleSellForApproximateBuyFromLiquidityProvider
}),
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

@@ -1,127 +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/IMStable.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract MStableSampler is
SamplerUtils,
ApproximateBuys
{
/// @dev Default gas limit for mStable calls.
uint256 constant private DEFAULT_CALL_GAS = 800e3; // 800k
/// @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 makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromMStable(
address router,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (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;
}
}
}
/// @dev Sample buy quotes from 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 makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromMStable(
address router,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
return _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken, router),
takerTokenData: abi.encode(takerToken, router),
getSellQuoteCallback: _sampleSellForApproximateBuyFromMStable
}),
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

@@ -1,267 +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;
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
);
}
contract MakerPSMSampler is
SamplerUtils
{
using LibSafeMathV06 for uint256;
/// @dev Information about which PSM module to use
struct MakerPsmInfo {
address psmAddress;
bytes32 ilkIdentifier;
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
/// @dev Sample sell quotes from Maker PSM
function sampleSellsFromMakerPsm(
MakerPsmInfo memory psmInfo,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (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;
}
}
function sampleBuysFromMakerPsm(
MakerPsmInfo memory psmInfo,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (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;
}
}
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,169 +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/IMooniswap.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract MooniswapSampler is
SamplerUtils,
ApproximateBuys
{
/// @dev Gas limit for Mooniswap calls.
uint256 constant private MOONISWAP_CALL_GAS = 150e3; // 150k
/// @dev Sample sell quotes from Mooniswap.
/// @param registry Address of the Mooniswap Registry.
/// @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 pool The contract address for the pool
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromMooniswap(
address registry,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (IMooniswap pool, 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
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;
}
}
/// @dev Sample buy quotes from Mooniswap.
/// @param registry Address of the Mooniswap Registry.
/// @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 pool The contract address for the pool
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromMooniswap(
address registry,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (IMooniswap pool, uint256[] memory takerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(registry, makerToken),
takerTokenData: abi.encode(registry, takerToken),
getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap
}),
makerTokenAmounts
);
pool = IMooniswap(
IMooniswapRegistry(registry).pools(takerToken, makerToken)
);
}
function _sampleSellForApproximateBuyFromMooniswap(
bytes memory takerTokenData,
bytes memory makerTokenData,
uint256 sellAmount
)
private
view
returns (uint256 buyAmount)
{
(address registry, address mooniswapTakerToken) = abi.decode(takerTokenData, (address, address));
(address _registry, address mooniswapMakerToken) = abi.decode(makerTokenData, (address, address));
return sampleSingleSellFromMooniswapPool(
registry,
mooniswapTakerToken,
mooniswapMakerToken,
sellAmount
);
}
}

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,239 +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;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
interface IExchange {
enum OrderStatus {
INVALID,
FILLABLE,
FILLED,
CANCELLED,
EXPIRED
}
/// @dev A standard OTC or OO limit order.
struct LimitOrder {
IERC20TokenV06 makerToken;
IERC20TokenV06 takerToken;
uint128 makerAmount;
uint128 takerAmount;
uint128 takerTokenFeeAmount;
address maker;
address taker;
address sender;
address feeRecipient;
bytes32 pool;
uint64 expiry;
uint256 salt;
}
/// @dev An RFQ limit order.
struct RfqOrder {
IERC20TokenV06 makerToken;
IERC20TokenV06 takerToken;
uint128 makerAmount;
uint128 takerAmount;
address maker;
address taker;
address txOrigin;
bytes32 pool;
uint64 expiry;
uint256 salt;
}
/// @dev Info on a limit or RFQ order.
struct OrderInfo {
bytes32 orderHash;
OrderStatus status;
uint128 takerTokenFilledAmount;
}
/// @dev Allowed signature types.
enum SignatureType {
ILLEGAL,
INVALID,
EIP712,
ETHSIGN
}
/// @dev Encoded EC signature.
struct Signature {
// How to validate the signature.
SignatureType signatureType;
// EC Signature data.
uint8 v;
// EC Signature data.
bytes32 r;
// EC Signature data.
bytes32 s;
}
/// @dev Get the order info for a limit order.
/// @param order The limit order.
/// @return orderInfo Info about the order.
function getLimitOrderInfo(LimitOrder memory order)
external
view
returns (OrderInfo memory orderInfo);
/// @dev Get order info, fillable amount, and signature validity for a limit order.
/// Fillable amount is determined using balances and allowances of the maker.
/// @param order The limit order.
/// @param signature The order signature.
/// @return orderInfo Info about the order.
/// @return actualFillableTakerTokenAmount How much of the order is fillable
/// based on maker funds, in taker tokens.
/// @return isSignatureValid Whether the signature is valid.
function getLimitOrderRelevantState(
LimitOrder memory order,
Signature calldata signature
)
external
view
returns (
OrderInfo memory orderInfo,
uint128 actualFillableTakerTokenAmount,
bool isSignatureValid
);
}
contract NativeOrderSampler {
using LibSafeMathV06 for uint256;
using LibBytesV06 for bytes;
/// @dev Gas limit for calls to `getOrderFillableTakerAmount()`.
uint256 constant internal DEFAULT_CALL_GAS = 200e3; // 200k
/// @dev Queries the fillable taker asset amounts of native orders.
/// Effectively ignores orders that have empty signatures or
/// maker/taker asset amounts (returning 0).
/// @param orders Native limit orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param exchange The V4 exchange.
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
function getLimitOrderFillableTakerAssetAmounts(
IExchange.LimitOrder[] memory orders,
IExchange.Signature[] memory orderSignatures,
IExchange exchange
)
public
view
returns (uint256[] memory orderFillableTakerAssetAmounts)
{
orderFillableTakerAssetAmounts = new uint256[](orders.length);
for (uint256 i = 0; i != orders.length; i++) {
try
this.getLimitOrderFillableTakerAmount
{gas: DEFAULT_CALL_GAS}
(
orders[i],
orderSignatures[i],
exchange
)
returns (uint256 amount)
{
orderFillableTakerAssetAmounts[i] = amount;
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
orderFillableTakerAssetAmounts[i] = 0;
}
}
}
/// @dev Queries the fillable taker asset amounts of native orders.
/// Effectively ignores orders that have empty signatures or
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param exchange The V4 exchange.
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
function getLimitOrderFillableMakerAssetAmounts(
IExchange.LimitOrder[] memory orders,
IExchange.Signature[] memory orderSignatures,
IExchange exchange
)
public
view
returns (uint256[] memory orderFillableMakerAssetAmounts)
{
orderFillableMakerAssetAmounts = getLimitOrderFillableTakerAssetAmounts(
orders,
orderSignatures,
exchange
);
// `orderFillableMakerAssetAmounts` now holds taker asset amounts, so
// convert them to maker asset amounts.
for (uint256 i = 0; i < orders.length; ++i) {
if (orderFillableMakerAssetAmounts[i] != 0) {
orderFillableMakerAssetAmounts[i] = LibMathV06.getPartialAmountCeil(
orderFillableMakerAssetAmounts[i],
orders[i].takerAmount,
orders[i].makerAmount
);
}
}
}
/// @dev Get the fillable taker amount of an order, taking into account
/// order state, maker fees, and maker balances.
function getLimitOrderFillableTakerAmount(
IExchange.LimitOrder memory order,
IExchange.Signature memory signature,
IExchange exchange
)
virtual
public
view
returns (uint256 fillableTakerAmount)
{
if (signature.signatureType == IExchange.SignatureType.ILLEGAL ||
signature.signatureType == IExchange.SignatureType.INVALID ||
order.makerAmount == 0 ||
order.takerAmount == 0)
{
return 0;
}
(
IExchange.OrderInfo memory orderInfo,
uint128 remainingFillableTakerAmount,
bool isSignatureValid
) = exchange.getLimitOrderRelevantState(order, signature);
if (
orderInfo.status != IExchange.OrderStatus.FILLABLE ||
!isSignatureValid ||
order.makerToken == IERC20TokenV06(0)
) {
return 0;
}
fillableTakerAmount = uint256(remainingFillableTakerAmount);
}
}

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

@@ -1,126 +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 "./ApproximateBuys.sol";
import "./interfaces/IShell.sol";
import "./SamplerUtils.sol";
contract ShellSampler is
SamplerUtils,
ApproximateBuys
{
struct ShellInfo {
address poolAddress;
}
/// @dev Default gas limit for Shell calls.
uint256 constant private DEFAULT_CALL_GAS = 300e3; // 300k
/// @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 makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromShell(
address pool,
address takerToken,
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);
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;
}
}
}
/// @dev Sample buy quotes from 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 makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromShell(
address pool,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
return _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken, pool),
takerTokenData: abi.encode(takerToken, pool),
getSellQuoteCallback: _sampleSellForApproximateBuyFromShell
}),
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

@@ -1,124 +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/LibBytesV06.sol";
contract TwoHopSampler {
using LibBytesV06 for bytes;
struct HopInfo {
uint256 sourceIndex;
bytes returnData;
}
function sampleTwoHopSell(
bytes[] memory firstHopCalls,
bytes[] memory secondHopCalls,
uint256 sellAmount
)
public
returns (
HopInfo memory firstHop,
HopInfo memory secondHop,
uint256 buyAmount
)
{
uint256 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]);
if (didSucceed) {
uint256 amount = returnData.readUint256(returnData.length - 32);
if (amount > intermediateAssetAmount) {
intermediateAssetAmount = amount;
firstHop.sourceIndex = i;
firstHop.returnData = returnData;
}
}
}
if (intermediateAssetAmount == 0) {
return (firstHop, secondHop, buyAmount);
}
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, intermediateAssetAmount);
(bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[j]);
if (didSucceed) {
uint256 amount = returnData.readUint256(returnData.length - 32);
if (amount > buyAmount) {
buyAmount = amount;
secondHop.sourceIndex = j;
secondHop.returnData = returnData;
}
}
}
}
function sampleTwoHopBuy(
bytes[] memory firstHopCalls,
bytes[] memory secondHopCalls,
uint256 buyAmount
)
public
returns (
HopInfo memory firstHop,
HopInfo memory secondHop,
uint256 sellAmount
)
{
sellAmount = uint256(-1);
uint256 intermediateAssetAmount = uint256(-1);
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, buyAmount);
(bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[j]);
if (didSucceed) {
uint256 amount = returnData.readUint256(returnData.length - 32);
if (
amount > 0 &&
amount < intermediateAssetAmount
) {
intermediateAssetAmount = amount;
secondHop.sourceIndex = j;
secondHop.returnData = returnData;
}
}
}
if (intermediateAssetAmount == uint256(-1)) {
return (firstHop, secondHop, sellAmount);
}
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, intermediateAssetAmount);
(bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]);
if (didSucceed) {
uint256 amount = returnData.readUint256(returnData.length - 32);
if (
amount > 0 &&
amount < sellAmount
) {
sellAmount = amount;
firstHop.sourceIndex = i;
firstHop.returnData = returnData;
}
}
}
}
}

View File

@@ -1,214 +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/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);
}
contract UniswapSampler is
SamplerUtils
{
/// @dev Gas limit for Uniswap calls.
uint256 constant private UNISWAP_CALL_GAS = 150e3; // 150k
/// @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 makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromUniswap(
address router,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (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;
}
}
}
/// @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 takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromUniswap(
address router,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (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))
);
}
}

View File

@@ -1,102 +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/IUniswapV2Router01.sol";
contract UniswapV2Sampler
{
/// @dev Gas limit for UniswapV2 calls.
uint256 constant private UNISWAPV2_CALL_GAS = 150e3; // 150k
/// @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 makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromUniswapV2(
address router,
address[] memory path,
uint256[] memory takerTokenAmounts
)
public
view
returns (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;
}
}
}
/// @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 takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromUniswapV2(
address router,
address[] memory path,
uint256[] memory makerTokenAmounts
)
public
view
returns (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;
}
}
}
}

View File

@@ -1,313 +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;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.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 {
function getPool(IERC20TokenV06 a, IERC20TokenV06 b, uint24 fee)
external
view
returns (IUniswapV3Pool pool);
}
interface IUniswapV3Pool {
function token0() external view returns (IERC20TokenV06);
function token1() external view returns (IERC20TokenV06);
function fee() external view returns (uint24);
}
contract UniswapV3Sampler
{
/// @dev Gas limit for UniswapV3 calls. This is 100% a guess.
uint256 constant private QUOTE_GAS = 300e3;
/// @dev Sample sell quotes from UniswapV3.
/// @param quoter UniswapV3 Quoter 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 makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromUniswapV3(
IUniswapV3Quoter quoter,
IERC20TokenV06[] memory path,
uint256[] memory takerTokenAmounts
)
public
returns (
bytes[] memory uniswapPaths,
uint256[] memory makerTokenAmounts
)
{
IUniswapV3Pool[][] memory poolPaths =
_getValidPoolPaths(quoter.factory(), path, 0);
makerTokenAmounts = 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 { }
}
// 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 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 takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromUniswapV3(
IUniswapV3Quoter quoter,
IERC20TokenV06[] memory path,
uint256[] memory makerTokenAmounts
)
public
returns (
bytes[] memory uniswapPaths,
uint256[] memory takerTokenAmounts
)
{
IUniswapV3Pool[][] memory poolPaths =
_getValidPoolPaths(quoter.factory(), path, 0);
IERC20TokenV06[] memory reversedPath = _reverseTokenPath(path);
takerTokenAmounts = 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 {}
}
// Break early if we can't complete the buys.
if (topSellAmount == 0) {
break;
}
takerTokenAmounts[i] = topSellAmount;
uniswapPaths[i] = topUniswapPath;
}
}
function _getValidPoolPaths(
IUniswapV3Factory factory,
IERC20TokenV06[] memory tokenPath,
uint256 startIndex
)
private
view
returns (IUniswapV3Pool[][] memory poolPaths)
{
require(
tokenPath.length - startIndex >= 2,
"UniswapV3Sampler/tokenPath too short"
);
uint24[3] memory validPoolFees = [
// The launch pool fees. Could get hairier if they add more.
uint24(0.0005e6),
uint24(0.003e6),
uint24(0.01e6)
];
IUniswapV3Pool[] memory validPools =
new IUniswapV3Pool[](validPoolFees.length);
uint256 numValidPools = 0;
{
IERC20TokenV06 inputToken = tokenPath[startIndex];
IERC20TokenV06 outputToken = tokenPath[startIndex + 1];
for (uint256 i = 0; i < validPoolFees.length; ++i) {
IUniswapV3Pool pool =
factory.getPool(inputToken, outputToken, validPoolFees[i]);
if (_isValidPool(pool)) {
validPools[numValidPools++] = pool;
}
}
}
if (numValidPools == 0) {
// No valid pools for this hop.
return poolPaths;
}
if (startIndex + 2 == tokenPath.length) {
// End of path.
poolPaths = new IUniswapV3Pool[][](numValidPools);
for (uint256 i = 0; i < numValidPools; ++i) {
poolPaths[i] = new IUniswapV3Pool[](1);
poolPaths[i][0] = validPools[i];
}
return poolPaths;
}
// Get paths for subsequent hops.
IUniswapV3Pool[][] memory subsequentPoolPaths =
_getValidPoolPaths(factory, tokenPath, startIndex + 1);
if (subsequentPoolPaths.length == 0) {
// Could not complete the path.
return poolPaths;
}
// Combine our pools with the next hop paths.
poolPaths = new IUniswapV3Pool[][](
numValidPools * subsequentPoolPaths.length
);
for (uint256 i = 0; i < numValidPools; ++i) {
for (uint256 j = 0; j < subsequentPoolPaths.length; ++j) {
uint256 o = i * subsequentPoolPaths.length + j;
// Prepend pool to the subsequent path.
poolPaths[o] =
new IUniswapV3Pool[](1 + subsequentPoolPaths[j].length);
poolPaths[o][0] = validPools[i];
for (uint256 k = 0; k < subsequentPoolPaths[j].length; ++k) {
poolPaths[o][1 + k] = subsequentPoolPaths[j][k];
}
}
}
return poolPaths;
}
function _reverseTokenPath(IERC20TokenV06[] memory tokenPath)
private
returns (IERC20TokenV06[] memory reversed)
{
reversed = new IERC20TokenV06[](tokenPath.length);
for (uint256 i = 0; i < tokenPath.length; ++i) {
reversed[i] = tokenPath[tokenPath.length - i - 1];
}
}
function _reversePoolPath(IUniswapV3Pool[] memory poolPath)
private
returns (IUniswapV3Pool[] memory reversed)
{
reversed = new IUniswapV3Pool[](poolPath.length);
for (uint256 i = 0; i < poolPath.length; ++i) {
reversed[i] = poolPath[poolPath.length - i - 1];
}
}
function _isValidPool(IUniswapV3Pool pool)
private
view
returns (bool isValid)
{
// Check if it has been deployed.
{
uint256 codeSize;
assembly {
codeSize := extcodesize(pool)
}
if (codeSize == 0) {
return false;
}
}
// Must have a balance of both tokens.
if (pool.token0().balanceOf(address(pool)) == 0) {
return false;
}
if (pool.token1().balanceOf(address(pool)) == 0) {
return false;
}
return true;
}
function _toUniswapPath(
IERC20TokenV06[] memory tokenPath,
IUniswapV3Pool[] memory poolPath
)
private
view
returns (bytes memory uniswapPath)
{
require(
tokenPath.length >= 2 && tokenPath.length == poolPath.length + 1,
"UniswapV3Sampler/invalid path lengths"
);
// Uniswap paths are tightly packed as:
// [token0, token0token1PairFee, token1, token1Token2PairFee, token2, ...]
uniswapPath = new bytes(tokenPath.length * 20 + poolPath.length * 3);
uint256 o;
assembly { o := add(uniswapPath, 32) }
for (uint256 i = 0; i < tokenPath.length; ++i) {
if (i > 0) {
uint24 poolFee = poolPath[i - 1].fee();
assembly {
mstore(o, shl(232, poolFee))
o := add(o, 3)
}
}
IERC20TokenV06 token = tokenPath[i];
assembly {
mstore(o, shl(96, token))
o := add(o, 20)
}
}
}
}

View File

@@ -1,80 +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;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
contract UtilitySampler {
using LibERC20TokenV06 for IERC20TokenV06;
IERC20TokenV06 private immutable UTILITY_ETH_ADDRESS = IERC20TokenV06(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
function getTokenDecimals(IERC20TokenV06[] memory tokens)
public
view
returns (uint256[] memory decimals)
{
decimals = new uint256[](tokens.length);
for (uint256 i = 0; i != tokens.length; i++) {
decimals[i] = tokens[i] == UTILITY_ETH_ADDRESS
? 18
: tokens[i].compatDecimals();
}
}
function getBalanceOf(IERC20TokenV06[] memory tokens, address account)
public
view
returns (uint256[] memory balances)
{
balances = new uint256[](tokens.length);
for (uint256 i = 0; i != tokens.length; i++) {
balances[i] = tokens[i] == UTILITY_ETH_ADDRESS
? account.balance
: tokens[i].compatBalanceOf(account);
}
}
function getAllowanceOf(IERC20TokenV06[] memory tokens, address account, address spender)
public
view
returns (uint256[] memory allowances)
{
allowances = new uint256[](tokens.length);
for (uint256 i = 0; i != tokens.length; i++) {
allowances[i] = tokens[i] == UTILITY_ETH_ADDRESS
? 0
: tokens[i].compatAllowance(account, spender);
}
}
function isContract(address account)
public
view
returns (bool)
{
uint256 size;
assembly { size := extcodesize(account) }
return size > 0;
}
}

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

@@ -1,96 +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;
// 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 {
enum TradeType {BestOfAll, MaskIn, MaskOut, Split}
enum ProcessWithRate {NotRequired, Required}
function getTradingReserves(
address tokenSrc,
address tokenDest,
bool isTokenToToken,
bytes calldata hint
)
external
view
returns (
bytes32[] memory reserveIds,
uint256[] memory splitValuesBps,
ProcessWithRate processWithRate
);
function buildTokenToEthHint(
address tokenSrc,
TradeType tokenToEthType,
bytes32[] calldata tokenToEthReserveIds,
uint256[] calldata tokenToEthSplits
)
external
view
returns (bytes memory hint);
function buildEthToTokenHint(
address tokenDest,
TradeType ethToTokenType,
bytes32[] calldata ethToTokenReserveIds,
uint256[] calldata ethToTokenSplits
)
external
view
returns (bytes memory hint);
function buildTokenToTokenHint(
address tokenSrc,
TradeType tokenToEthType,
bytes32[] calldata tokenToEthReserveIds,
uint256[] calldata tokenToEthSplits,
address tokenDest,
TradeType ethToTokenType,
bytes32[] calldata ethToTokenReserveIds,
uint256[] calldata ethToTokenSplits
)
external
view
returns (bytes memory hint);
}

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

@@ -1,52 +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 IUniswapExchangeQuotes {
function getEthToTokenInputPrice(
uint256 ethSold
)
external
view
returns (uint256 tokensBought);
function getEthToTokenOutputPrice(
uint256 tokensBought
)
external
view
returns (uint256 ethSold);
function getTokenToEthInputPrice(
uint256 tokensSold
)
external
view
returns (uint256 ethBought);
function getTokenToEthOutputPrice(
uint256 ethBought
)
external
view
returns (uint256 tokensSold);
}

View File

@@ -1,34 +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 IUniswapV2Router01 {
function getAmountsOut(uint256 amountIn, address[] calldata path)
external
view
returns (uint256[] memory amounts);
function getAmountsIn(uint256 amountOut, address[] calldata path)
external
view
returns (uint256[] memory amounts);
}

View File

@@ -1,39 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
contract DummyLiquidityProvider
{
/// @dev Quotes the amount of `makerToken` that would be obtained by
/// selling `sellAmount` of `takerToken`.
/// @param sellAmount Amount of `takerToken` to sell.
/// @return makerTokenAmount Amount of `makerToken` that would be obtained.
function getSellQuote(
address, /* takerToken */
address, /* makerToken */
uint256 sellAmount
)
external
view
returns (uint256 makerTokenAmount)
{
makerTokenAmount = sellAmount - 1;
}
/// @dev Quotes the amount of `takerToken` that would need to be sold in
/// order to obtain `buyAmount` of `makerToken`.
/// @param buyAmount Amount of `makerToken` to buy.
/// @return takerTokenAmount Amount of `takerToken` that would need to be sold.
function getBuyQuote(
address, /* takerToken */
address, /* makerToken */
uint256 buyAmount
)
external
view
returns (uint256 takerTokenAmount)
{
takerTokenAmount = buyAmount + 1;
}
}

View File

@@ -1,455 +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 "../src/ERC20BridgeSampler.sol";
import "../src/interfaces/IKyberNetwork.sol";
import "../src/interfaces/IUniswapV2Router01.sol";
library LibDeterministicQuotes {
address private constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
uint256 private constant RATE_DENOMINATOR = 1 ether;
uint256 private constant MIN_RATE = RATE_DENOMINATOR / 100;
uint256 private constant MAX_RATE = 100 * RATE_DENOMINATOR;
uint8 private constant MIN_DECIMALS = 4;
uint8 private constant MAX_DECIMALS = 20;
function getDeterministicSellQuote(
bytes32 salt,
address sellToken,
address buyToken,
uint256 sellAmount
)
internal
pure
returns (uint256 buyAmount)
{
uint256 sellBase = uint256(10) ** getDeterministicTokenDecimals(sellToken);
uint256 buyBase = uint256(10) ** getDeterministicTokenDecimals(buyToken);
uint256 rate = getDeterministicRate(salt, sellToken, buyToken);
return sellAmount * rate * buyBase / sellBase / RATE_DENOMINATOR;
}
function getDeterministicBuyQuote(
bytes32 salt,
address sellToken,
address buyToken,
uint256 buyAmount
)
internal
pure
returns (uint256 sellAmount)
{
uint256 sellBase = uint256(10) ** getDeterministicTokenDecimals(sellToken);
uint256 buyBase = uint256(10) ** getDeterministicTokenDecimals(buyToken);
uint256 rate = getDeterministicRate(salt, sellToken, buyToken);
return buyAmount * RATE_DENOMINATOR * sellBase / rate / buyBase;
}
function getDeterministicTokenDecimals(address token)
internal
pure
returns (uint8 decimals)
{
if (token == WETH_ADDRESS) {
return 18;
}
bytes32 seed = keccak256(abi.encodePacked(token));
return uint8(uint256(seed) % (MAX_DECIMALS - MIN_DECIMALS)) + MIN_DECIMALS;
}
function getDeterministicRate(bytes32 salt, address sellToken, address buyToken)
internal
pure
returns (uint256 rate)
{
bytes32 seed = keccak256(abi.encodePacked(salt, sellToken, buyToken));
return uint256(seed) % (MAX_RATE - MIN_RATE) + MIN_RATE;
}
}
contract TestDeploymentConstants {
// solhint-disable separate-by-one-line-in-contract
// Mainnet addresses ///////////////////////////////////////////////////////
/// @dev Mainnet address of the WETH contract.
address constant private WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev Overridable way to get the WETH address.
/// @return wethAddress The WETH address.
function _getWethAddress()
internal
view
returns (address wethAddress)
{
return WETH_ADDRESS;
}
}
contract FailTrigger {
// Give this address a balance to force operations to fail.
address payable constant public FAILURE_ADDRESS = 0xe9dB8717BC5DFB20aaf538b4a5a02B7791FF430C;
// Funds `FAILURE_ADDRESS`.
function enableFailTrigger() external payable {
FAILURE_ADDRESS.transfer(msg.value);
}
function _revertIfShouldFail() internal view {
if (FAILURE_ADDRESS.balance != 0) {
revert("FAIL_TRIGGERED");
}
}
}
contract TestERC20BridgeSamplerUniswapExchange is
IUniswapExchangeQuotes,
TestDeploymentConstants,
FailTrigger
{
bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab;
address public tokenAddress;
bytes32 public salt;
constructor(address _tokenAddress) public {
tokenAddress = _tokenAddress;
salt = keccak256(abi.encodePacked(BASE_SALT, _tokenAddress));
}
// Deterministic `IUniswapExchangeQuotes.getEthToTokenInputPrice()`.
function getEthToTokenInputPrice(
uint256 ethSold
)
override
external
view
returns (uint256 tokensBought)
{
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicSellQuote(
salt,
tokenAddress,
_getWethAddress(),
ethSold
);
}
// Deterministic `IUniswapExchangeQuotes.getEthToTokenOutputPrice()`.
function getEthToTokenOutputPrice(
uint256 tokensBought
)
override
external
view
returns (uint256 ethSold)
{
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicBuyQuote(
salt,
_getWethAddress(),
tokenAddress,
tokensBought
);
}
// Deterministic `IUniswapExchangeQuotes.getTokenToEthInputPrice()`.
function getTokenToEthInputPrice(
uint256 tokensSold
)
override
external
view
returns (uint256 ethBought)
{
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicSellQuote(
salt,
tokenAddress,
_getWethAddress(),
tokensSold
);
}
// Deterministic `IUniswapExchangeQuotes.getTokenToEthOutputPrice()`.
function getTokenToEthOutputPrice(
uint256 ethBought
)
override
external
view
returns (uint256 tokensSold)
{
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicBuyQuote(
salt,
_getWethAddress(),
tokenAddress,
ethBought
);
}
}
contract TestERC20BridgeSamplerUniswapV2Router01 is
IUniswapV2Router01,
TestDeploymentConstants,
FailTrigger
{
bytes32 constant private SALT = 0xadc7fcb33c735913b8635927e66896b356a53a912ab2ceff929e60a04b53b3c1;
// Deterministic `IUniswapV2Router01.getAmountsOut()`.
function getAmountsOut(uint256 amountIn, address[] calldata path)
override
external
view
returns (uint256[] memory amounts)
{
require(path.length >= 2, "PATH_TOO_SHORT");
_revertIfShouldFail();
amounts = new uint256[](path.length);
amounts[0] = amountIn;
for (uint256 i = 0; i < path.length - 1; ++i) {
amounts[i + 1] = LibDeterministicQuotes.getDeterministicSellQuote(
SALT,
path[i],
path[i + 1],
amounts[i]
);
}
}
// Deterministic `IUniswapV2Router01.getAmountsInt()`.
function getAmountsIn(uint256 amountOut, address[] calldata path)
override
external
view
returns (uint256[] memory amounts)
{
require(path.length >= 2, "PATH_TOO_SHORT");
_revertIfShouldFail();
amounts = new uint256[](path.length);
amounts[path.length - 1] = amountOut;
for (uint256 i = path.length - 1; i > 0; --i) {
amounts[i - 1] = LibDeterministicQuotes.getDeterministicBuyQuote(
SALT,
path[i - 1],
path[i],
amounts[i]
);
}
}
}
// solhint-disable space-after-comma
contract TestERC20BridgeSamplerKyberNetwork is
TestDeploymentConstants,
FailTrigger
{
bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7;
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
enum TradeType {BestOfAll, MaskIn, MaskOut, Split}
enum ProcessWithRate {NotRequired, Required}
// IKyberHintHandler
function buildTokenToEthHint(
address tokenSrc,
TradeType /* tokenToEthType */,
bytes32[] calldata /* tokenToEthReserveIds */,
uint256[] calldata /* tokenToEthSplits */
) external view returns (bytes memory hint)
{
return abi.encode(tokenSrc);
}
function buildEthToTokenHint(
address tokenDest,
TradeType /* ethToTokenType */,
bytes32[] calldata /* ethToTokenReserveIds */,
uint256[] calldata /* ethToTokenSplits */
) external view returns (bytes memory hint)
{
return abi.encode(tokenDest);
}
// IKyberHintHandler
function buildTokenToTokenHint(
address tokenSrc,
TradeType /* tokenToEthType */,
bytes32[] calldata /* tokenToEthReserveIds */,
uint256[] calldata /* tokenToEthSplits */,
address /* tokenDest */,
TradeType /* EthToTokenType */,
bytes32[] calldata /* EthToTokenReserveIds */,
uint256[] calldata /* EthToTokenSplits */
) external view returns (bytes memory hint)
{
return abi.encode(tokenSrc);
}
// IKyberHintHandler
function getTradingReserves(
address tokenSrc,
address tokenDest,
bool isTokenToToken,
bytes calldata hint
)
external
view
returns (
bytes32[] memory reserveIds,
uint256[] memory splitValuesBps,
ProcessWithRate processWithRate
)
{
reserveIds = new bytes32[](1);
reserveIds[0] = bytes32(uint256(1));
splitValuesBps = new uint256[](0);
processWithRate = ProcessWithRate.NotRequired;
}
// Deterministic `IKyberNetworkProxy.getExpectedRateAfterFee()`.
function getExpectedRateAfterFee(
address fromToken,
address toToken,
uint256 /* srcQty */,
uint256 /* fee */,
bytes calldata /* hint */
)
external
view
returns
(uint256 expectedRate)
{
_revertIfShouldFail();
fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken;
toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken;
expectedRate = LibDeterministicQuotes.getDeterministicRate(
SALT,
fromToken,
toToken
);
}
// Deterministic `IKyberNetworkProxy.getExpectedRate()`.
function getExpectedRate(
address fromToken,
address toToken,
uint256
)
external
view
returns (uint256 expectedRate, uint256)
{
_revertIfShouldFail();
fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken;
toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken;
expectedRate = LibDeterministicQuotes.getDeterministicRate(
SALT,
fromToken,
toToken
);
}
}
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;
TestERC20BridgeSamplerKyberNetwork public kyber;
uint8 private constant MAX_ORDER_STATUS = uint8(IExchange.OrderStatus.CANCELLED) + 1;
constructor() public ERC20BridgeSampler() {
uniswap = new TestERC20BridgeSamplerUniswapExchangeFactory();
uniswapV2Router = new TestERC20BridgeSamplerUniswapV2Router01();
kyber = new TestERC20BridgeSamplerKyberNetwork();
}
// Creates Uniswap exchange contracts for tokens.
function createTokenExchanges(address[] calldata tokenAddresses)
external
{
uniswap.createTokenExchanges(tokenAddresses);
}
// Overridden to return deterministic states.
function getLimitOrderFillableTakerAmount(
IExchange.LimitOrder memory order,
IExchange.Signature memory,
IExchange
)
override
public
view
returns (uint256 fillableTakerAmount)
{
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

@@ -1,135 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2019 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 "../src/NativeOrderSampler.sol";
import "../src/UtilitySampler.sol";
contract TestNativeOrderSamplerToken {
mapping (address => uint256) public balanceOf;
mapping (address => mapping(address => uint256)) public allowance;
function setBalanceAndAllowance(
address owner,
address spender,
uint256 balance,
uint256 allowance_
)
external
{
balanceOf[owner] = balance;
allowance[owner][spender] = allowance_;
}
}
contract TestNativeOrderSampler is
NativeOrderSampler,
UtilitySampler
{
uint8 private constant MAX_ORDER_STATUS = uint8(IExchange.OrderStatus.CANCELLED) + 1;
bytes32 private constant VALID_SIGNATURE_HASH = bytes32(hex"01");
function createTokens(uint256 count)
external
returns (TestNativeOrderSamplerToken[] memory tokens)
{
tokens = new TestNativeOrderSamplerToken[](count);
for (uint256 i = 0; i < count; ++i) {
tokens[i] = new TestNativeOrderSamplerToken();
}
}
function setTokenBalanceAndAllowance(
TestNativeOrderSamplerToken token,
address owner,
address spender,
uint256 balance,
uint256 allowance
)
external
{
token.setBalanceAndAllowance(owner, spender, balance, allowance);
}
// IExchange.getLimitOrderRelevantState()
function getLimitOrderRelevantState(
IExchange.LimitOrder memory order,
IExchange.Signature calldata signature
)
external
view
returns (
IExchange.OrderInfo memory orderInfo,
uint128 actualFillableTakerTokenAmount,
bool isSignatureValid
)
{
// The order salt determines everything.
orderInfo.orderHash = keccak256(abi.encode(order.salt));
if (uint8(order.salt) == 0xFF) {
orderInfo.status = IExchange.OrderStatus.FILLED;
} else {
orderInfo.status = IExchange.OrderStatus.FILLABLE;
}
isSignatureValid = signature.r == VALID_SIGNATURE_HASH;
// The expiration time is the filled taker asset amount.
orderInfo.takerTokenFilledAmount = uint128(order.expiry);
// Calculate how much is fillable in maker terms given the filled taker amount
uint256 fillableMakerTokenAmount = LibMathV06.getPartialAmountFloor(
uint256(
order.takerAmount
- orderInfo.takerTokenFilledAmount
),
uint256(order.takerAmount),
uint256(order.makerAmount)
);
// Take the min of the balance/allowance and the fillable maker amount
fillableMakerTokenAmount = LibSafeMathV06.min256(
fillableMakerTokenAmount,
_getSpendableERC20BalanceOf(order.makerToken, order.maker)
);
// Convert to taker terms
actualFillableTakerTokenAmount = LibMathV06.getPartialAmountCeil(
fillableMakerTokenAmount,
uint256(order.makerAmount),
uint256(order.takerAmount)
).safeDowncastToUint128();
}
function _getSpendableERC20BalanceOf(
IERC20TokenV06 token,
address owner
)
internal
view
returns (uint256)
{
return LibSafeMathV06.min256(
token.allowance(owner, address(this)),
token.balanceOf(owner)
);
}
}

View File

@@ -39,7 +39,7 @@
"config": {
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
"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|FakeTaker|IBalancer|IBancor|ICurve|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|FakeTaker).json",
"postpublish": {
"assets": []
}
@@ -80,8 +80,9 @@
"@ethersproject/contracts": "^5.0.1",
"@ethersproject/providers": "^5.0.4",
"@ethersproject/strings": "^5.0.10",
"axios": "^0.21.1",
"axios-mock-adapter": "^1.19.0",
"@open-rpc/client-js": "^1.7.1",
"axios": "^0.24.0",
"axios-mock-adapter": "^1.20.0",
"cream-sor": "^0.3.3",
"decimal.js": "^10.2.0",
"ethereum-types": "^3.6.0",

View File

@@ -1,4 +1,3 @@
import { ChainId } from '@0x/contract-addresses';
import { SignatureType } from '@0x/protocol-utils';
import { BigNumber, logUtils } from '@0x/utils';
@@ -11,12 +10,9 @@ import {
RfqRequestOpts,
SwapQuoteGetOutputOpts,
SwapQuoteRequestOpts,
SwapQuoterOpts,
} from './types';
import {
DEFAULT_GET_MARKET_ORDERS_OPTS,
DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID,
DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID,
} from './utils/market_operation_utils/constants';
const ETH_GAS_STATION_API_URL = 'https://ethgasstation.info/api/ethgasAPI.json';
@@ -43,20 +39,6 @@ const PROTOCOL_FEE_MULTIPLIER = new BigNumber(0);
// default 50% buffer for selecting native orders to be aggregated with other sources
const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5;
const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
chainId: ChainId.Mainnet,
orderRefreshIntervalMs: 10000, // 10 seconds
...DEFAULT_ORDER_PRUNER_OPTS,
samplerGasLimit: 500e6,
ethGasStationUrl: ETH_GAS_STATION_API_URL,
rfqt: {
integratorsWhitelist: [],
makerAssetOfferings: {},
txOriginBlacklist: new Set(),
},
tokenAdjacencyGraph: DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID[ChainId.Mainnet],
};
const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts = {
isFromETH: false,
isToETH: false,
@@ -91,8 +73,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
@@ -111,8 +91,6 @@ export const constants = {
ONE_AMOUNT: new BigNumber(1),
ONE_SECOND_MS,
ONE_MINUTE_MS,
DEFAULT_SWAP_QUOTER_OPTS,
DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID,
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
DEFAULT_EXCHANGE_PROXY_SWAP_QUOTE_GET_OPTS,
DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,

View File

@@ -109,6 +109,7 @@ export {
SwapQuoteGetOutputOpts,
SwapQuoteInfo,
SwapQuoteOrdersBreakdown,
SwapQuoteMultiHopBreakdown,
SwapQuoteRequestOpts,
SwapQuoterError,
SwapQuoterOpts,
@@ -116,48 +117,24 @@ export {
} from './types';
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,
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
} from './utils/market_operation_utils/constants';
export {
Parameters,
SamplerContractCall,
SamplerContractOperation,
} from './utils/market_operation_utils/sampler_contract_operation';
export {
BalancerFillData,
BancorFillData,
CollapsedFill,
CurveFillData,
CurveFunctionSelectors,
CurveInfo,
DexSample,
DODOFillData,
ERC20BridgeSource,
ExchangeProxyOverhead,
FeeSchedule,
Fill,
FillData,
GetMarketOrdersRfqOpts,
KyberFillData,
LiquidityProviderFillData,
LiquidityProviderRegistry,
MarketDepth,
MarketDepthSide,
MooniswapFillData,
MultiHopFillData,
NativeCollapsedFill,
NativeRfqOrderFillData,
NativeLimitOrderFillData,
NativeFillData,
OptimizedMarketOrder,
SourceQuoteOperation,
TokenAdjacencyGraph,
UniswapV2FillData,
} from './utils/market_operation_utils/types';
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
export {

View File

@@ -12,12 +12,14 @@ import {
FillQuoteTransformerSide,
findTransformerNonce,
} from '@0x/protocol-utils';
import { BigNumber } from '@0x/utils';
import { BigNumber, hexUtils } from '@0x/utils';
import * as _ from 'lodash';
import { constants, POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS } from '../constants';
import {
Address,
AffiliateFeeType,
Bytes,
CalldataInfo,
ExchangeProxyContractOpts,
MarketBuySwapQuote,
@@ -28,23 +30,22 @@ import {
SwapQuoteConsumerOpts,
SwapQuoteExecutionOpts,
SwapQuoteGetOutputOpts,
SwapQuoteLiquidityProviderBridgeOrder,
SwapQuoteUniswapV2BridgeOrder,
SwapQuoteUniswapV3BridgeOrder,
SwapQuoteCurveBridgeOrder,
SwapQuoteMooniswapBridgeOrder,
SwapQuoteHop,
SwapQuoteGenericBridgeOrder,
SwapQuoteOrder,
} from '../types';
import { assert } from '../utils/assert';
import { valueByChainId } from '../utils/utils';
import {
CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID,
MOONISWAP_LIQUIDITY_PROVIDER_BY_CHAIN_ID,
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
} from '../utils/market_operation_utils/constants';
import { poolEncoder } from '../utils/market_operation_utils/orders';
import {
CurveFillData,
ERC20BridgeSource,
FinalUniswapV3FillData,
LiquidityProviderFillData,
MooniswapFillData,
OptimizedMarketBridgeOrder,
OptimizedMarketOrder,
UniswapV2FillData,
} from '../utils/market_operation_utils/types';
import {
@@ -53,6 +54,7 @@ import {
MultiplexSubcall,
multiplexTransformERC20Encoder,
multiplexUniswapEncoder,
multiplexBatchSellEncoder,
} from './multiplex_encoders';
import {
getFQTTransformerDataFromOptimizedOrders,
@@ -77,12 +79,30 @@ const PANCAKE_SWAP_FORKS = [
ERC20BridgeSource.CheeseSwap,
ERC20BridgeSource.JulSwap,
];
const FAKE_PROVIDER: any = {
sendAsync(): void {
return;
},
};
const CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID = valueByChainId<string>(
{
[ChainId.Mainnet]: '0x561b94454b65614ae3db0897b74303f4acf7cc75',
[ChainId.Ropsten]: '0xae241c6fc7f28f6dc0cb58b4112ba7f63fcaf5e2',
},
NULL_ADDRESS,
);
const MOONISWAP_LIQUIDITY_PROVIDER_BY_CHAIN_ID = valueByChainId<string>(
{
[ChainId.Mainnet]: '0xa2033d6ba88756ce6a87584d69dc87bda9a4f889',
[ChainId.Ropsten]: '0x87e0393aee0fb8c10b8653c6507c182264fe5a34',
},
NULL_ADDRESS,
);
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
public readonly chainId: ChainId;
public readonly transformerNonces: {
@@ -95,9 +115,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
private readonly _exchangeProxy: IZeroExContract;
constructor(public readonly contractAddresses: ContractAddresses, options: Partial<SwapQuoteConsumerOpts> = {}) {
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
assert.isNumber('chainId', chainId);
constructor(public readonly contractAddresses: ContractAddresses, options: SwapQuoteConsumerOpts) {
const { chainId } = options;
this.chainId = chainId;
this.contractAddresses = contractAddresses;
this._exchangeProxy = new IZeroExContract(contractAddresses.exchangeProxy, FAKE_PROVIDER);
@@ -151,15 +170,14 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
ethAmount = ethAmount.plus(sellAmount);
}
const slippedOrders = slipNonNativeOrders(quote);
// VIP routes.
if (
this.chainId === ChainId.Mainnet &&
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap])
) {
const source = slippedOrders[0].source;
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
const order = quote.hops[0].orders[0] as SwapQuoteUniswapV2BridgeOrder;
const { source } = order;
const { fillData } = order;
return {
calldataHexString: this._exchangeProxy
.sellToUniswap(
@@ -188,19 +206,20 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
this.chainId === ChainId.Mainnet &&
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV3])
) {
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
const order = quote.hops[0].orders[0] as SwapQuoteUniswapV3BridgeOrder;
const { fillData } = order;
let _calldataHexString;
if (isFromETH) {
_calldataHexString = this._exchangeProxy
.sellEthForTokenToUniswapV3(fillData.uniswapPath, minBuyAmount, NULL_ADDRESS)
.sellEthForTokenToUniswapV3(fillData.encodedPath, minBuyAmount, NULL_ADDRESS)
.getABIEncodedTransactionData();
} else if (isToETH) {
_calldataHexString = this._exchangeProxy
.sellTokenForEthToUniswapV3(fillData.uniswapPath, sellAmount, minBuyAmount, NULL_ADDRESS)
.sellTokenForEthToUniswapV3(fillData.encodedPath, sellAmount, minBuyAmount, NULL_ADDRESS)
.getABIEncodedTransactionData();
} else {
_calldataHexString = this._exchangeProxy
.sellTokenForTokenToUniswapV3(fillData.uniswapPath, sellAmount, minBuyAmount, NULL_ADDRESS)
.sellTokenForTokenToUniswapV3(fillData.encodedPath, sellAmount, minBuyAmount, NULL_ADDRESS)
.getABIEncodedTransactionData();
}
return {
@@ -225,8 +244,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
ERC20BridgeSource.JulSwap,
])
) {
const source = slippedOrders[0].source;
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
const order = quote.hops[0].orders[0] as SwapQuoteUniswapV2BridgeOrder;
const { source, fillData } = order;
return {
calldataHexString: this._exchangeProxy
.sellToPancakeSwap(
@@ -255,14 +274,13 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
[ChainId.Mainnet, ChainId.BSC].includes(this.chainId) &&
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.LiquidityProvider])
) {
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
const target = fillData.poolAddress;
const { fillData } = quote.hops[0].orders[0] as SwapQuoteLiquidityProviderBridgeOrder;
return {
calldataHexString: this._exchangeProxy
.sellToLiquidityProvider(
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
target,
fillData.poolAddress,
NULL_ADDRESS,
sellAmount,
minBuyAmount,
@@ -284,7 +302,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
// ETH buy/sell is supported
![sellToken, buyToken].includes(NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet])
) {
const fillData = slippedOrders[0].fills[0].fillData as CurveFillData;
const { fillData } = quote.hops[0].orders[0] as SwapQuoteCurveBridgeOrder;
return {
calldataHexString: this._exchangeProxy
.sellToLiquidityProvider(
@@ -295,8 +313,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
sellAmount,
minBuyAmount,
encodeCurveLiquidityProviderData({
curveAddress: fillData.pool.poolAddress,
exchangeFunctionSelector: fillData.pool.exchangeFunctionSelector,
curveAddress: fillData.poolAddress,
exchangeFunctionSelector: fillData.exchangeFunctionSelector,
fromCoinIdx: new BigNumber(fillData.fromTokenIdx),
toCoinIdx: new BigNumber(fillData.toTokenIdx),
}),
@@ -313,7 +331,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
this.chainId === ChainId.Mainnet &&
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Mooniswap])
) {
const fillData = slippedOrders[0].fills[0].fillData as MooniswapFillData;
const { fillData } = quote.hops[0].orders[0] as SwapQuoteMooniswapBridgeOrder;
return {
calldataHexString: this._exchangeProxy
.sellToLiquidityProvider(
@@ -323,7 +341,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
NULL_ADDRESS,
sellAmount,
minBuyAmount,
poolEncoder.encode([fillData.poolAddress]),
encodeAddress(fillData.poolAddress),
)
.getABIEncodedTransactionData(),
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
@@ -336,7 +354,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) {
return {
calldataHexString: this._encodeMultiplexBatchFillCalldata(
{ ...quote, orders: slippedOrders },
quote.hops[0],
optsWithDefaults,
),
ethAmount,
@@ -345,10 +363,13 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
gasOverhead: ZERO_AMOUNT,
};
}
// Sort hops so they always flow taker -> maker
const orderedHops = isBuyQuote(quote) ? quote.hops.slice().reverse() : quote.hops;
if (this.chainId === ChainId.Mainnet && isMultiplexMultiHopFillCompatible(quote, optsWithDefaults)) {
return {
calldataHexString: this._encodeMultiplexMultiHopFillCalldata(
{ ...quote, orders: slippedOrders },
orderedHops,
optsWithDefaults,
),
ethAmount,
@@ -371,45 +392,26 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
});
}
// If it's two hop we have an intermediate token this is needed to encode the individual FQT
// and we also want to ensure no dust amount is left in the flash wallet
const intermediateToken = quote.isTwoHop ? slippedOrders[0].makerToken : NULL_ADDRESS;
// This transformer will fill the quote.
if (quote.isTwoHop) {
const [firstHopOrder, secondHopOrder] = slippedOrders;
for (const [i, hop] of orderedHops.entries()) {
let fillAmount = !isBuyQuote(quote)
? shouldSellEntireBalance ? MAX_UINT256 : hop.takerAmount
: hop.makerAmount;
let side = !isBuyQuote(quote) ? FillQuoteTransformerSide.Sell : FillQuoteTransformerSide.Buy;
if (orderedHops.length > 1) { // Multi-hop.
// Multi-hop is always a sell.
side = FillQuoteTransformerSide.Sell;
// Subsequent multi-hops always sell entire balance.
fillAmount = i > 0 ? MAX_UINT256 : hop.takerAmount;
}
transforms.push({
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
data: encodeFillQuoteTransformerData({
side: FillQuoteTransformerSide.Sell,
sellToken,
buyToken: intermediateToken,
...getFQTTransformerDataFromOptimizedOrders([firstHopOrder]),
side,
fillAmount,
sellToken: hop.takerToken,
buyToken: hop.makerToken,
...getFQTTransformerDataFromOptimizedOrders(hop.orders),
refundReceiver: refundReceiver || NULL_ADDRESS,
fillAmount: shouldSellEntireBalance ? MAX_UINT256 : firstHopOrder.takerAmount,
}),
});
transforms.push({
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
data: encodeFillQuoteTransformerData({
side: FillQuoteTransformerSide.Sell,
buyToken,
sellToken: intermediateToken,
...getFQTTransformerDataFromOptimizedOrders([secondHopOrder]),
refundReceiver: refundReceiver || NULL_ADDRESS,
fillAmount: MAX_UINT256,
}),
});
} else {
const fillAmount = isBuyQuote(quote) ? quote.makerTokenFillAmount : quote.takerTokenFillAmount;
transforms.push({
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
data: encodeFillQuoteTransformerData({
side: isBuyQuote(quote) ? FillQuoteTransformerSide.Buy : FillQuoteTransformerSide.Sell,
sellToken,
buyToken,
...getFQTTransformerDataFromOptimizedOrders(slippedOrders),
refundReceiver: refundReceiver || NULL_ADDRESS,
fillAmount: !isBuyQuote(quote) && shouldSellEntireBalance ? MAX_UINT256 : fillAmount,
}),
});
}
@@ -475,10 +477,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
// Return any unspent sell tokens.
const payTakerTokens = [sellToken];
// Return any unspent intermediate tokens for two-hop swaps.
if (quote.isTwoHop) {
payTakerTokens.push(intermediateToken);
}
// Return any unspent ETH. If ETH is the buy token, it will
// be returned in TransformERC20Feature rather than PayTakerTransformer.
if (!isToETH) {
@@ -519,9 +517,108 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
throw new Error('Execution not supported for Exchange Proxy quotes');
}
private _encodeMultiplexBatchFillCalldata(quote: SwapQuote, opts: ExchangeProxyContractOpts): string {
private _encodeMultiplexBatchFillCalldata(hop: SwapQuoteHop, opts: ExchangeProxyContractOpts): string {
const subcalls = this._getMultiplexBatchSellSubcalls(hop.orders);
if (opts.isFromETH) {
return this._exchangeProxy
.multiplexBatchSellEthForToken(hop.makerToken, subcalls, hop.minMakerAmount)
.getABIEncodedTransactionData();
} else if (opts.isToETH) {
return this._exchangeProxy
.multiplexBatchSellTokenForEth(
hop.takerToken,
subcalls,
hop.maxTakerAmount,
hop.minMakerAmount,
)
.getABIEncodedTransactionData();
} else {
return this._exchangeProxy
.multiplexBatchSellTokenForToken(
hop.takerToken,
hop.makerToken,
subcalls,
hop.maxTakerAmount,
hop.minMakerAmount,
)
.getABIEncodedTransactionData();
}
}
private _encodeMultiplexMultiHopFillCalldata(hops: SwapQuoteHop[], opts: ExchangeProxyContractOpts): string {
const subcalls = [];
for_loop: for (const [i, order] of quote.orders.entries()) {
for (const hop of hops) {
if (hop.orders.length !== 1) {
subcalls.push({
id: MultiplexSubcall.BatchSell,
data: multiplexBatchSellEncoder.encode({ subcalls: this._getMultiplexBatchSellSubcalls(hop.orders) }),
});
continue;
}
const order = hop.orders[0] as SwapQuoteGenericBridgeOrder;
switch (order.source) {
case ERC20BridgeSource.UniswapV2:
case ERC20BridgeSource.SushiSwap:
subcalls.push({
id: MultiplexSubcall.UniswapV2,
data: multiplexUniswapEncoder.encode({
tokens: (order as SwapQuoteUniswapV2BridgeOrder).fillData.tokenAddressPath,
isSushi: order.source === ERC20BridgeSource.SushiSwap,
}),
});
break;
case ERC20BridgeSource.LiquidityProvider:
subcalls.push({
id: MultiplexSubcall.LiquidityProvider,
data: multiplexPlpEncoder.encode({
provider: (order as SwapQuoteLiquidityProviderBridgeOrder).fillData.poolAddress,
auxiliaryData: NULL_BYTES,
}),
});
break;
case ERC20BridgeSource.UniswapV3:
subcalls.push({
id: MultiplexSubcall.UniswapV3,
data: (order as SwapQuoteUniswapV3BridgeOrder).fillData.encodedPath,
});
break;
default:
// Should never happen because we check `isMultiplexMultiHopFillCompatible`
// before calling this function.
throw new Error(`Multiplex multi-hop unsupported source: ${order.source}`);
}
}
const tokenPath = getTokenPathFromHops(hops);
const firstHop = hops[0];
const lastHop = hops[hops.length - 1];
if (opts.isFromETH) {
return this._exchangeProxy
.multiplexMultiHopSellEthForToken(tokenPath, subcalls, lastHop.minMakerAmount)
.getABIEncodedTransactionData();
} else if (opts.isToETH) {
return this._exchangeProxy
.multiplexMultiHopSellTokenForEth(
tokenPath,
subcalls,
firstHop.maxTakerAmount,
lastHop.minMakerAmount,
)
.getABIEncodedTransactionData();
} else {
return this._exchangeProxy
.multiplexMultiHopSellTokenForToken(
tokenPath,
subcalls,
firstHop.maxTakerAmount,
lastHop.minMakerAmount,
)
.getABIEncodedTransactionData();
}
}
private _getMultiplexBatchSellSubcalls(orders: SwapQuoteOrder[]): any[] {
const subcalls = [];
for_loop: for (const [i, order] of orders.entries()) {
switch_statement: switch (order.source) {
case ERC20BridgeSource.Native:
if (order.type !== FillQuoteTransformerOrderType.Rfq) {
@@ -542,9 +639,9 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
case ERC20BridgeSource.SushiSwap:
subcalls.push({
id: MultiplexSubcall.UniswapV2,
sellAmount: order.takerAmount,
sellAmount: (order as SwapQuoteUniswapV2BridgeOrder).maxTakerAmount,
data: multiplexUniswapEncoder.encode({
tokens: (order.fillData as UniswapV2FillData).tokenAddressPath,
tokens: (order as SwapQuoteUniswapV2BridgeOrder).fillData.tokenAddressPath,
isSushi: order.source === ERC20BridgeSource.SushiSwap,
}),
});
@@ -552,43 +649,46 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
case ERC20BridgeSource.LiquidityProvider:
subcalls.push({
id: MultiplexSubcall.LiquidityProvider,
sellAmount: order.takerAmount,
sellAmount: (order as SwapQuoteLiquidityProviderBridgeOrder).maxTakerAmount,
data: multiplexPlpEncoder.encode({
provider: (order.fillData as LiquidityProviderFillData).poolAddress,
provider: (order as SwapQuoteLiquidityProviderBridgeOrder).fillData.poolAddress,
auxiliaryData: NULL_BYTES,
}),
});
break switch_statement;
case ERC20BridgeSource.UniswapV3:
const fillData = (order as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
subcalls.push({
id: MultiplexSubcall.UniswapV3,
sellAmount: order.takerAmount,
data: fillData.uniswapPath,
sellAmount: (order as SwapQuoteUniswapV3BridgeOrder).maxTakerAmount,
data: (order as SwapQuoteUniswapV3BridgeOrder).fillData.encodedPath,
});
break switch_statement;
default:
const fqtData = encodeFillQuoteTransformerData({
side: FillQuoteTransformerSide.Sell,
sellToken: quote.takerToken,
buyToken: quote.makerToken,
...getFQTTransformerDataFromOptimizedOrders(quote.orders.slice(i)),
sellToken: order.takerToken,
buyToken: order.makerToken,
...getFQTTransformerDataFromOptimizedOrders(orders.slice(i)),
refundReceiver: NULL_ADDRESS,
fillAmount: MAX_UINT256,
});
const transformations = [
{ deploymentNonce: this.transformerNonces.fillQuoteTransformer, data: fqtData },
{
deploymentNonce: this.transformerNonces.payTakerTransformer,
data: encodePayTakerTransformerData({
tokens: [quote.takerToken],
amounts: [],
}),
},
// TODO(lawrence): needed?
// {
// deploymentNonce: this.transformerNonces.payTakerTransformer,
// data: encodePayTakerTransformerData({
// tokens: [hop.takerToken],
// amounts: [],
// }),
// },
];
subcalls.push({
id: MultiplexSubcall.TransformERC20,
sellAmount: BigNumber.sum(...quote.orders.slice(i).map(o => o.takerAmount)),
sellAmount: BigNumber.sum(
...orders.slice(i)
.map(o => (o as SwapQuoteGenericBridgeOrder).maxTakerAmount),
),
data: multiplexTransformERC20Encoder.encode({
transformations,
}),
@@ -596,128 +696,21 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
break for_loop;
}
}
if (opts.isFromETH) {
return this._exchangeProxy
.multiplexBatchSellEthForToken(quote.makerToken, subcalls, quote.worstCaseQuoteInfo.makerAmount)
.getABIEncodedTransactionData();
} else if (opts.isToETH) {
return this._exchangeProxy
.multiplexBatchSellTokenForEth(
quote.takerToken,
subcalls,
quote.worstCaseQuoteInfo.totalTakerAmount,
quote.worstCaseQuoteInfo.makerAmount,
)
.getABIEncodedTransactionData();
} else {
return this._exchangeProxy
.multiplexBatchSellTokenForToken(
quote.takerToken,
quote.makerToken,
subcalls,
quote.worstCaseQuoteInfo.totalTakerAmount,
quote.worstCaseQuoteInfo.makerAmount,
)
.getABIEncodedTransactionData();
}
}
private _encodeMultiplexMultiHopFillCalldata(quote: SwapQuote, opts: ExchangeProxyContractOpts): string {
const subcalls = [];
const [firstHopOrder, secondHopOrder] = quote.orders;
const intermediateToken = firstHopOrder.makerToken;
const tokens = [quote.takerToken, intermediateToken, quote.makerToken];
for (const order of [firstHopOrder, secondHopOrder]) {
switch (order.source) {
case ERC20BridgeSource.UniswapV2:
case ERC20BridgeSource.SushiSwap:
subcalls.push({
id: MultiplexSubcall.UniswapV2,
data: multiplexUniswapEncoder.encode({
tokens: (order.fillData as UniswapV2FillData).tokenAddressPath,
isSushi: order.source === ERC20BridgeSource.SushiSwap,
}),
});
break;
case ERC20BridgeSource.LiquidityProvider:
subcalls.push({
id: MultiplexSubcall.LiquidityProvider,
data: multiplexPlpEncoder.encode({
provider: (order.fillData as LiquidityProviderFillData).poolAddress,
auxiliaryData: NULL_BYTES,
}),
});
break;
case ERC20BridgeSource.UniswapV3:
subcalls.push({
id: MultiplexSubcall.UniswapV3,
data: (order.fillData as FinalUniswapV3FillData).uniswapPath,
});
break;
default:
// Should never happen because we check `isMultiplexMultiHopFillCompatible`
// before calling this function.
throw new Error(`Multiplex multi-hop unsupported source: ${order.source}`);
}
}
if (opts.isFromETH) {
return this._exchangeProxy
.multiplexMultiHopSellEthForToken(tokens, subcalls, quote.worstCaseQuoteInfo.makerAmount)
.getABIEncodedTransactionData();
} else if (opts.isToETH) {
return this._exchangeProxy
.multiplexMultiHopSellTokenForEth(
tokens,
subcalls,
quote.worstCaseQuoteInfo.totalTakerAmount,
quote.worstCaseQuoteInfo.makerAmount,
)
.getABIEncodedTransactionData();
} else {
return this._exchangeProxy
.multiplexMultiHopSellTokenForToken(
tokens,
subcalls,
quote.worstCaseQuoteInfo.totalTakerAmount,
quote.worstCaseQuoteInfo.makerAmount,
)
.getABIEncodedTransactionData();
}
return subcalls;
}
}
function slipNonNativeOrders(quote: MarketSellSwapQuote | MarketBuySwapQuote): OptimizedMarketOrder[] {
const slippage = getMaxQuoteSlippageRate(quote);
if (!slippage) {
return quote.orders;
}
return quote.orders.map(o => {
if (o.source === ERC20BridgeSource.Native) {
return o;
function getTokenPathFromHops(hops: SwapQuoteHop[]): Address[] {
const path = [];
for (const [i, hop] of hops.entries()) {
path.push(hop.takerToken);
if (i === path.length - 1) {
path.push(hop.makerToken);
}
return {
...o,
...(quote.type === MarketOperation.Sell
? { makerAmount: o.makerAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN) }
: { takerAmount: o.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP) }),
};
});
}
return path;
}
function getMaxQuoteSlippageRate(quote: MarketBuySwapQuote | MarketSellSwapQuote): number {
if (quote.type === MarketOperation.Buy) {
// (worstCaseTaker - bestCaseTaker) / bestCaseTaker
// where worstCaseTaker >= bestCaseTaker
return quote.worstCaseQuoteInfo.takerAmount
.minus(quote.bestCaseQuoteInfo.takerAmount)
.div(quote.bestCaseQuoteInfo.takerAmount)
.toNumber();
}
// (bestCaseMaker - worstCaseMaker) / bestCaseMaker
// where bestCaseMaker >= worstCaseMaker
return quote.bestCaseQuoteInfo.makerAmount
.minus(quote.worstCaseQuoteInfo.makerAmount)
.div(quote.bestCaseQuoteInfo.makerAmount)
.toNumber();
function encodeAddress(address: Address): Bytes {
return hexUtils.leftPad(hexUtils.slice(address, 0, 20));
}

View File

@@ -12,6 +12,7 @@ export enum MultiplexSubcall {
BatchSell,
MultiHopSell,
}
export const multiplexTransformERC20Encoder = AbiEncoder.create([
{
name: 'transformations',
@@ -22,15 +23,30 @@ export const multiplexTransformERC20Encoder = AbiEncoder.create([
],
},
]);
export const multiplexRfqEncoder = AbiEncoder.create([
{ name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI },
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
]);
export const multiplexUniswapEncoder = AbiEncoder.create([
{ name: 'tokens', type: 'address[]' },
{ name: 'isSushi', type: 'bool' },
]);
export const multiplexPlpEncoder = AbiEncoder.create([
{ name: 'provider', type: 'address' },
{ name: 'auxiliaryData', type: 'bytes' },
]);
export const multiplexBatchSellEncoder = AbiEncoder.create([
{
name: 'subcalls',
type: 'tuple[]',
components: [
{ name: 'id', type: 'uint8' },
{ name: 'sellAmount', type: 'uint256' },
{ name: 'data', type: 'bytes' },
],
},
]);

View File

@@ -2,17 +2,17 @@ import { FillQuoteTransformerData, FillQuoteTransformerOrderType } from '@0x/pro
import { ExchangeProxyContractOpts, MarketBuySwapQuote, MarketOperation, SwapQuote } from '../types';
import {
createBridgeDataForBridgeOrder,
getErc20BridgeSourceToBridgeSource,
} from '../utils/market_operation_utils/orders';
import {
ERC20BridgeSource,
NativeLimitOrderFillData,
NativeRfqOrderFillData,
OptimizedMarketBridgeOrder,
OptimizedMarketOrder,
OptimizedMarketOrderBase,
} from '../utils/market_operation_utils/types';
import {
SwapQuoteGenericBridgeOrder,
SwapQuoteOrder,
SwapQuoteLimitOrder,
SwapQuoteRfqOrder,
} from '../types';
const MULTIPLEX_BATCH_FILL_SOURCES = [
ERC20BridgeSource.UniswapV2,
@@ -29,16 +29,19 @@ export function isMultiplexBatchFillCompatible(quote: SwapQuote, opts: ExchangeP
if (requiresTransformERC20(opts)) {
return false;
}
if (quote.isTwoHop) {
// Must not be multi-hop.
if (quote.hops.length > 1) {
return false;
}
if (quote.orders.map(o => o.type).includes(FillQuoteTransformerOrderType.Limit)) {
// Must not contain limit orders.
const allOrderTypes = quote.hops.map(h => h.orders.map(o => o.type)).flat(2);
if (allOrderTypes.includes(FillQuoteTransformerOrderType.Limit)) {
return false;
}
// Use Multiplex if the non-fallback sources are a subset of
// {UniswapV2, Sushiswap, RFQ, PLP, UniswapV3}
const nonFallbackSources = Object.keys(quote.sourceBreakdown);
return nonFallbackSources.every(source => MULTIPLEX_BATCH_FILL_SOURCES.includes(source as ERC20BridgeSource));
const nonFallbackSources = quote.hops.map(h => h.orders.filter(o => !o.isFallback).map(o => o.source)).flat(2);
return nonFallbackSources.every(s => MULTIPLEX_BATCH_FILL_SOURCES.includes(s));
}
const MULTIPLEX_MULTIHOP_FILL_SOURCES = [
@@ -55,14 +58,12 @@ export function isMultiplexMultiHopFillCompatible(quote: SwapQuote, opts: Exchan
if (requiresTransformERC20(opts)) {
return false;
}
if (!quote.isTwoHop) {
// Must be multi-hop.
if (quote.hops.length < 2) {
return false;
}
const [firstHopOrder, secondHopOrder] = quote.orders;
return (
MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(firstHopOrder.source) &&
MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(secondHopOrder.source)
);
const sources = quote.hops.map(h => h.orders.map(o => o.source)).flat(2);
return sources.every(s => MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(s));
}
/**
@@ -77,11 +78,11 @@ export function isDirectSwapCompatible(
if (requiresTransformERC20(opts)) {
return false;
}
// Must be a single order.
if (quote.orders.length !== 1) {
// Must be a single hop with a single order.
if (quote.hops.length !== 1 || quote.hops[0].orders.length !== 1) {
return false;
}
const order = quote.orders[0];
const order = quote.hops[0].orders[0];
if (!directSources.includes(order.source)) {
return false;
}
@@ -95,24 +96,24 @@ export function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
return quote.type === MarketOperation.Buy;
}
function isOptimizedBridgeOrder(x: OptimizedMarketOrder): x is OptimizedMarketBridgeOrder {
function isBridgeOrder(x: SwapQuoteOrder): x is SwapQuoteGenericBridgeOrder {
return x.type === FillQuoteTransformerOrderType.Bridge;
}
function isOptimizedLimitOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeLimitOrderFillData> {
return x.type === FillQuoteTransformerOrderType.Limit;
}
function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeRfqOrderFillData> {
return x.type === FillQuoteTransformerOrderType.Rfq;
}
// function isOptimizedLimitOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeLimitOrderFillData> {
// return x.type === FillQuoteTransformerOrderType.Limit;
// }
//
// function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeRfqOrderFillData> {
// return x.type === FillQuoteTransformerOrderType.Rfq;
// }
/**
* Converts the given `OptimizedMarketOrder`s into bridge, limit, and RFQ orders for
* FillQuoteTransformer.
*/
export function getFQTTransformerDataFromOptimizedOrders(
orders: OptimizedMarketOrder[],
orders: SwapQuoteOrder[],
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> {
const fqtData: Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> = {
bridgeOrders: [],
@@ -122,25 +123,25 @@ export function getFQTTransformerDataFromOptimizedOrders(
};
for (const order of orders) {
if (isOptimizedBridgeOrder(order)) {
if (isBridgeOrder(order)) {
fqtData.bridgeOrders.push({
bridgeData: createBridgeDataForBridgeOrder(order),
makerTokenAmount: order.makerAmount,
takerTokenAmount: order.takerAmount,
bridgeData: order.fillData.encodedFillData,
makerTokenAmount: order.minMakerAmount,
takerTokenAmount: order.maxTakerAmount,
source: getErc20BridgeSourceToBridgeSource(order.source),
});
} else if (isOptimizedLimitOrder(order)) {
fqtData.limitOrders.push({
order: order.fillData.order,
signature: order.fillData.signature,
maxTakerTokenFillAmount: order.takerAmount,
});
} else if (isOptimizedRfqOrder(order)) {
fqtData.rfqOrders.push({
order: order.fillData.order,
signature: order.fillData.signature,
maxTakerTokenFillAmount: order.takerAmount,
});
// } else if (isOptimizedLimitOrder(order)) {
// fqtData.limitOrders.push({
// order: order.fillData.order,
// signature: order.fillData.signature,
// maxTakerTokenFillAmount: order.takerAmount,
// });
// } else if (isOptimizedRfqOrder(order)) {
// fqtData.rfqOrders.push({
// order: order.fillData.order,
// signature: order.fillData.signature,
// maxTakerTokenFillAmount: order.takerAmount,
// });
} else {
// Should never happen
throw new Error('Unknown Order type');

View File

@@ -20,13 +20,12 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
private readonly _contractAddresses: ContractAddresses;
private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer;
public static getSwapQuoteConsumer(options: Partial<SwapQuoteConsumerOpts> = {}): SwapQuoteConsumer {
public static getSwapQuoteConsumer(options: SwapQuoteConsumerOpts): SwapQuoteConsumer {
return new SwapQuoteConsumer(options);
}
constructor(options: Partial<SwapQuoteConsumerOpts> = {}) {
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
assert.isNumber('chainId', chainId);
constructor(options: SwapQuoteConsumerOpts) {
const { chainId } = options;
this.chainId = chainId;
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);

View File

@@ -1,16 +1,15 @@
import { ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
import { FillQuoteTransformerOrderType, LimitOrder } from '@0x/protocol-utils';
import { BigNumber, providerUtils } from '@0x/utils';
import Axios, { AxiosInstance } from 'axios';
import { BlockParamLiteral, MethodAbi, SupportedProvider, ZeroExProvider } from 'ethereum-types';
import { FastABI } from 'fast-abi';
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
import { Agent as HttpAgent } from 'http';
import { Agent as HttpsAgent } from 'https';
import * as _ from 'lodash';
import { artifacts } from './artifacts';
import { constants, INVALID_SIGNATURE, KEEP_ALIVE_TTL } from './constants';
import {
Address,
AssetSwapperContractAddresses,
MarketBuySwapQuote,
MarketOperation,
@@ -19,6 +18,10 @@ import {
SignedNativeOrder,
SwapQuote,
SwapQuoteInfo,
SwapQuoteHop,
SwapQuoteOrder,
SwapQuoteGenericBridgeOrder,
SwapQuoteNativeOrder,
SwapQuoteOrdersBreakdown,
SwapQuoteRequestOpts,
SwapQuoterOpts,
@@ -26,25 +29,26 @@ import {
} from './types';
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 { DexOrderSampler } from './utils/market_operation_utils/sampler';
import { ZERO_AMOUNT } from './utils/market_operation_utils/constants';
import { SamplerClient } from './utils/market_operation_utils/sampler';
import { SourceFilters } from './utils/market_operation_utils/source_filters';
import {
ERC20BridgeSource,
FeeSchedule,
FillData,
GetMarketOrdersOpts,
MarketDepth,
MarketDepthSide,
MarketSideLiquidity,
OptimizedMarketOrder,
OptimizedHop,
OptimizedOrder,
OptimizedBridgeOrder,
OptimizedLimitOrder,
OptimizedRfqOrder,
OptimizedGenericBridgeOrder,
OptimizerResultWithReport,
} from './utils/market_operation_utils/types';
import { ProtocolFeeUtils } from './utils/protocol_fee_utils';
import { QuoteRequestor } from './utils/quote_requestor';
import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './utils/quote_simulation';
import { ERC20BridgeSamplerContract } from './wrappers';
export abstract class Orderbook {
public abstract getOrdersAsync(
@@ -85,20 +89,15 @@ export class SwapQuoter {
*
* @return An instance of SwapQuoter
*/
constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: Partial<SwapQuoterOpts> = {}) {
constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: SwapQuoterOpts) {
const {
chainId,
expiryBufferMs,
permittedOrderFeeTypes,
samplerGasLimit,
rfqt,
tokenAdjacencyGraph,
liquidityProviderRegistry,
} = { ...constants.DEFAULT_SWAP_QUOTER_OPTS, ...options };
} = options;
const provider = providerUtils.standardizeOrThrow(supportedProvider);
assert.isValidOrderbook('orderbook', orderbook);
assert.isNumber('chainId', chainId);
assert.isNumber('expiryBufferMs', expiryBufferMs);
this.chainId = chainId;
this.provider = provider;
this.orderbook = orderbook;
@@ -113,45 +112,11 @@ export class SwapQuoter {
constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
options.ethGasStationUrl,
);
// 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;
const defaultCodeOverrides = samplerBytecode
? {
[samplerAddress]: { code: samplerBytecode },
}
: {};
const samplerOverrides = _.assign(
{ block: BlockParamLiteral.Latest, overrides: defaultCodeOverrides },
options.samplerOverrides,
);
const fastAbi = new FastABI(ERC20BridgeSamplerContract.ABI() as MethodAbi[], { BigNumber });
const samplerContract = new ERC20BridgeSamplerContract(
samplerAddress,
this.provider,
{
gas: samplerGasLimit,
},
{},
undefined,
{
encodeInput: (fnName: string, values: any) => fastAbi.encodeInput(fnName, values),
decodeOutput: (fnName: string, data: string) => fastAbi.decodeOutput(fnName, data),
},
);
this._marketOperationUtils = new MarketOperationUtils(
new DexOrderSampler(
SamplerClient.createFromChainIdAndEndpoint(
this.chainId,
samplerContract,
samplerOverrides,
undefined, // pools caches for balancer and cream
tokenAdjacencyGraph,
liquidityProviderRegistry,
this.chainId === ChainId.Mainnet // Enable Bancor only on Mainnet
? async () => BancorService.createAsync(provider)
: async () => undefined,
options.samplerServiceUrl,
),
this._contractAddresses,
{
@@ -216,7 +181,6 @@ export class SwapQuoter {
MarketOperation.Buy,
makerTokenBuyAmounts[i],
gasPrice,
opts.gasSchedule,
opts.bridgeSlippage,
);
} else {
@@ -243,49 +207,50 @@ export class SwapQuoter {
takerAssetAmount: BigNumber,
options: Partial<SwapQuoteRequestOpts> = {},
): Promise<MarketDepth> {
assert.isString('makerToken', makerToken);
assert.isString('takerToken', takerToken);
const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources);
let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native)
? [[], []]
: await Promise.all([
this.orderbook.getOrdersAsync(makerToken, takerToken),
this.orderbook.getOrdersAsync(takerToken, makerToken),
]);
if (!sellOrders || sellOrders.length === 0) {
sellOrders = [createDummyOrder(makerToken, takerToken)];
}
if (!buyOrders || buyOrders.length === 0) {
buyOrders = [createDummyOrder(takerToken, makerToken)];
}
const getMarketDepthSide = (marketSideLiquidity: MarketSideLiquidity): MarketDepthSide => {
const { dexQuotes, nativeOrders } = marketSideLiquidity.quotes;
const { side } = marketSideLiquidity;
return [
...dexQuotes,
nativeOrders.map(o => {
return {
input: side === MarketOperation.Sell ? o.fillableTakerAmount : o.fillableMakerAmount,
output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount,
fillData: o,
source: ERC20BridgeSource.Native,
};
}),
];
};
const [bids, asks] = await Promise.all([
this._marketOperationUtils.getMarketBuyLiquidityAsync(buyOrders, takerAssetAmount, options),
this._marketOperationUtils.getMarketSellLiquidityAsync(sellOrders, takerAssetAmount, options),
]);
return {
bids: getMarketDepthSide(bids),
asks: getMarketDepthSide(asks),
makerTokenDecimals: asks.makerTokenDecimals,
takerTokenDecimals: asks.takerTokenDecimals,
};
throw new Error(`Not implemented`);
// assert.isString('makerToken', makerToken);
// assert.isString('takerToken', takerToken);
// const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources);
//
// let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native)
// ? [[], []]
// : await Promise.all([
// this.orderbook.getOrdersAsync(makerToken, takerToken),
// this.orderbook.getOrdersAsync(takerToken, makerToken),
// ]);
// if (!sellOrders || sellOrders.length === 0) {
// sellOrders = [createDummyOrder(makerToken, takerToken)];
// }
// if (!buyOrders || buyOrders.length === 0) {
// buyOrders = [createDummyOrder(takerToken, makerToken)];
// }
//
// const getMarketDepthSide = (marketSideLiquidity: MarketSideLiquidity): MarketDepthSide => {
// const { dexQuotes, nativeOrders } = marketSideLiquidity.quotes;
// const { side } = marketSideLiquidity;
//
// return [
// ...dexQuotes,
// nativeOrders.map(o => {
// return {
// input: side === MarketOperation.Sell ? o.fillableTakerAmount : o.fillableMakerAmount,
// output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount,
// fillData: o,
// source: ERC20BridgeSource.Native,
// };
// }),
// ];
// };
// const [bids, asks] = await Promise.all([
// this._marketOperationUtils.getMarketBuyLiquidityAsync(buyOrders, takerAssetAmount, options),
// this._marketOperationUtils.getMarketSellLiquidityAsync(sellOrders, takerAssetAmount, options),
// ]);
// return {
// bids: getMarketDepthSide(bids),
// asks: getMarketDepthSide(asks),
// makerTokenDecimals: asks.makerTokenDecimals,
// takerTokenDecimals: asks.takerTokenDecimals,
// };
}
/**
@@ -364,9 +329,6 @@ export class SwapQuoter {
const calcOpts: GetMarketOrdersOpts = {
...cloneOpts,
gasPrice,
feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData: FillData) =>
gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)),
),
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
};
// pass the QuoteRequestor on if rfqt enabled
@@ -397,12 +359,13 @@ export class SwapQuoter {
marketOperation,
assetFillAmount,
gasPrice,
opts.gasSchedule,
opts.bridgeSlippage,
);
// Use the raw gas, not scaled by gas price
const exchangeProxyOverhead = opts.exchangeProxyOverhead(result.sourceFlags).toNumber();
const exchangeProxyOverhead = BigNumber.sum(
...result.hops.map(h => opts.exchangeProxyOverhead(h.sourceFlags)),
).toNumber();
swapQuote.bestCaseQuoteInfo.gas += exchangeProxyOverhead;
swapQuote.worstCaseQuoteInfo.gas += exchangeProxyOverhead;
@@ -496,26 +459,22 @@ function createSwapQuote(
optimizerResult: OptimizerResultWithReport,
makerToken: string,
takerToken: string,
operation: MarketOperation,
side: MarketOperation,
assetFillAmount: BigNumber,
gasPrice: BigNumber,
gasSchedule: FeeSchedule,
slippage: number,
): SwapQuote {
const {
optimizedOrders,
hops,
quoteReport,
sourceFlags,
takerAmountPerEth,
makerAmountPerEth,
priceComparisonsReport,
} = optimizerResult;
const isTwoHop = sourceFlags === SOURCE_FLAGS[ERC20BridgeSource.MultiHop];
// Calculate quote info
const { bestCaseQuoteInfo, worstCaseQuoteInfo, sourceBreakdown } = isTwoHop
? calculateTwoHopQuoteInfo(optimizedOrders, operation, gasSchedule, slippage)
: calculateQuoteInfo(optimizedOrders, operation, assetFillAmount, gasPrice, gasSchedule, slippage);
const quoteHops = hops.map(hop => toSwapQuoteHop(hop, side, slippage));
const { bestCaseQuoteInfo, worstCaseQuoteInfo, sourceBreakdown } =
calculateQuoteInfo(quoteHops, side, assetFillAmount, gasPrice, slippage);
// Put together the swap quote
const { makerTokenDecimals, takerTokenDecimals } = optimizerResult.marketSideLiquidity;
@@ -523,7 +482,6 @@ function createSwapQuote(
makerToken,
takerToken,
gasPrice,
orders: optimizedOrders,
bestCaseQuoteInfo,
worstCaseQuoteInfo,
sourceBreakdown,
@@ -532,116 +490,225 @@ function createSwapQuote(
takerAmountPerEth,
makerAmountPerEth,
quoteReport,
isTwoHop,
priceComparisonsReport,
};
if (operation === MarketOperation.Buy) {
if (side === MarketOperation.Buy) {
return {
...swapQuote,
type: MarketOperation.Buy,
makerTokenFillAmount: assetFillAmount,
maxSlippage: slippage,
hops: quoteHops,
};
} else {
return {
...swapQuote,
type: MarketOperation.Sell,
takerTokenFillAmount: assetFillAmount,
maxSlippage: slippage,
hops: quoteHops,
};
}
}
function toSwapQuoteHop(hop: OptimizedHop, side: MarketOperation, slippage: number): SwapQuoteHop {
const orders = hop.orders.map(o => toSwapQuoteOrder(o, side, slippage));
const takerAmount = side === MarketOperation.Sell ? hop.inputAmount : hop.outputAmount;
const makerAmount = side === MarketOperation.Sell ? hop.outputAmount : hop.inputAmount;
return {
orders,
makerAmount: roundMakerAmount(side, makerAmount),
takerAmount: roundTakerAmount(side, takerAmount),
makerToken: side === MarketOperation.Sell ? hop.outputToken : hop.inputToken,
takerToken: side === MarketOperation.Sell ? hop.inputToken : hop.outputToken,
minMakerAmount: slipMakerAmount(side, makerAmount, slippage),
maxTakerAmount: slipTakerAmount(side, takerAmount, slippage),
sourceFlags: hop.sourceFlags,
};
}
function roundMakerAmount(side: MarketOperation, makerAmount: BigNumber): BigNumber {
const rm = side === MarketOperation.Sell ? BigNumber.ROUND_DOWN : BigNumber.ROUND_UP;
return makerAmount.integerValue(rm);
}
function roundTakerAmount(side: MarketOperation, takerAmount: BigNumber): BigNumber {
const rm = side === MarketOperation.Sell ? BigNumber.ROUND_UP : BigNumber.ROUND_UP;
return takerAmount.integerValue(rm);
}
function slipMakerAmount(side: MarketOperation, makerAmount: BigNumber, slippage: number): BigNumber {
return roundMakerAmount(
side,
side === MarketOperation.Sell ? makerAmount.times(1 - slippage) : makerAmount,
);
}
function slipTakerAmount(side: MarketOperation, takerAmount: BigNumber, slippage: number): BigNumber {
return roundTakerAmount(
side,
side === MarketOperation.Sell ? takerAmount : takerAmount.times(1 + slippage),
);
}
function toSwapQuoteOrder(order: OptimizedOrder, side: MarketOperation, slippage: number): SwapQuoteGenericBridgeOrder | SwapQuoteNativeOrder {
const { inputToken, outputToken, inputAmount, outputAmount, ...rest } = order;
const common = {
...rest,
takerToken: side === MarketOperation.Sell ? inputToken : outputToken,
makerToken: side === MarketOperation.Sell ? outputToken : inputToken,
takerAmount: side === MarketOperation.Sell ? inputAmount : outputAmount,
makerAmount: side === MarketOperation.Sell ? outputAmount : inputAmount,
};
if (isBridgeOrder(order)) {
return {
...common,
minMakerAmount: slipMakerAmount(
side,
side === MarketOperation.Sell
? order.outputAmount
: order.inputAmount,
slippage,
),
maxTakerAmount: slipTakerAmount(
side,
side === MarketOperation.Sell
? order.inputAmount
: order.outputAmount,
slippage,
),
};
}
return common as SwapQuoteNativeOrder;
}
function isBridgeOrder(order: OptimizedOrder): order is OptimizedGenericBridgeOrder {
return order.type === FillQuoteTransformerOrderType.Bridge;
}
function calculateQuoteInfo(
optimizedOrders: OptimizedMarketOrder[],
operation: MarketOperation,
assetFillAmount: BigNumber,
hops: SwapQuoteHop[],
side: MarketOperation,
fillAmount: BigNumber,
gasPrice: BigNumber,
gasSchedule: FeeSchedule,
slippage: number,
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
const bestCaseFillResult = simulateBestCaseFill({
gasPrice,
orders: optimizedOrders,
side: operation,
fillAmount: assetFillAmount,
opts: { gasSchedule },
});
const worstCaseFillResult = simulateWorstCaseFill({
gasPrice,
orders: optimizedOrders,
side: operation,
fillAmount: assetFillAmount,
opts: { gasSchedule, slippage },
});
return {
bestCaseQuoteInfo: fillResultsToQuoteInfo(bestCaseFillResult),
worstCaseQuoteInfo: fillResultsToQuoteInfo(worstCaseFillResult),
sourceBreakdown: getSwapQuoteOrdersBreakdown(bestCaseFillResult.fillAmountBySource),
};
}
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();
return {
bestCaseQuoteInfo: {
makerAmount: operation === MarketOperation.Sell ? secondHopFill.output : secondHopFill.input,
takerAmount: operation === MarketOperation.Sell ? firstHopFill.input : firstHopFill.output,
totalTakerAmount: operation === MarketOperation.Sell ? firstHopFill.input : firstHopFill.output,
feeTakerTokenAmount: constants.ZERO_AMOUNT,
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
gas,
},
// TODO jacob consolidate this with quote simulation worstCase
worstCaseQuoteInfo: {
makerAmount: MarketOperation.Sell
? secondHopOrder.makerAmount.times(1 - slippage).integerValue()
: secondHopOrder.makerAmount,
takerAmount: MarketOperation.Sell
? firstHopOrder.takerAmount
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(),
totalTakerAmount: MarketOperation.Sell
? firstHopOrder.takerAmount
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(),
feeTakerTokenAmount: constants.ZERO_AMOUNT,
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
gas,
},
sourceBreakdown: {
[ERC20BridgeSource.MultiHop]: {
proportion: new BigNumber(1),
intermediateToken: secondHopOrder.takerToken,
hops: [firstHopFill.source, secondHopFill.source],
},
},
};
}
function getSwapQuoteOrdersBreakdown(fillAmountBySource: { [source: string]: BigNumber }): SwapQuoteOrdersBreakdown {
const totalFillAmount = BigNumber.sum(...Object.values(fillAmountBySource));
const breakdown: SwapQuoteOrdersBreakdown = {};
Object.entries(fillAmountBySource).forEach(([s, fillAmount]) => {
const source = s as keyof SwapQuoteOrdersBreakdown;
if (source === ERC20BridgeSource.MultiHop) {
// TODO jacob has a different breakdown
} else {
breakdown[source] = fillAmount.div(totalFillAmount);
const getNextFillAmount = (fillResults: QuoteFillResult[]) => {
if (fillResults.length === 0) {
return fillAmount;
}
const lastFillResult = fillResults[fillResults.length - 1];
const { totalTakerAssetAmount, makerAssetAmount } = lastFillResult;
return side === MarketOperation.Sell
? makerAssetAmount : totalTakerAssetAmount;
};
const bestCaseFillResults = [];
const worstCaseFillResults = [];
const tokenPath = [];
for (const [i, hop] of hops.entries()) {
if (i === 0 || i < hops.length - 1) {
if (side == MarketOperation.Sell) {
tokenPath.push(hop.takerToken);
} else {
tokenPath.unshift(hop.makerToken);
}
}
if (i === tokenPath.length - 1) {
if (side === MarketOperation.Sell) {
tokenPath.push(hop.makerToken);
} else {
tokenPath.unshift(hop.takerToken);
}
}
const bestCaseFillResult = simulateBestCaseFill({
gasPrice,
side,
orders: hop.orders,
fillAmount: getNextFillAmount(bestCaseFillResults),
opts: {},
});
bestCaseFillResults.push(bestCaseFillResult);
const worstCaseFillResult = simulateWorstCaseFill({
gasPrice,
side,
orders: hop.orders,
fillAmount: getNextFillAmount(worstCaseFillResults),
opts: { slippage },
});
worstCaseFillResults.push(worstCaseFillResult);
}
const combinedBestCaseFillResult = combineQuoteFillResults(side, bestCaseFillResults);
const combinedWorstCaseFillResult = combineQuoteFillResults(side, worstCaseFillResults);
const sourceBreakdown = getSwapQuoteOrdersBreakdown(side, tokenPath, bestCaseFillResults);
return {
sourceBreakdown,
bestCaseQuoteInfo: fillResultsToQuoteInfo(combinedBestCaseFillResult),
worstCaseQuoteInfo: fillResultsToQuoteInfo(combinedWorstCaseFillResult),
};
}
function combineQuoteFillResults(side: MarketOperation, fillResults: QuoteFillResult[]): QuoteFillResult {
if (fillResults.length === 0) {
throw new Error(`Empty fillResults array`);
}
const orderedFillResults = side === MarketOperation.Sell ? fillResults : fillResults.slice().reverse();
const lastResult = orderedFillResults[orderedFillResults.length - 1];
const r = {
...orderedFillResults[0],
makerAssetAmount: lastResult.makerAssetAmount,
totalMakerAssetAmount: lastResult.totalMakerAssetAmount,
};
for (const fr of orderedFillResults.slice(1)) {
r.gas += fr.gas + 30e3;
r.protocolFeeAmount = r.protocolFeeAmount.plus(fr.protocolFeeAmount);
}
return r;
}
function getSwapQuoteOrdersBreakdown(side: MarketOperation, tokenPath: Address[], hopFillResults: QuoteFillResult[]): SwapQuoteOrdersBreakdown {
const cumulativeFillRatioBySource: Partial<{ [key in ERC20BridgeSource]: number }> = {};
for (const hop of hopFillResults) {
const hopTotalFillAmount = side === MarketOperation.Sell
? hop.totalTakerAssetAmount
: hop.totalMakerAssetAmount;
for (const [source, sourceFillAmount] of Object.entries(hop.fillAmountBySource)) {
cumulativeFillRatioBySource[source as ERC20BridgeSource] =
(cumulativeFillRatioBySource[source as ERC20BridgeSource] || 0)
+ sourceFillAmount.div(hopTotalFillAmount).toNumber();
}
}
const globalFillRatiosSum = Object.values(cumulativeFillRatioBySource).reduce((a, v) => a! + v!, 0);
if (!globalFillRatiosSum) {
return {};
}
const breakdown: SwapQuoteOrdersBreakdown = {};
for (const [source, fillRatio] of Object.entries(cumulativeFillRatioBySource)) {
(breakdown as any)[source] = fillRatio! / globalFillRatiosSum;
}
const hopBreakdowns = hopFillResults.map(hop => {
const hopTotalFillAmount = side === MarketOperation.Sell
? hop.totalTakerAssetAmount
: hop.totalMakerAssetAmount;
return Object.assign(
{},
...Object.entries(hop.fillAmountBySource).map(([source, sourceFillAmount]) => ({
[source as ERC20BridgeSource]: sourceFillAmount.div(hopTotalFillAmount).toNumber(),
})),
);
});
if (hopFillResults.length > 1) {
return {
[ERC20BridgeSource.MultiHop]: {
proportion: 1,
tokenPath: tokenPath,
breakdowns: side === MarketOperation.Sell ? hopBreakdowns : hopBreakdowns.reverse(),
},
};
}
return breakdown;
}

View File

@@ -1,7 +1,9 @@
import { ChainId } from '@0x/contract-addresses';
import { BlockParam, ContractAddresses, GethCallOverrides } from '@0x/contract-wrappers';
import {
FillQuoteTransformerLimitOrderInfo,
FillQuoteTransformerOrderType,
FillQuoteTransformerRfqOrderInfo,
LimitOrderFields,
RfqOrder,
RfqOrderFields,
@@ -16,12 +18,21 @@ import {
ERC20BridgeSource,
GetMarketOrdersOpts,
LiquidityProviderRegistry,
OptimizedMarketOrder,
LiquidityProviderFillData,
TokenAdjacencyGraph,
BridgeFillData,
CurveFillData,
UniswapV2FillData,
UniswapV3FillData,
NativeOrderFillData,
MooniswapFillData,
} from './utils/market_operation_utils/types';
import { PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
import { MetricsProxy } from './utils/quote_requestor';
export type Address = string;
export type Bytes = string;
/**
* expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
* permittedOrderFeeTypes: A set of all the takerFee types that OrderPruner will filter for
@@ -37,19 +48,9 @@ export interface SignedOrder<T> {
signature: Signature;
}
export type SignedNativeOrder = SignedOrder<LimitOrderFields> | SignedOrder<RfqOrderFields>;
export type NativeOrderWithFillableAmounts = SignedNativeOrder & NativeOrderFillableAmountFields;
/**
* fillableMakerAmount: Amount of makerAsset that is fillable
* fillableTakerAmount: Amount of takerAsset that is fillable
* fillableTakerFeeAmount: Amount of takerFee paid to fill fillableTakerAmount
*/
export interface NativeOrderFillableAmountFields {
fillableMakerAmount: BigNumber;
fillableTakerAmount: BigNumber;
fillableTakerFeeAmount: BigNumber;
}
export type SignedRfqOrder = SignedOrder<RfqOrderFields>;
export type SignedLimitOrder = SignedOrder<LimitOrderFields>;
export type SignedNativeOrder = SignedLimitOrder | SignedRfqOrder;
/**
* Represents the metadata to call a smart contract with calldata.
@@ -166,19 +167,72 @@ export interface SwapQuoteBase {
takerToken: string;
makerToken: string;
gasPrice: BigNumber;
orders: OptimizedMarketOrder[];
hops: SwapQuoteHop[];
bestCaseQuoteInfo: SwapQuoteInfo;
worstCaseQuoteInfo: SwapQuoteInfo;
sourceBreakdown: SwapQuoteOrdersBreakdown;
quoteReport?: QuoteReport;
priceComparisonsReport?: PriceComparisonsReport;
isTwoHop: boolean;
makerTokenDecimals: number;
takerTokenDecimals: number;
takerAmountPerEth: BigNumber;
makerAmountPerEth: BigNumber;
maxSlippage: number;
}
export interface SwapQuoteHop {
takerToken: Address;
makerToken: Address;
makerAmount: BigNumber;
takerAmount: BigNumber;
minMakerAmount: BigNumber;
maxTakerAmount: BigNumber;
sourceFlags: bigint;
orders: SwapQuoteOrder[];
}
export interface SwapQuoteOrder {
type: FillQuoteTransformerOrderType; // should correspond with TFillData
source: ERC20BridgeSource;
makerToken: string;
takerToken: string;
gasCost: number;
makerAmount: BigNumber;
takerAmount: BigNumber;
isFallback: boolean;
fillData?: any;
}
export interface SwapQuoteBridgeOrder<TFillData extends BridgeFillData> extends SwapQuoteOrder {
fillData: TFillData;
minMakerAmount: BigNumber;
maxTakerAmount: BigNumber;
}
export interface SwapQuoteGenericBridgeOrder extends SwapQuoteBridgeOrder<BridgeFillData> {}
export interface SwapQuoteUniswapV2BridgeOrder extends SwapQuoteBridgeOrder<UniswapV2FillData> {}
export interface SwapQuoteUniswapV3BridgeOrder extends SwapQuoteBridgeOrder<UniswapV3FillData> {}
export interface SwapQuoteLiquidityProviderBridgeOrder extends SwapQuoteBridgeOrder<LiquidityProviderFillData> {}
export interface SwapQuoteMooniswapBridgeOrder extends SwapQuoteBridgeOrder<MooniswapFillData> {}
export interface SwapQuoteCurveBridgeOrder extends SwapQuoteBridgeOrder<CurveFillData> {}
export interface SwapQuoteLimitOrder extends SwapQuoteOrder {
type: FillQuoteTransformerOrderType.Limit;
fillData: NativeOrderFillData;
}
export interface SwapQuoteRfqOrder extends SwapQuoteOrder {
type: FillQuoteTransformerOrderType.Rfq;
fillData: NativeOrderFillData;
}
export type SwapQuoteNativeOrder = SwapQuoteLimitOrder | SwapQuoteRfqOrder;
/**
* takerAssetFillAmount: The amount of takerAsset sold for makerAsset.
* type: Specified MarketOperation the SwapQuote is provided for
@@ -220,15 +274,17 @@ export interface SwapQuoteInfo {
* percentage breakdown of each liquidity source used in quote
*/
export type SwapQuoteOrdersBreakdown = Partial<
{ [key in Exclude<ERC20BridgeSource, typeof ERC20BridgeSource.MultiHop>]: BigNumber } & {
[ERC20BridgeSource.MultiHop]: {
proportion: BigNumber;
intermediateToken: string;
hops: ERC20BridgeSource[];
};
{ [key in Exclude<ERC20BridgeSource, typeof ERC20BridgeSource.MultiHop>]: number } & {
[ERC20BridgeSource.MultiHop]: SwapQuoteMultiHopBreakdown;
}
>;
export interface SwapQuoteMultiHopBreakdown {
proportion: number;
tokenPath: Address[];
breakdowns: Partial<{ [key in ERC20BridgeSource]: number }>[];
};
/**
* nativeExclusivelyRFQ: if set to `true`, Swap quote will exclude Open Orderbook liquidity.
* If set to `true` and `ERC20BridgeSource.Native` is part of the `excludedSources`
@@ -326,15 +382,16 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
chainId: ChainId;
orderRefreshIntervalMs: number;
expiryBufferMs: number;
ethereumRpcUrl?: string;
// ethereumRpcUrl?: string;
contractAddresses?: AssetSwapperContractAddresses;
samplerGasLimit?: number;
multiBridgeAddress?: string;
// multiBridgeAddress?: string;
ethGasStationUrl?: string;
rfqt?: SwapQuoterRfqOpts;
samplerOverrides?: SamplerOverrides;
tokenAdjacencyGraph?: TokenAdjacencyGraph;
liquidityProviderRegistry?: LiquidityProviderRegistry;
// samplerOverrides?: SamplerOverrides;
// tokenAdjacencyGraph?: TokenAdjacencyGraph;
// liquidityProviderRegistry?: LiquidityProviderRegistry;
samplerServiceUrl: string;
}
/**
@@ -410,8 +467,6 @@ export interface SamplerCallResult {
data: string;
}
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export enum AltQuoteModel {
Firm = 'firm',
Indicative = 'indicative',

View File

@@ -1,34 +0,0 @@
import { SupportedProvider } from '@0x/dev-utils';
import { SDK } from '@bancor/sdk';
import { Ethereum } from '@bancor/sdk/dist/blockchains/ethereum';
import { BlockchainType } from '@bancor/sdk/dist/types';
import { MAINNET_TOKENS } from './constants';
const findToken = (tokenAddress: string, graph: object): string =>
// If we're looking for WETH it is stored by Bancor as the 0xeee address
tokenAddress.toLowerCase() === MAINNET_TOKENS.WETH.toLowerCase()
? '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
: Object.keys(graph).filter(k => k.toLowerCase() === tokenAddress.toLowerCase())[0];
export class BancorService {
public static async createAsync(provider: SupportedProvider): Promise<BancorService> {
const sdk = await SDK.create({ ethereumNodeEndpoint: provider });
const service = new BancorService(sdk);
return service;
}
constructor(public sdk: SDK) {}
public getPaths(_fromToken: string, _toToken: string): string[][] {
// HACK: We reach into the blockchain object and pull in it's cache of tokens
// and we use it's internal non-async getPathsFunc
try {
const blockchain = this.sdk._core.blockchains[BlockchainType.Ethereum] as Ethereum;
const fromToken = findToken(_fromToken, blockchain.graph);
const toToken = findToken(_toToken, blockchain.graph);
return blockchain.getPathsFunc.bind(blockchain)(fromToken, toToken);
} catch (e) {
return [];
}
}
}

View File

@@ -1,528 +0,0 @@
import { ChainId } from '@0x/contract-addresses';
import { BigNumber, NULL_BYTES } from '@0x/utils';
import {
ACRYPTOS_BSC_INFOS,
APESWAP_ROUTER_BY_CHAIN_ID,
BAKERYSWAP_ROUTER_BY_CHAIN_ID,
BELT_BSC_INFOS,
CAFESWAP_ROUTER_BY_CHAIN_ID,
CHEESESWAP_ROUTER_BY_CHAIN_ID,
COMETHSWAP_ROUTER_BY_CHAIN_ID,
COMPONENT_POOLS_BY_CHAIN_ID,
CRYPTO_COM_ROUTER_BY_CHAIN_ID,
CURVE_FANTOM_INFOS,
CURVE_MAINNET_INFOS,
CURVE_POLYGON_INFOS,
CURVE_V2_FANTOM_INFOS,
CURVE_V2_MAINNET_INFOS,
CURVE_V2_POLYGON_INFOS,
DFYN_ROUTER_BY_CHAIN_ID,
ELLIPSIS_BSC_INFOS,
FIREBIRDONESWAP_BSC_INFOS,
FIREBIRDONESWAP_POLYGON_INFOS,
IRONSWAP_POLYGON_INFOS,
JETSWAP_ROUTER_BY_CHAIN_ID,
JULSWAP_ROUTER_BY_CHAIN_ID,
KYBER_BANNED_RESERVES,
KYBER_BRIDGED_LIQUIDITY_PREFIX,
MAX_DODOV2_POOLS_QUERIED,
MAX_KYBER_RESERVES_QUERIED,
MSTABLE_POOLS_BY_CHAIN_ID,
NERVE_BSC_INFOS,
NULL_ADDRESS,
PANCAKESWAP_ROUTER_BY_CHAIN_ID,
PANCAKESWAPV2_ROUTER_BY_CHAIN_ID,
PANGOLIN_ROUTER_BY_CHAIN_ID,
POLYDEX_ROUTER_BY_CHAIN_ID,
QUICKSWAP_ROUTER_BY_CHAIN_ID,
SADDLE_MAINNET_INFOS,
SHELL_POOLS_BY_CHAIN_ID,
SHIBASWAP_ROUTER_BY_CHAIN_ID,
SMOOTHY_BSC_INFOS,
SMOOTHY_MAINNET_INFOS,
SNOWSWAP_MAINNET_INFOS,
SPIRITSWAP_ROUTER_BY_CHAIN_ID,
SPOOKYSWAP_ROUTER_BY_CHAIN_ID,
SUSHISWAP_ROUTER_BY_CHAIN_ID,
SWERVE_MAINNET_INFOS,
TRADER_JOE_ROUTER_BY_CHAIN_ID,
UNISWAPV2_ROUTER_BY_CHAIN_ID,
WAULTSWAP_ROUTER_BY_CHAIN_ID,
XSIGMA_MAINNET_INFOS,
} from './constants';
import { CurveInfo, ERC20BridgeSource } from './types';
/**
* Filter Kyber reserves which should not be used (0xbb bridged reserves)
* @param reserveId Kyber reserveId
*/
export function isAllowedKyberReserveId(reserveId: string): boolean {
return (
reserveId !== NULL_BYTES &&
!reserveId.startsWith(KYBER_BRIDGED_LIQUIDITY_PREFIX) &&
!KYBER_BANNED_RESERVES.includes(reserveId)
);
}
// tslint:disable-next-line: completed-docs ban-types
export function isValidAddress(address: string | String): address is string {
return (typeof address === 'string' || address instanceof String) && address.toString() !== NULL_ADDRESS;
}
/**
* Returns the offsets to be used to discover Kyber reserves
*/
export function getKyberOffsets(): BigNumber[] {
return Array(MAX_KYBER_RESERVES_QUERIED)
.fill(0)
.map((_v, i) => new BigNumber(i));
}
// tslint:disable completed-docs
export function getDodoV2Offsets(): BigNumber[] {
return Array(MAX_DODOV2_POOLS_QUERIED)
.fill(0)
.map((_v, i) => new BigNumber(i));
}
// tslint:disable completed-docs
export function getShellsForPair(chainId: ChainId, takerToken: string, makerToken: string): string[] {
if (chainId !== ChainId.Mainnet) {
return [];
}
return Object.values(SHELL_POOLS_BY_CHAIN_ID[chainId])
.filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)))
.map(i => i.poolAddress);
}
// tslint:disable completed-docs
export function getComponentForPair(chainId: ChainId, takerToken: string, makerToken: string): string[] {
if (chainId !== ChainId.Mainnet) {
return [];
}
return Object.values(COMPONENT_POOLS_BY_CHAIN_ID[chainId])
.filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)))
.map(i => i.poolAddress);
}
// tslint:disable completed-docs
export function getMStableForPair(chainId: ChainId, takerToken: string, makerToken: string): string[] {
if (chainId !== ChainId.Mainnet && chainId !== ChainId.Polygon) {
return [];
}
return Object.values(MSTABLE_POOLS_BY_CHAIN_ID[chainId])
.filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)))
.map(i => i.poolAddress);
}
// tslint:disable completed-docs
export function getCurveInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
switch (chainId) {
case ChainId.Mainnet:
return Object.values(CURVE_MAINNET_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
case ChainId.Polygon:
return Object.values(CURVE_POLYGON_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
case ChainId.Fantom:
return Object.values(CURVE_FANTOM_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
default:
return [];
}
}
// tslint:disable completed-docs
export function getCurveV2InfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
switch (chainId) {
case ChainId.Mainnet:
return Object.values(CURVE_V2_MAINNET_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
case ChainId.Polygon:
return Object.values(CURVE_V2_POLYGON_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
case ChainId.Fantom:
return Object.values(CURVE_V2_FANTOM_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
default:
return [];
}
}
export function getSwerveInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId !== ChainId.Mainnet) {
return [];
}
return Object.values(SWERVE_MAINNET_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
}
export function getSnowSwapInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId !== ChainId.Mainnet) {
return [];
}
return Object.values(SNOWSWAP_MAINNET_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
}
export function getNerveInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId !== ChainId.BSC) {
return [];
}
return Object.values(NERVE_BSC_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
}
export function getFirebirdOneSwapInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId === ChainId.BSC) {
return Object.values(FIREBIRDONESWAP_BSC_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
} else if (chainId === ChainId.Polygon) {
return Object.values(FIREBIRDONESWAP_POLYGON_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
} else {
return [];
}
}
export function getBeltInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId !== ChainId.BSC) {
return [];
}
return Object.values(BELT_BSC_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
}
export function getEllipsisInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId !== ChainId.BSC) {
return [];
}
return Object.values(ELLIPSIS_BSC_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
}
export function getSmoothyInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId === ChainId.BSC) {
return Object.values(SMOOTHY_BSC_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
} else if (chainId === ChainId.Mainnet) {
return Object.values(SMOOTHY_MAINNET_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) &&
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
} else {
return [];
}
}
export function getSaddleInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId !== ChainId.Mainnet) {
return [];
}
return Object.values(SADDLE_MAINNET_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
}
export function getIronSwapInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId !== ChainId.Polygon) {
return [];
}
return Object.values(IRONSWAP_POLYGON_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
}
export function getXSigmaInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId !== ChainId.Mainnet) {
return [];
}
return Object.values(XSIGMA_MAINNET_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
}
export function getAcryptosInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
if (chainId !== ChainId.BSC) {
return [];
}
return Object.values(ACRYPTOS_BSC_INFOS).filter(c =>
[makerToken, takerToken].every(
t =>
(c.tokens.includes(t) && c.metaTokens === undefined) ||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
),
);
}
export function getShellLikeInfosForPair(
chainId: ChainId,
takerToken: string,
makerToken: string,
source: ERC20BridgeSource.Shell | ERC20BridgeSource.Component | ERC20BridgeSource.MStable,
): string[] {
switch (source) {
case ERC20BridgeSource.Shell:
return getShellsForPair(chainId, takerToken, makerToken);
case ERC20BridgeSource.Component:
return getComponentForPair(chainId, takerToken, makerToken);
case ERC20BridgeSource.MStable:
return getMStableForPair(chainId, takerToken, makerToken);
default:
throw new Error(`Unknown Shell like source ${source}`);
}
}
export interface CurveDetailedInfo extends CurveInfo {
makerTokenIdx: number;
takerTokenIdx: number;
}
export function getCurveLikeInfosForPair(
chainId: ChainId,
takerToken: string,
makerToken: string,
source:
| ERC20BridgeSource.Curve
| ERC20BridgeSource.CurveV2
| ERC20BridgeSource.Swerve
| ERC20BridgeSource.SnowSwap
| ERC20BridgeSource.Nerve
| ERC20BridgeSource.Belt
| ERC20BridgeSource.Ellipsis
| ERC20BridgeSource.Smoothy
| ERC20BridgeSource.Saddle
| ERC20BridgeSource.IronSwap
| ERC20BridgeSource.XSigma
| ERC20BridgeSource.FirebirdOneSwap
| ERC20BridgeSource.ACryptos,
): CurveDetailedInfo[] {
let pools: CurveInfo[] = [];
switch (source) {
case ERC20BridgeSource.Curve:
pools = getCurveInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.CurveV2:
pools = getCurveV2InfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.Swerve:
pools = getSwerveInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.SnowSwap:
pools = getSnowSwapInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.Nerve:
pools = getNerveInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.Belt:
pools = getBeltInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.Ellipsis:
pools = getEllipsisInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.Smoothy:
pools = getSmoothyInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.Saddle:
pools = getSaddleInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.XSigma:
pools = getXSigmaInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.FirebirdOneSwap:
pools = getFirebirdOneSwapInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.IronSwap:
pools = getIronSwapInfosForPair(chainId, takerToken, makerToken);
break;
case ERC20BridgeSource.ACryptos:
pools = getAcryptosInfosForPair(chainId, takerToken, makerToken);
break;
default:
throw new Error(`Unknown Curve like source ${source}`);
}
return pools.map(pool => ({
...pool,
makerTokenIdx: pool.tokens.indexOf(makerToken),
takerTokenIdx: pool.tokens.indexOf(takerToken),
}));
}
export function uniswapV2LikeRouterAddress(
chainId: ChainId,
source:
| ERC20BridgeSource.UniswapV2
| ERC20BridgeSource.SushiSwap
| ERC20BridgeSource.CryptoCom
| ERC20BridgeSource.PancakeSwap
| ERC20BridgeSource.PancakeSwapV2
| ERC20BridgeSource.BakerySwap
| ERC20BridgeSource.ApeSwap
| ERC20BridgeSource.CafeSwap
| ERC20BridgeSource.CheeseSwap
| ERC20BridgeSource.JulSwap
| ERC20BridgeSource.QuickSwap
| ERC20BridgeSource.ComethSwap
| ERC20BridgeSource.Dfyn
| ERC20BridgeSource.WaultSwap
| ERC20BridgeSource.Polydex
| ERC20BridgeSource.ShibaSwap
| ERC20BridgeSource.JetSwap
| ERC20BridgeSource.TraderJoe
| ERC20BridgeSource.Pangolin
| ERC20BridgeSource.SpookySwap
| ERC20BridgeSource.SpiritSwap,
): string {
switch (source) {
case ERC20BridgeSource.UniswapV2:
return UNISWAPV2_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.SushiSwap:
return SUSHISWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.CryptoCom:
return CRYPTO_COM_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.PancakeSwap:
return PANCAKESWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.PancakeSwapV2:
return PANCAKESWAPV2_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.BakerySwap:
return BAKERYSWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.ApeSwap:
return APESWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.CafeSwap:
return CAFESWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.CheeseSwap:
return CHEESESWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.JulSwap:
return JULSWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.QuickSwap:
return QUICKSWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.ComethSwap:
return COMETHSWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.Dfyn:
return DFYN_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.WaultSwap:
return WAULTSWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.Polydex:
return POLYDEX_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.ShibaSwap:
return SHIBASWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.JetSwap:
return JETSWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.Pangolin:
return PANGOLIN_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.TraderJoe:
return TRADER_JOE_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.SpookySwap:
return SPOOKYSWAP_ROUTER_BY_CHAIN_ID[chainId];
case ERC20BridgeSource.SpiritSwap:
return SPIRITSWAP_ROUTER_BY_CHAIN_ID[chainId];
default:
throw new Error(`Unknown UniswapV2 like source ${source}`);
}
}
const BAD_TOKENS_BY_SOURCE: Partial<{ [key in ERC20BridgeSource]: string[] }> = {
[ERC20BridgeSource.Uniswap]: [
'0xb8c77482e45f1f44de1745f52c74426c631bdd52', // BNB
],
};
export function isBadTokenForSource(token: string, source: ERC20BridgeSource): boolean {
return (BAD_TOKENS_BY_SOURCE[source] || []).includes(token.toLowerCase());
}

View File

@@ -1,5 +1,4 @@
import { Web3Wrapper } from '@0x/dev-utils';
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
import { BigNumber, logUtils } from '@0x/utils';
import * as _ from 'lodash';
@@ -8,10 +7,6 @@ import { MarketOperation } from '../../types';
import { COMPARISON_PRICE_DECIMALS, SOURCE_FLAGS } from './constants';
import {
ComparisonPrice,
ERC20BridgeSource,
ExchangeProxyOverhead,
FeeEstimate,
FeeSchedule,
MarketSideLiquidity,
} from './types';
@@ -29,41 +24,20 @@ export function getComparisonPrices(
adjustedRate: BigNumber,
amount: BigNumber,
marketSideLiquidity: MarketSideLiquidity,
feeSchedule: FeeSchedule,
exchangeProxyOverhead: ExchangeProxyOverhead,
gasPrice: BigNumber,
): 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 };
}
}
let feeInEth = gasPrice.times(100e3);
const [inputAmountPerEth, outputAmountPerEth] = [
marketSideLiquidity.tokenAmountPerEth[marketSideLiquidity.inputToken],
marketSideLiquidity.tokenAmountPerEth[marketSideLiquidity.outputToken],
];
// Calc native order fee penalty in output unit (maker units for sells, taker unit for buys)
const feePenalty = !marketSideLiquidity.outputAmountPerEth.isZero()
? marketSideLiquidity.outputAmountPerEth.times(feeInEth)
const feePenalty = !outputAmountPerEth.isZero()
? outputAmountPerEth.times(feeInEth)
: // if it's a sell, the input token is the taker token
marketSideLiquidity.inputAmountPerEth
inputAmountPerEth
.times(feeInEth)
.times(marketSideLiquidity.side === MarketOperation.Sell ? adjustedRate : adjustedRate.pow(-1));

View File

@@ -1,10 +1,11 @@
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
import { BigNumber, hexUtils } from '@0x/utils';
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
import { NativeOrderWithFillableAmounts } from '../native_orders';
import { MarketOperation } from '../../types';
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
import { DexSample, ERC20BridgeSource, Fill, GenericBridgeFill, NativeOrderFill } from './types';
// tslint:disable: prefer-for-of no-bitwise completed-docs
@@ -18,12 +19,9 @@ export function createFills(opts: {
targetInput?: BigNumber;
outputAmountPerEth?: BigNumber;
inputAmountPerEth?: BigNumber;
excludedSources?: ERC20BridgeSource[];
feeSchedule?: FeeSchedule;
gasPrice: BigNumber;
}): 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,15 +33,15 @@ 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))
.filter(fills => hasLiquidity(fills) && !excludedSources.includes(fills[0].source));
.filter(fills => hasLiquidity(fills));
}
function clipFillsToInput(fills: Fill[], targetInput: BigNumber = POSITIVE_INF): Fill[] {
@@ -95,24 +93,34 @@ export function nativeOrdersToFills(
targetInput: BigNumber = POSITIVE_INF,
outputAmountPerEth: BigNumber,
inputAmountPerEth: BigNumber,
fees: FeeSchedule,
): Fill[] {
gasPrice: BigNumber,
): NativeOrderFill[] {
if (orders.length === 0) {
return [];
}
const sourcePathId = hexUtils.random();
// Create a single path from all orders.
let fills: Array<Fill & { adjustedRate: BigNumber }> = [];
let fills: Array<NativeOrderFill & { adjustedRate: BigNumber }> = [];
for (const o of orders) {
const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = o;
const makerAmount = fillableMakerAmount;
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 { fillableTakerAmount, fillableMakerAmount, type } = o;
// TODO(lawrence): handle taker fees.
if (o.fillableTakerFeeAmount.gt(0)) {
continue;
}
let input, output;
if (side === MarketOperation.Sell) {
input = fillableTakerAmount;
output = fillableMakerAmount;
} else {
input = fillableMakerAmount;
output = fillableTakerAmount;
}
const outputPenalty = ethToOutputAmount({
input,
output,
inputAmountPerEth,
outputAmountPerEth,
ethAmount: fee,
ethAmount: gasPrice.times(o.gasCost),
});
// targetInput can be less than the order size
// whilst the penalty is constant, it affects the adjusted output
@@ -131,17 +139,21 @@ export function nativeOrdersToFills(
continue;
}
fills.push({
type,
sourcePathId,
adjustedRate,
adjustedOutput,
adjustedRate,
input: clippedInput,
output: clippedOutput,
flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
index: 0, // TBD
parent: undefined, // TBD
source: ERC20BridgeSource.Native,
type,
fillData: { ...o },
gasCost: o.gasCost,
data: {
order: o.order,
signature: o.signature,
},
});
}
// Sort by descending adjusted rate.
@@ -159,10 +171,10 @@ export function dexSamplesToFills(
samples: DexSample[],
outputAmountPerEth: BigNumber,
inputAmountPerEth: BigNumber,
fees: FeeSchedule,
): Fill[] {
gasPrice: BigNumber,
): GenericBridgeFill[] {
const sourcePathId = hexUtils.random();
const fills: Fill[] = [];
const fills: GenericBridgeFill[] = [];
// Drop any non-zero entries. This can occur if the any fills on Kyber were UniswapReserves
// We need not worry about Kyber fills going to UniswapReserve as the input amount
// we fill is the same as we sampled. I.e we received [0,20,30] output from [1,2,3] input
@@ -171,10 +183,10 @@ export function dexSamplesToFills(
for (let i = 0; i < nonzeroSamples.length; i++) {
const sample = nonzeroSamples[i];
const prevSample = i === 0 ? undefined : nonzeroSamples[i - 1];
const { source, fillData } = sample;
const { source, encodedFillData, metadata } = 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;
const fee = gasPrice.times(sample.gasCost);
let penalty = ZERO_AMOUNT;
if (i === 0) {
// Only the first fill in a DEX path incurs a penalty.
@@ -194,11 +206,15 @@ export function dexSamplesToFills(
output,
adjustedOutput,
source,
fillData,
type: FillQuoteTransformerOrderType.Bridge,
gasCost: sample.gasCost,
index: i,
parent: i !== 0 ? fills[fills.length - 1] : undefined,
flags: SOURCE_FLAGS[source],
data: {
...metadata,
encodedFillData,
},
});
}
return fills;

View File

@@ -1,16 +1,6 @@
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { Omit } from '../../types';
import { ZERO_AMOUNT } from './constants';
import { getTwoHopAdjustedRate } from './rate_utils';
import {
DexSample,
ExchangeProxyOverhead,
FeeSchedule,
MarketSideLiquidity,
MultiHopFillData,
TokenAdjacencyGraph,
} from './types';
@@ -30,49 +20,3 @@ export function getIntermediateTokens(
token => token.toLowerCase() !== makerToken.toLowerCase() && token.toLowerCase() !== takerToken.toLowerCase(),
);
}
/**
* Returns the best two-hop quote and the fee-adjusted rate of that quote.
*/
export function getBestTwoHopQuote(
marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>,
feeSchedule?: FeeSchedule,
exchangeProxyOverhead?: ExchangeProxyOverhead,
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity;
const { twoHopQuotes } = quotes;
// Ensure the expected data we require exists. In the case where all hops reverted
// or there were no sources included that allowed for multi hop,
// we can end up with empty, but not undefined, fill data
const filteredQuotes = twoHopQuotes.filter(
quote =>
quote &&
quote.fillData &&
quote.fillData.firstHopSource &&
quote.fillData.secondHopSource &&
quote.output.isGreaterThan(ZERO_AMOUNT),
);
if (filteredQuotes.length === 0) {
return { quote: undefined, adjustedRate: ZERO_AMOUNT };
}
const best = filteredQuotes
.map(quote =>
getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, feeSchedule, exchangeProxyOverhead),
)
.reduce(
(prev, curr, i) =>
curr.isGreaterThan(prev.adjustedRate) ? { adjustedRate: curr, quote: filteredQuotes[i] } : prev,
{
adjustedRate: getTwoHopAdjustedRate(
side,
filteredQuotes[0],
inputAmount,
outputAmountPerEth,
feeSchedule,
exchangeProxyOverhead,
),
quote: filteredQuotes[0],
},
);
return best;
}

View File

@@ -1,82 +1,19 @@
import { BridgeProtocol, encodeBridgeSourceId, FillQuoteTransformerOrderType } from '@0x/protocol-utils';
import { AbiEncoder, BigNumber } from '@0x/utils';
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
import { Address, MarketOperation } from '../../types';
import { MAX_UINT256, ZERO_AMOUNT } from './constants';
import {
AggregationError,
BalancerFillData,
BalancerV2FillData,
BancorFillData,
CollapsedFill,
CurveFillData,
DexSample,
DODOFillData,
CollapsedGenericBridgeFill,
ERC20BridgeSource,
FillData,
FinalUniswapV3FillData,
GenericRouterFillData,
KyberDmmFillData,
KyberFillData,
LidoFillData,
LiquidityProviderFillData,
MakerPsmFillData,
MooniswapFillData,
MultiHopFillData,
NativeCollapsedFill,
NativeLimitOrderFillData,
NativeRfqOrderFillData,
OptimizedMarketBridgeOrder,
OptimizedMarketOrder,
OptimizedMarketOrderBase,
OrderDomain,
ShellFillData,
UniswapV2FillData,
UniswapV3FillData,
CollapsedNativeOrderFill,
OptimizedGenericBridgeOrder,
OptimizedLimitOrder,
OptimizedRfqOrder,
} from './types';
// tslint:disable completed-docs
export interface CreateOrderFromPathOpts {
side: MarketOperation;
inputToken: string;
outputToken: string;
orderDomain: OrderDomain;
contractAddresses: AssetSwapperContractAddresses;
bridgeSlippage: number;
}
export function createOrdersFromTwoHopSample(
sample: DexSample<MultiHopFillData>,
opts: CreateOrderFromPathOpts,
): OptimizedMarketOrder[] {
const [makerToken, takerToken] = getMakerTakerTokens(opts);
const { firstHopSource, secondHopSource, intermediateToken } = sample.fillData;
const firstHopFill: CollapsedFill = {
sourcePathId: '',
source: firstHopSource.source,
type: FillQuoteTransformerOrderType.Bridge,
input: opts.side === MarketOperation.Sell ? sample.input : ZERO_AMOUNT,
output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
subFills: [],
fillData: firstHopSource.fillData,
};
const secondHopFill: CollapsedFill = {
sourcePathId: '',
source: secondHopSource.source,
type: FillQuoteTransformerOrderType.Bridge,
input: opts.side === MarketOperation.Sell ? MAX_UINT256 : sample.input,
output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
subFills: [],
fillData: secondHopSource.fillData,
};
return [
createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts.side),
createBridgeOrder(secondHopFill, makerToken, intermediateToken, opts.side),
];
}
export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): string {
switch (source) {
case ERC20BridgeSource.Balancer:
@@ -193,332 +130,51 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
}
}
export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder): string {
let bridgeData: string;
if (
order.source === ERC20BridgeSource.MultiHop ||
order.source === ERC20BridgeSource.MultiBridge ||
order.source === ERC20BridgeSource.Native
) {
throw new Error('Invalid order to encode for Bridge Data');
}
const encoder = BRIDGE_ENCODERS[order.source];
if (!encoder) {
throw new Error(AggregationError.NoBridgeForSource);
}
switch (order.source) {
case ERC20BridgeSource.Curve:
case ERC20BridgeSource.CurveV2:
case ERC20BridgeSource.Swerve:
case ERC20BridgeSource.SnowSwap:
case ERC20BridgeSource.Nerve:
case ERC20BridgeSource.Belt:
case ERC20BridgeSource.Ellipsis:
case ERC20BridgeSource.Smoothy:
case ERC20BridgeSource.Saddle:
case ERC20BridgeSource.XSigma:
case ERC20BridgeSource.FirebirdOneSwap:
case ERC20BridgeSource.IronSwap:
case ERC20BridgeSource.ACryptos:
const curveFillData = (order as OptimizedMarketBridgeOrder<CurveFillData>).fillData;
bridgeData = encoder.encode([
curveFillData.pool.poolAddress,
curveFillData.pool.exchangeFunctionSelector,
curveFillData.fromTokenIdx,
curveFillData.toTokenIdx,
]);
break;
case ERC20BridgeSource.Balancer:
case ERC20BridgeSource.Cream:
const balancerFillData = (order as OptimizedMarketBridgeOrder<BalancerFillData>).fillData;
bridgeData = encoder.encode([balancerFillData.poolAddress]);
break;
case ERC20BridgeSource.BalancerV2:
const balancerV2FillData = (order as OptimizedMarketBridgeOrder<BalancerV2FillData>).fillData;
const { vault, poolId } = balancerV2FillData;
bridgeData = encoder.encode([vault, poolId]);
break;
case ERC20BridgeSource.Bancor:
const bancorFillData = (order as OptimizedMarketBridgeOrder<BancorFillData>).fillData;
bridgeData = encoder.encode([bancorFillData.networkAddress, bancorFillData.path]);
break;
case ERC20BridgeSource.UniswapV2:
case ERC20BridgeSource.SushiSwap:
case ERC20BridgeSource.CryptoCom:
case ERC20BridgeSource.Linkswap:
case ERC20BridgeSource.PancakeSwap:
case ERC20BridgeSource.PancakeSwapV2:
case ERC20BridgeSource.BakerySwap:
case ERC20BridgeSource.ApeSwap:
case ERC20BridgeSource.CafeSwap:
case ERC20BridgeSource.CheeseSwap:
case ERC20BridgeSource.JulSwap:
case ERC20BridgeSource.QuickSwap:
case ERC20BridgeSource.ComethSwap:
case ERC20BridgeSource.Dfyn:
case ERC20BridgeSource.WaultSwap:
case ERC20BridgeSource.Polydex:
case ERC20BridgeSource.ShibaSwap:
case ERC20BridgeSource.JetSwap:
case ERC20BridgeSource.Pangolin:
case ERC20BridgeSource.TraderJoe:
case ERC20BridgeSource.SpiritSwap:
case ERC20BridgeSource.SpookySwap:
const uniswapV2FillData = (order as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
bridgeData = encoder.encode([uniswapV2FillData.router, uniswapV2FillData.tokenAddressPath]);
break;
case ERC20BridgeSource.Kyber:
const kyberFillData = (order as OptimizedMarketBridgeOrder<KyberFillData>).fillData;
bridgeData = encoder.encode([kyberFillData.networkProxy, kyberFillData.hint]);
break;
case ERC20BridgeSource.Mooniswap:
const mooniswapFillData = (order as OptimizedMarketBridgeOrder<MooniswapFillData>).fillData;
bridgeData = encoder.encode([mooniswapFillData.poolAddress]);
break;
case ERC20BridgeSource.Dodo:
const dodoFillData = (order as OptimizedMarketBridgeOrder<DODOFillData>).fillData;
bridgeData = encoder.encode([
dodoFillData.helperAddress,
dodoFillData.poolAddress,
dodoFillData.isSellBase,
]);
break;
case ERC20BridgeSource.DodoV2:
const dodoV2FillData = (order as OptimizedMarketBridgeOrder<DODOFillData>).fillData;
bridgeData = encoder.encode([dodoV2FillData.poolAddress, dodoV2FillData.isSellBase]);
break;
case ERC20BridgeSource.Shell:
case ERC20BridgeSource.Component:
const shellFillData = (order as OptimizedMarketBridgeOrder<ShellFillData>).fillData;
bridgeData = encoder.encode([shellFillData.poolAddress]);
break;
case ERC20BridgeSource.LiquidityProvider:
const lpFillData = (order as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
bridgeData = encoder.encode([lpFillData.poolAddress, tokenAddressEncoder.encode([order.takerToken])]);
break;
case ERC20BridgeSource.Uniswap:
const uniFillData = (order as OptimizedMarketBridgeOrder<GenericRouterFillData>).fillData;
bridgeData = encoder.encode([uniFillData.router]);
break;
case ERC20BridgeSource.Eth2Dai:
const oasisFillData = (order as OptimizedMarketBridgeOrder<GenericRouterFillData>).fillData;
bridgeData = encoder.encode([oasisFillData.router]);
break;
case ERC20BridgeSource.MStable:
const mStableFillData = (order as OptimizedMarketBridgeOrder<GenericRouterFillData>).fillData;
bridgeData = encoder.encode([mStableFillData.router]);
break;
case ERC20BridgeSource.MakerPsm:
const psmFillData = (order as OptimizedMarketBridgeOrder<MakerPsmFillData>).fillData;
bridgeData = encoder.encode([psmFillData.psmAddress, psmFillData.gemTokenAddress]);
break;
case ERC20BridgeSource.UniswapV3:
const uniswapV3FillData = (order as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
bridgeData = encoder.encode([uniswapV3FillData.router, uniswapV3FillData.uniswapPath]);
break;
case ERC20BridgeSource.KyberDmm:
const kyberDmmFillData = (order as OptimizedMarketBridgeOrder<KyberDmmFillData>).fillData;
bridgeData = encoder.encode([
kyberDmmFillData.router,
kyberDmmFillData.poolsPath,
kyberDmmFillData.tokenAddressPath,
]);
break;
case ERC20BridgeSource.Lido:
const lidoFillData = (order as OptimizedMarketBridgeOrder<LidoFillData>).fillData;
bridgeData = encoder.encode([lidoFillData.stEthTokenAddress]);
break;
default:
throw new Error(AggregationError.NoBridgeForSource);
}
return bridgeData;
}
export function createBridgeOrder(
fill: CollapsedFill,
makerToken: string,
takerToken: string,
side: MarketOperation,
): OptimizedMarketBridgeOrder {
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
fill: CollapsedGenericBridgeFill,
inputToken: Address,
outputToken: Address,
): OptimizedGenericBridgeOrder {
return {
makerToken,
takerToken,
makerAmount,
takerAmount,
fillData: createFinalBridgeOrderFillDataFromCollapsedFill(fill),
inputToken,
outputToken,
inputAmount: fill.input,
outputAmount: fill.output,
fillData: fill.data,
source: fill.source,
sourcePathId: fill.sourcePathId,
type: FillQuoteTransformerOrderType.Bridge,
fills: [fill],
gasCost: fill.gasCost,
isFallback: fill.isFallback,
...((fill as any).metadata !== undefined ? { metadata: (fill as any).metadata } : {}),
};
}
function createFinalBridgeOrderFillDataFromCollapsedFill(fill: CollapsedFill): FillData {
switch (fill.source) {
case ERC20BridgeSource.UniswapV3: {
const fd = fill.fillData as UniswapV3FillData;
return {
router: fd.router,
tokenAddressPath: fd.tokenAddressPath,
uniswapPath: getBestUniswapV3PathForInputAmount(fd, fill.input),
};
}
default:
break;
}
return fill.fillData;
}
function getBestUniswapV3PathForInputAmount(fillData: UniswapV3FillData, inputAmount: BigNumber): string {
if (fillData.pathAmounts.length === 0) {
throw new Error(`No Uniswap V3 paths`);
}
// Find the best path that can satisfy `inputAmount`.
// Assumes `fillData.pathAmounts` is sorted ascending.
for (const { inputAmount: pathInputAmount, uniswapPath } of fillData.pathAmounts) {
if (pathInputAmount.gte(inputAmount)) {
return uniswapPath;
}
}
return fillData.pathAmounts[fillData.pathAmounts.length - 1].uniswapPath;
}
export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] {
const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;
export function getMakerTakerTokens(side: MarketOperation, inputToken: Address, outputToken: Address): [Address, Address] {
const makerToken = side === MarketOperation.Sell ? outputToken : inputToken;
const takerToken = side === MarketOperation.Sell ? inputToken : outputToken;
return [makerToken, takerToken];
}
export const poolEncoder = AbiEncoder.create([{ name: 'poolAddress', type: 'address' }]);
const curveEncoder = AbiEncoder.create([
{ name: 'curveAddress', type: 'address' },
{ name: 'exchangeFunctionSelector', type: 'bytes4' },
{ name: 'fromTokenIdx', type: 'int128' },
{ name: 'toTokenIdx', type: 'int128' },
]);
const makerPsmEncoder = AbiEncoder.create([
{ name: 'psmAddress', type: 'address' },
{ name: 'gemTokenAddress', type: 'address' },
]);
const balancerV2Encoder = AbiEncoder.create([
{ name: 'vault', type: 'address' },
{ name: 'poolId', type: 'bytes32' },
]);
const routerAddressPathEncoder = AbiEncoder.create('(address,address[])');
const tokenAddressEncoder = AbiEncoder.create([{ name: 'tokenAddress', type: 'address' }]);
export const BRIDGE_ENCODERS: {
[key in Exclude<
ERC20BridgeSource,
ERC20BridgeSource.Native | ERC20BridgeSource.MultiHop | ERC20BridgeSource.MultiBridge
>]: AbiEncoder.DataType;
} = {
[ERC20BridgeSource.LiquidityProvider]: AbiEncoder.create([
{ name: 'provider', type: 'address' },
{ name: 'data', type: 'bytes' },
]),
[ERC20BridgeSource.Kyber]: AbiEncoder.create([
{ name: 'kyberNetworkProxy', type: 'address' },
{ name: 'hint', type: 'bytes' },
]),
[ERC20BridgeSource.Dodo]: AbiEncoder.create([
{ name: 'helper', type: 'address' },
{ name: 'poolAddress', type: 'address' },
{ name: 'isSellBase', type: 'bool' },
]),
[ERC20BridgeSource.DodoV2]: AbiEncoder.create([
{ name: 'poolAddress', type: 'address' },
{ name: 'isSellBase', type: 'bool' },
]),
// Curve like
[ERC20BridgeSource.Curve]: curveEncoder,
[ERC20BridgeSource.CurveV2]: curveEncoder,
[ERC20BridgeSource.Swerve]: curveEncoder,
[ERC20BridgeSource.SnowSwap]: curveEncoder,
[ERC20BridgeSource.Nerve]: curveEncoder,
[ERC20BridgeSource.Belt]: curveEncoder,
[ERC20BridgeSource.Ellipsis]: curveEncoder,
[ERC20BridgeSource.Smoothy]: curveEncoder,
[ERC20BridgeSource.Saddle]: curveEncoder,
[ERC20BridgeSource.XSigma]: curveEncoder,
[ERC20BridgeSource.FirebirdOneSwap]: curveEncoder,
[ERC20BridgeSource.IronSwap]: curveEncoder,
[ERC20BridgeSource.ACryptos]: curveEncoder,
// UniswapV2 like, (router, address[])
[ERC20BridgeSource.Bancor]: routerAddressPathEncoder,
[ERC20BridgeSource.UniswapV2]: routerAddressPathEncoder,
[ERC20BridgeSource.SushiSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.CryptoCom]: routerAddressPathEncoder,
[ERC20BridgeSource.Linkswap]: routerAddressPathEncoder,
[ERC20BridgeSource.ShibaSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.Pangolin]: routerAddressPathEncoder,
[ERC20BridgeSource.TraderJoe]: routerAddressPathEncoder,
[ERC20BridgeSource.SpiritSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.SpookySwap]: routerAddressPathEncoder,
// BSC
[ERC20BridgeSource.PancakeSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.PancakeSwapV2]: routerAddressPathEncoder,
[ERC20BridgeSource.BakerySwap]: routerAddressPathEncoder,
[ERC20BridgeSource.ApeSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.CafeSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.CheeseSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.JulSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.WaultSwap]: routerAddressPathEncoder,
// Polygon
[ERC20BridgeSource.QuickSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.ComethSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.Dfyn]: routerAddressPathEncoder,
[ERC20BridgeSource.Polydex]: routerAddressPathEncoder,
[ERC20BridgeSource.JetSwap]: routerAddressPathEncoder,
// Generic pools
[ERC20BridgeSource.Shell]: poolEncoder,
[ERC20BridgeSource.Component]: poolEncoder,
[ERC20BridgeSource.Mooniswap]: poolEncoder,
[ERC20BridgeSource.Eth2Dai]: poolEncoder,
[ERC20BridgeSource.MStable]: poolEncoder,
[ERC20BridgeSource.Balancer]: poolEncoder,
[ERC20BridgeSource.Cream]: poolEncoder,
[ERC20BridgeSource.Uniswap]: poolEncoder,
// Custom integrations
[ERC20BridgeSource.MakerPsm]: makerPsmEncoder,
[ERC20BridgeSource.BalancerV2]: balancerV2Encoder,
[ERC20BridgeSource.UniswapV3]: AbiEncoder.create([
{ name: 'router', type: 'address' },
{ name: 'path', type: 'bytes' },
]),
[ERC20BridgeSource.KyberDmm]: AbiEncoder.create('(address,address[],address[])'),
[ERC20BridgeSource.Lido]: AbiEncoder.create('(address)'),
};
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {
return [
// Maker asset amount.
side === MarketOperation.Sell ? fill.output.integerValue(BigNumber.ROUND_DOWN) : fill.input,
// Taker asset amount.
side === MarketOperation.Sell ? fill.input : fill.output.integerValue(BigNumber.ROUND_UP),
];
}
export function createNativeOptimizedOrder(
fill: NativeCollapsedFill,
fill: CollapsedNativeOrderFill,
side: MarketOperation,
): OptimizedMarketOrderBase<NativeLimitOrderFillData> | OptimizedMarketOrderBase<NativeRfqOrderFillData> {
const fillData = fill.fillData;
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
const base = {
type: fill.type,
source: ERC20BridgeSource.Native,
makerToken: fillData.order.makerToken,
takerToken: fillData.order.takerToken,
makerAmount,
takerAmount,
fills: [fill],
fillData,
};
return fill.type === FillQuoteTransformerOrderType.Rfq
? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
: { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
): OptimizedLimitOrder | OptimizedRfqOrder {
throw new Error(`No implementado`);
// const fillData = fill.fillData;
// const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
// const base = {
// type: fill.type,
// source: ERC20BridgeSource.Native,
// makerToken: fillData.order.makerToken,
// takerToken: fillData.order.takerToken,
// makerAmount,
// takerAmount,
// fills: [fill],
// fillData,
// };
// return fill.type === FillQuoteTransformerOrderType.Rfq
// ? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
// : { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
}

View File

@@ -1,18 +1,19 @@
import { BigNumber } from '@0x/utils';
import { MarketOperation } from '../../types';
import { Address, MarketOperation } from '../../types';
import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
import { ethToOutputAmount } from './fills';
import { createBridgeOrder, createNativeOptimizedOrder, CreateOrderFromPathOpts, getMakerTakerTokens } from './orders';
import { createBridgeOrder, createNativeOptimizedOrder } from './orders';
import { getCompleteRate, getRate } from './rate_utils';
import {
CollapsedGenericBridgeFill,
CollapsedFill,
CollapsedNativeOrderFill,
ERC20BridgeSource,
ExchangeProxyOverhead,
Fill,
NativeCollapsedFill,
OptimizedMarketOrder,
OptimizedOrder,
} from './types';
// tslint:disable: prefer-for-of no-bitwise completed-docs
@@ -38,10 +39,11 @@ export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
export class Path {
public collapsedFills?: ReadonlyArray<CollapsedFill>;
public orders?: OptimizedMarketOrder[];
public orders?: OptimizedOrder[];
public sourceFlags: bigint = BigInt(0);
protected _size: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
protected _adjustedSize: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
private _fallbackFillsStartIndex: number = 0;
public static create(
side: MarketOperation,
@@ -107,33 +109,25 @@ export class Path {
// Add the fills to the end that aren't already included
...fallback.fills.filter(f => !otherFillIds.includes(fillToFillId(f))),
];
this._fallbackFillsStartIndex = nativeFills.length + otherFills.length;
// Recompute the source flags
this.sourceFlags = this.fills.reduce((flags, fill) => flags | fill.flags, BigInt(0));
return this;
}
public collapse(opts: CreateOrderFromPathOpts): CollapsedPath {
const [makerToken, takerToken] = getMakerTakerTokens(opts);
public collapse(opts: { side: MarketOperation, inputToken: Address; outputToken: Address; }): CollapsedPath {
const collapsedFills = this.collapsedFills === undefined ? this._collapseFills() : this.collapsedFills;
this.orders = [];
for (let i = 0; i < collapsedFills.length; ) {
for (let i = 0; i < collapsedFills.length; ++i) {
if (collapsedFills[i].source === ERC20BridgeSource.Native) {
this.orders.push(createNativeOptimizedOrder(collapsedFills[i] as NativeCollapsedFill, opts.side));
++i;
this.orders.push(createNativeOptimizedOrder(collapsedFills[i] as CollapsedNativeOrderFill, opts.side));
continue;
}
// If there are contiguous bridge orders, we can batch them together.
// TODO jacob pretty sure this is from DFB and we can remove
const contiguousBridgeFills = [collapsedFills[i]];
for (let j = i + 1; j < collapsedFills.length; ++j) {
if (collapsedFills[j].source === ERC20BridgeSource.Native) {
break;
}
contiguousBridgeFills.push(collapsedFills[j]);
}
this.orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts.side));
i += 1;
this.orders.push(createBridgeOrder(
collapsedFills[i] as CollapsedGenericBridgeFill,
opts.inputToken,
opts.outputToken,
));
}
return this as CollapsedPath;
}
@@ -159,7 +153,7 @@ export class Path {
};
}
public adjustedCompleteRate(): BigNumber {
public adjustedCompleteMakerToTakerRate(): BigNumber {
const { input, output } = this.adjustedSize();
return getCompleteRate(this.side, input, output, this.targetInput);
}
@@ -199,7 +193,7 @@ export class Path {
if (input.isLessThan(targetInput) || otherInput.isLessThan(targetInput)) {
return input.isGreaterThan(otherInput);
} else {
return this.adjustedCompleteRate().isGreaterThan(other.adjustedCompleteRate());
return this.adjustedCompleteMakerToTakerRate().isGreaterThan(other.adjustedCompleteMakerToTakerRate());
}
// if (otherInput.isLessThan(targetInput)) {
// return input.isGreaterThan(otherInput);
@@ -214,7 +208,7 @@ export class Path {
return input.gte(this.targetInput);
}
public isValid(skipDuplicateCheck: boolean = false): boolean {
public isValid(quick: boolean = false): boolean {
for (let i = 0; i < this.fills.length; ++i) {
// Fill must immediately follow its parent.
if (this.fills[i].parent) {
@@ -222,8 +216,9 @@ export class Path {
return false;
}
}
if (!skipDuplicateCheck) {
if (!quick) {
// Fill must not be duplicated.
// Fills must all have the same input and output tokens.
for (let j = 0; j < i; ++j) {
if (this.fills[i] === this.fills[j]) {
return false;
@@ -249,7 +244,7 @@ export class Path {
private _collapseFills(): ReadonlyArray<CollapsedFill> {
this.collapsedFills = [];
for (const fill of this.fills) {
for (const [i, fill] of this.fills.entries()) {
const source = fill.source;
if (this.collapsedFills.length !== 0 && source !== ERC20BridgeSource.Native) {
const prevFill = this.collapsedFills[this.collapsedFills.length - 1];
@@ -257,8 +252,9 @@ export class Path {
if (prevFill.sourcePathId === fill.sourcePathId) {
prevFill.input = prevFill.input.plus(fill.input);
prevFill.output = prevFill.output.plus(fill.output);
prevFill.fillData = fill.fillData;
prevFill.data = fill.data;
prevFill.subFills.push(fill);
prevFill.gasCost;
continue;
}
}
@@ -266,10 +262,12 @@ export class Path {
sourcePathId: fill.sourcePathId,
source: fill.source,
type: fill.type,
fillData: fill.fillData,
data: fill.data,
input: fill.input,
output: fill.output,
subFills: [fill],
gasCost: fill.gasCost,
isFallback: this._fallbackFillsStartIndex > 0 ? i >= this._fallbackFillsStartIndex : false,
});
}
return this.collapsedFills;
@@ -296,5 +294,5 @@ export class Path {
export interface CollapsedPath extends Path {
readonly collapsedFills: ReadonlyArray<CollapsedFill>;
readonly orders: OptimizedMarketOrder[];
readonly orders: OptimizedOrder[];
}

View File

@@ -6,13 +6,14 @@ import * as _ from 'lodash';
import { performance } from 'perf_hooks';
import { DEFAULT_INFO_LOGGER } from '../../constants';
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
import { NativeOrderWithFillableAmounts } from '../native_orders';
import { MarketOperation } from '../../types';
import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID } from '../market_operation_utils/constants';
import { dexSamplesToFills, ethToOutputAmount, nativeOrdersToFills } from './fills';
import { DEFAULT_PATH_PENALTY_OPTS, Path, PathPenaltyOpts } from './path';
import { getRate } from './rate_utils';
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillData } from './types';
import { DexSample, ERC20BridgeSource, Fill } from './types';
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
@@ -42,11 +43,11 @@ function calculateOuputFee(
sampleOrNativeOrder: DexSample | NativeOrderWithFillableAmounts,
outputAmountPerEth: BigNumber,
inputAmountPerEth: BigNumber,
fees: FeeSchedule,
gasPrice: BigNumber,
): BigNumber {
if (isDexSample(sampleOrNativeOrder)) {
const { input, output, source, fillData } = sampleOrNativeOrder;
const fee = fees[source]?.(fillData) || 0;
const { input, output } = sampleOrNativeOrder;
const fee = gasPrice.times(sampleOrNativeOrder.gasCost);
const outputFee = ethToOutputAmount({
input,
output,
@@ -57,13 +58,12 @@ function calculateOuputFee(
return outputFee;
} else {
const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
const fee = fees[ERC20BridgeSource.Native]?.(sampleOrNativeOrder) || 0;
const outputFee = ethToOutputAmount({
input,
output,
inputAmountPerEth,
outputAmountPerEth,
ethAmount: fee,
ethAmount: gasPrice.times((sampleOrNativeOrder as NativeOrderWithFillableAmounts).gasCost),
});
return outputFee;
}
@@ -90,10 +90,10 @@ function findRoutesAndCreateOptimalPath(
nativeOrders: NativeOrderWithFillableAmounts[],
input: BigNumber,
opts: PathPenaltyOpts,
fees: FeeSchedule,
gasPrice: BigNumber,
): Path | undefined {
const createFill = (sample: DexSample) =>
dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, fees)[0];
dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, gasPrice)[0];
// Track sample id's to integers (required by rust router)
const sampleIdLookup: { [key: string]: number } = {};
let sampleIdCounter = 0;
@@ -135,7 +135,7 @@ function findRoutesAndCreateOptimalPath(
memo.inputs.push(sample.input.integerValue().toNumber());
memo.outputs.push(sample.output.integerValue().toNumber());
memo.outputFees.push(
calculateOuputFee(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
calculateOuputFee(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, gasPrice)
.integerValue()
.toNumber(),
);
@@ -183,7 +183,7 @@ function findRoutesAndCreateOptimalPath(
normalizedOrderOutput.integerValue().toNumber(),
];
// NOTE: same fee no matter if full or partial fill
const fee = calculateOuputFee(side, nativeOrder, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
const fee = calculateOuputFee(side, nativeOrder, opts.outputAmountPerEth, opts.inputAmountPerEth, gasPrice)
.integerValue()
.toNumber();
const outputFees = [fee, fee, fee];
@@ -250,7 +250,7 @@ function findRoutesAndCreateOptimalPath(
rustInputAdjusted,
opts.outputAmountPerEth,
opts.inputAmountPerEth,
fees,
gasPrice,
)[0];
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
adjustedFills.push(nativeFill);
@@ -259,7 +259,7 @@ function findRoutesAndCreateOptimalPath(
// NOTE: For DexSamples only
let fill = createFill(current);
const routeSamples = routeSamplesAndNativeOrders as Array<DexSample<FillData>>;
const routeSamples = routeSamplesAndNativeOrders as Array<DexSample>;
// 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
@@ -321,7 +321,7 @@ export function findOptimalRustPathFromSamples(
nativeOrders: NativeOrderWithFillableAmounts[],
input: BigNumber,
opts: PathPenaltyOpts,
fees: FeeSchedule,
gasPrice: BigNumber,
chainId: ChainId,
): Path | undefined {
const before = performance.now();
@@ -331,7 +331,7 @@ export function findOptimalRustPathFromSamples(
'Rust router total routing performance',
);
const allSourcesPath = findRoutesAndCreateOptimalPath(side, samples, nativeOrders, input, opts, fees);
const allSourcesPath = findRoutesAndCreateOptimalPath(side, samples, nativeOrders, input, opts, gasPrice);
if (!allSourcesPath) {
return undefined;
}
@@ -345,7 +345,7 @@ export function findOptimalRustPathFromSamples(
const vipSourcesSamples = samples.filter(s => s[0] && vipSourcesSet.has(s[0].source));
if (vipSourcesSamples.length > 0) {
const vipSourcesPath = findRoutesAndCreateOptimalPath(side, vipSourcesSamples, [], input, opts, fees);
const vipSourcesPath = findRoutesAndCreateOptimalPath(side, vipSourcesSamples, [], input, opts, gasPrice);
const { input: allSourcesInput, output: allSourcesOutput } = allSourcesPath.adjustedSize();
// NOTE: For sell quotes input is the taker asset and for buy quotes input is the maker asset
@@ -381,7 +381,7 @@ export async function findOptimalPathJSAsync(
): Promise<Path | undefined> {
// 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);
const sortedPaths = reducePaths(fillsToSortedPaths(fills, side, targetInput, opts));
if (sortedPaths.length === 0) {
return undefined;
}
@@ -404,8 +404,8 @@ export function fillsToSortedPaths(
): Path[] {
const paths = fills.map(singleSourceFills => Path.create(side, singleSourceFills, targetInput, opts));
const sortedPaths = paths.sort((a, b) => {
const aRate = a.adjustedCompleteRate();
const bRate = b.adjustedCompleteRate();
const aRate = a.adjustedCompleteMakerToTakerRate();
const bRate = b.adjustedCompleteMakerToTakerRate();
// There is a case where the adjusted completed rate isn't sufficient for the desired amount
// resulting in a NaN div by 0 (output)
if (bRate.isNaN()) {
@@ -420,7 +420,7 @@ export function fillsToSortedPaths(
}
// Remove paths which have no impact on the optimal path
export function reducePaths(sortedPaths: Path[], side: MarketOperation): Path[] {
export function reducePaths(sortedPaths: Path[]): Path[] {
// Any path which has a min rate that is less than the best adjusted completed rate has no chance of improving
// the overall route.
const bestNonNativeCompletePath = sortedPaths.filter(
@@ -433,7 +433,7 @@ export function reducePaths(sortedPaths: Path[], side: MarketOperation): Path[]
if (!bestNonNativeCompletePath) {
return sortedPaths;
}
const bestNonNativeCompletePathAdjustedRate = bestNonNativeCompletePath.adjustedCompleteRate();
const bestNonNativeCompletePathAdjustedRate = bestNonNativeCompletePath.adjustedCompleteMakerToTakerRate();
if (!bestNonNativeCompletePathAdjustedRate.isGreaterThan(0)) {
return sortedPaths;
}

View File

@@ -1,149 +0,0 @@
import { BigNumber } from '@0x/utils';
/**
* This has been copied from https://github.com/balancer-labs/balancer-sor/blob/john/rc2/src/helpers.ts.
* Still awaiting V2 support for @balancer-labs/sor, once full V2 support is shipped we can upgrade sor and delete this file
*/
export const parsePoolData = (
directPools: SubGraphPoolDictionary,
tokenIn: string,
tokenOut: string,
mostLiquidPoolsFirstHop: SubGraphPool[] = [],
mostLiquidPoolsSecondHop: SubGraphPool[] = [],
hopTokens: string[] = [],
): [SubGraphPoolDictionary, Path[]] => {
const pathDataList: Path[] = [];
const pools: SubGraphPoolDictionary = {};
// First add direct pair paths
// tslint:disable-next-line:forin
for (const idKey in directPools) {
const p: SubGraphPool = directPools[idKey];
// Add pool to the set with all pools (only adds if it's still not present in dict)
pools[idKey] = p;
const swap: Swap = {
pool: p.id,
tokenIn,
tokenOut,
tokenInDecimals: 18, // Placeholder for actual decimals
tokenOutDecimals: 18,
};
const path: Path = {
id: p.id,
swaps: [swap],
};
pathDataList.push(path);
}
// Now add multi-hop paths.
// mostLiquidPoolsFirstHop and mostLiquidPoolsSecondHop always has the same
// lengh of hopTokens
for (let i = 0; i < hopTokens.length; i++) {
// Add pools to the set with all pools (only adds if it's still not present in dict)
pools[mostLiquidPoolsFirstHop[i].id] = mostLiquidPoolsFirstHop[i];
pools[mostLiquidPoolsSecondHop[i].id] = mostLiquidPoolsSecondHop[i];
const swap1: Swap = {
pool: mostLiquidPoolsFirstHop[i].id,
tokenIn,
tokenOut: hopTokens[i],
tokenInDecimals: 18, // Placeholder for actual decimals
tokenOutDecimals: 18,
};
const swap2: Swap = {
pool: mostLiquidPoolsSecondHop[i].id,
tokenIn: hopTokens[i],
tokenOut,
tokenInDecimals: 18, // Placeholder for actual decimals
tokenOutDecimals: 18,
};
const path: Path = {
id: mostLiquidPoolsFirstHop[i].id + mostLiquidPoolsSecondHop[i].id, // Path id is the concatenation of the ids of poolFirstHop and poolSecondHop
swaps: [swap1, swap2],
};
pathDataList.push(path);
}
return [pools, pathDataList];
};
interface SubGraphPool {
id: string;
swapFee: string;
totalWeight: string;
totalShares: string;
tokens: SubGraphToken[];
tokensList: string[];
poolType?: string;
// Only for stable pools
amp: string;
// Only for element pools
lpShares?: BigNumber;
time?: BigNumber;
principalToken?: string;
baseToken?: string;
}
interface SubGraphPoolDictionary {
[poolId: string]: SubGraphPool;
}
interface SubGraphToken {
address: string;
balance: string;
decimals: string | number;
// Stable & Element field
weight?: string;
}
interface Path {
id: string; // pool address if direct path, contactenation of pool addresses if multihop
swaps: Swap[];
poolPairData?: PoolPairData[];
limitAmount?: BigNumber;
filterEffectivePrice?: BigNumber; // TODO: This is just used for filtering, maybe there is a better way to filter?
}
interface Swap {
pool: string;
tokenIn: string;
tokenOut: string;
swapAmount?: string;
limitReturnAmount?: string;
maxPrice?: string;
tokenInDecimals: number;
tokenOutDecimals: number;
}
export interface PoolPairData {
id: string;
poolType?: string; // Todo: make this a mandatory field?
pairType?: string; // Todo: make this a mandatory field?
tokenIn: string;
tokenOut: string;
balanceIn?: BigNumber;
balanceOut?: BigNumber;
decimalsIn: number;
decimalsOut: number;
swapFee: BigNumber;
// For weighted & element pools
weightIn?: BigNumber;
weightOut?: BigNumber;
// Only for stable pools
allBalances: BigNumber[];
invariant?: BigNumber;
amp?: BigNumber;
tokenIndexIn?: number;
tokenIndexOut?: number;
// Only for element pools
lpShares?: BigNumber;
time?: BigNumber;
principalToken?: string;
baseToken?: string;
}

View File

@@ -1,107 +0,0 @@
import { getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor';
import { Pool } from '@balancer-labs/sor/dist/types';
import { gql, request } from 'graphql-request';
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_SUBGRAPH_URL, BALANCER_TOP_POOLS_FETCHED } from '../constants';
import { CacheValue, PoolsCache } from './pools_cache';
// tslint:disable:custom-no-magic-numbers
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
// tslint:enable:custom-no-magic-numbers
interface BalancerPoolResponse {
id: string;
swapFee: string;
tokens: Array<{ address: string; decimals: number; balance: string }>;
tokensList: string[];
totalWeight: string;
}
export class BalancerPoolsCache extends PoolsCache {
constructor(
private readonly _subgraphUrl: string = BALANCER_SUBGRAPH_URL,
cache: { [key: string]: CacheValue } = {},
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
) {
super(cache);
void this._loadTopPoolsAsync();
// Reload the top pools every 12 hours
setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2);
}
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
try {
const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
// Sort by maker token balance (descending)
const pools = parsePoolData(poolData, takerToken, makerToken).sort((a, b) =>
b.balanceOut.minus(a.balanceOut).toNumber(),
);
return pools.length > this.maxPoolsFetched ? pools.slice(0, this.maxPoolsFetched) : pools;
} catch (err) {
return [];
}
}
protected async _loadTopPoolsAsync(): Promise<void> {
const fromToPools: {
[from: string]: { [to: string]: Pool[] };
} = {};
const pools = await this._fetchTopPoolsAsync();
for (const pool of pools) {
const { tokensList } = pool;
for (const from of tokensList) {
for (const to of tokensList.filter(t => t.toLowerCase() !== from.toLowerCase())) {
fromToPools[from] = fromToPools[from] || {};
fromToPools[from][to] = fromToPools[from][to] || [];
try {
// The list of pools must be relevant to `from` and `to` for `parsePoolData`
const poolData = parsePoolData([pool], from, to);
fromToPools[from][to].push(poolData[0]);
// Cache this as we progress through
const expiresAt = Date.now() + this._cacheTimeMs;
this._cachePoolsForPair(from, to, fromToPools[from][to], expiresAt);
} catch {
// soldier on
}
}
}
}
}
protected async _fetchTopPoolsAsync(): Promise<BalancerPoolResponse[]> {
const query = gql`
query fetchTopPools($topPoolsFetched: Int!) {
pools(
first: $topPoolsFetched
where: { publicSwap: true, liquidity_gt: 0 }
orderBy: swapsCount
orderDirection: desc
) {
id
publicSwap
swapFee
totalWeight
tokensList
tokens {
id
address
balance
decimals
symbol
denormWeight
}
}
}
`;
try {
const { pools } = await request(this._subgraphUrl, query, { topPoolsFetched: this._topPoolsFetched });
return pools;
} catch (err) {
return [];
}
}
}

View File

@@ -1,178 +0,0 @@
import { ChainId } from '@0x/contract-addresses';
import { BigNumber } from '@0x/utils';
// import { parsePoolData } from '@balancer-labs'; // TODO - upgrade to v2
import { Pool } from '@balancer-labs/sor/dist/types';
import { gql, request } from 'graphql-request';
import { DEFAULT_WARNING_LOGGER } from '../../../constants';
import { LogFunction } from '../../../types';
import {
BALANCER_MAX_POOLS_FETCHED,
BALANCER_TOP_POOLS_FETCHED,
BALANCER_V2_SUBGRAPH_URL_BY_CHAIN,
} from '../constants';
import { parsePoolData } from './balancer_sor_v2';
import { CacheValue, PoolsCache } from './pools_cache';
// tslint:disable-next-line:custom-no-magic-numbers
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
interface BalancerPoolResponse {
id: string;
swapFee: string;
tokens: Array<{ address: string; decimals: number; balance: string; weight: string; symbol: string }>;
tokensList: string[];
totalWeight: string;
totalShares: string;
amp: string | null;
}
export class BalancerV2PoolsCache extends PoolsCache {
private static _parseSubgraphPoolData(pool: any, takerToken: string, makerToken: string): Pool {
const tToken = pool.tokens.find((t: any) => t.address === takerToken);
const mToken = pool.tokens.find((t: any) => t.address === makerToken);
const swap = pool.swaps && pool.swaps[0];
const tokenAmountOut = swap ? swap.tokenAmountOut : undefined;
const tokenAmountIn = swap ? swap.tokenAmountIn : undefined;
const spotPrice =
tokenAmountOut && tokenAmountIn ? new BigNumber(tokenAmountOut).div(tokenAmountIn) : undefined; // TODO: xianny check
return {
id: pool.id,
balanceIn: new BigNumber(tToken.balance),
balanceOut: new BigNumber(mToken.balance),
weightIn: new BigNumber(tToken.weight),
weightOut: new BigNumber(mToken.weight),
swapFee: new BigNumber(pool.swapFee),
spotPrice,
};
}
constructor(
chainId: ChainId,
private readonly subgraphUrl: string = BALANCER_V2_SUBGRAPH_URL_BY_CHAIN[chainId],
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
cache: { [key: string]: CacheValue } = {},
) {
super(cache);
void this._loadTopPoolsAsync();
// Reload the top pools every 12 hours
setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2);
}
// protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
// try {
// const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
// // Sort by maker token balance (descending)
// const pools = parsePoolData(poolData, takerToken, makerToken).sort((a, b) =>
// b.balanceOut.minus(a.balanceOut).toNumber(),
// );
// return pools.length > this.maxPoolsFetched ? pools.slice(0, this.maxPoolsFetched) : pools;
// } catch (err) {
// return [];
// }
// }
protected async _fetchTopPoolsAsync(): Promise<BalancerPoolResponse[]> {
const query = gql`
query fetchTopPools($topPoolsFetched: Int!) {
pools(
first: $topPoolsFetched
where: { totalLiquidity_gt: 0 }
orderBy: swapsCount
orderDirection: desc
) {
id
swapFee
totalWeight
tokensList
amp
totalShares
tokens {
id
address
balance
decimals
symbol
weight
}
}
}
`;
const { pools } = await request<{ pools: BalancerPoolResponse[] }>(this.subgraphUrl, query, {
topPoolsFetched: this._topPoolsFetched,
});
return pools;
}
protected async _loadTopPoolsAsync(): Promise<void> {
const fromToPools: {
[from: string]: { [to: string]: Pool[] };
} = {};
const pools = await this._fetchTopPoolsAsync();
for (const pool of pools) {
const { tokensList } = pool;
for (const from of tokensList) {
for (const to of tokensList.filter(t => t.toLowerCase() !== from.toLowerCase())) {
fromToPools[from] = fromToPools[from] || {};
fromToPools[from][to] = fromToPools[from][to] || [];
try {
// The list of pools must be relevant to `from` and `to` for `parsePoolData`
const [poolData] = parsePoolData({ [pool.id]: pool as any }, from, to);
fromToPools[from][to].push(
BalancerV2PoolsCache._parseSubgraphPoolData(poolData[pool.id], from, to),
);
// Cache this as we progress through
const expiresAt = Date.now() + this._cacheTimeMs;
this._cachePoolsForPair(from, to, fromToPools[from][to], expiresAt);
} catch (err) {
this._warningLogger(err, `Failed to load Balancer V2 top pools`);
// soldier on
}
}
}
}
}
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
const query = gql`
query getPools {
pools(
first: ${this.maxPoolsFetched},
where: {
tokensList_contains: ["${takerToken}", "${makerToken}"]
}
) {
id
tokens {
address
balance
weight
}
swapFee
swaps(
orderBy: timestamp, orderDirection: desc, first: 1,
where:{
tokenIn: "${takerToken}",
tokenOut: "${makerToken}"
}
) {
tokenAmountIn
tokenAmountOut
}
}
}
`;
try {
const { pools } = await request(this.subgraphUrl, query);
return pools.map((pool: any) => BalancerV2PoolsCache._parseSubgraphPoolData(pool, takerToken, makerToken));
} catch (e) {
return [];
}
}
}

View File

@@ -1,28 +0,0 @@
import { Pool } from '@balancer-labs/sor/dist/types';
import { getPoolsWithTokens, parsePoolData } from 'cream-sor';
import { BALANCER_MAX_POOLS_FETCHED } from '../constants';
import { CacheValue, PoolsCache } from './pools_cache';
export class CreamPoolsCache extends PoolsCache {
constructor(
_cache: { [key: string]: CacheValue } = {},
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
) {
super(_cache);
}
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
try {
const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
// Sort by maker token balance (descending)
const pools = parsePoolData(poolData, takerToken, makerToken).sort((a, b) =>
b.balanceOut.minus(a.balanceOut).toNumber(),
);
return pools.slice(0, this.maxPoolsFetched);
} catch (err) {
return [];
}
}
}

View File

@@ -1,4 +0,0 @@
export { BalancerPoolsCache } from './balancer_utils';
export { BalancerV2PoolsCache } from './balancer_v2_utils';
export { CreamPoolsCache } from './cream_utils';
export { PoolsCache } from './pools_cache';

View File

@@ -1,78 +0,0 @@
import { Pool } from '@balancer-labs/sor/dist/types';
import { ONE_HOUR_IN_SECONDS, ONE_SECOND_MS } from '../constants';
export { Pool };
export interface CacheValue {
expiresAt: number;
pools: Pool[];
}
// tslint:disable:custom-no-magic-numbers
// Cache results for 30mins
const DEFAULT_CACHE_TIME_MS = (ONE_HOUR_IN_SECONDS / 2) * ONE_SECOND_MS;
const DEFAULT_TIMEOUT_MS = 1000;
// tslint:enable:custom-no-magic-numbers
export abstract class PoolsCache {
protected static _isExpired(value: CacheValue): boolean {
return Date.now() >= value.expiresAt;
}
constructor(
protected readonly _cache: { [key: string]: CacheValue },
protected readonly _cacheTimeMs: number = DEFAULT_CACHE_TIME_MS,
) {}
public async getFreshPoolsForPairAsync(
takerToken: string,
makerToken: string,
timeoutMs: number = DEFAULT_TIMEOUT_MS,
): Promise<Pool[]> {
const timeout = new Promise<Pool[]>(resolve => setTimeout(resolve, timeoutMs, []));
return Promise.race([this._getAndSaveFreshPoolsForPairAsync(takerToken, makerToken), timeout]);
}
public getCachedPoolAddressesForPair(
takerToken: string,
makerToken: string,
ignoreExpired: boolean = true,
): string[] | undefined {
const key = JSON.stringify([takerToken, makerToken]);
const value = this._cache[key];
if (ignoreExpired) {
return value === undefined ? [] : value.pools.map(pool => pool.id);
}
if (!value) {
return undefined;
}
if (PoolsCache._isExpired(value)) {
return undefined;
}
return (value || []).pools.map(pool => pool.id);
}
public isFresh(takerToken: string, makerToken: string): boolean {
const cached = this.getCachedPoolAddressesForPair(takerToken, makerToken, false);
return cached !== undefined;
}
protected async _getAndSaveFreshPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
const key = JSON.stringify([takerToken, makerToken]);
const value = this._cache[key];
if (value === undefined || value.expiresAt >= Date.now()) {
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
const expiresAt = Date.now() + this._cacheTimeMs;
this._cachePoolsForPair(takerToken, makerToken, pools, expiresAt);
}
return this._cache[key].pools;
}
protected _cachePoolsForPair(takerToken: string, makerToken: string, pools: Pool[], expiresAt: number): void {
const key = JSON.stringify([takerToken, makerToken]);
this._cache[key] = {
pools,
expiresAt,
};
}
protected abstract _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]>;
}

View File

@@ -2,38 +2,10 @@ 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 { ZERO_AMOUNT } from './constants';
// tslint:disable:no-bitwise
/**
* Returns the fee-adjusted rate of a two-hop quote. Returns zero if the
* quote falls short of the target input.
*/
export function getTwoHopAdjustedRate(
side: MarketOperation,
twoHopQuote: DexSample<MultiHopFillData>,
targetInput: BigNumber,
outputAmountPerEth: BigNumber,
fees: FeeSchedule = {},
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 adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput);
}
/**
* Computes the "complete" rate given the input/output of a path.
* This value penalizes the path if it falls short of the target input.

View File

@@ -1,167 +1,121 @@
import { ChainId } from '@0x/contract-addresses';
import { BigNumber, NULL_BYTES } from '@0x/utils';
import { BigNumber } from '@0x/utils';
import { SamplerOverrides } from '../../types';
import { ERC20BridgeSamplerContract } from '../../wrappers';
import { Address } from '../../types';
import { BancorService } from './bancor_service';
import { PoolsCache } from './pools_cache';
import { SamplerOperations } from './sampler_operations';
import { BatchedOperation, ERC20BridgeSource, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types';
import { DexSample, ERC20BridgeSource, TokenAdjacencyGraph } from './types';
import { SamplerServiceRpcClient } from './sampler_service_rpc_client';
/**
* Generate sample amounts up to `maxFillAmount`.
*/
export function getSampleAmounts(maxFillAmount: BigNumber, numSamples: number, expBase: number = 1): BigNumber[] {
const distribution = [...Array<BigNumber>(numSamples)].map((_v, i) => new BigNumber(expBase).pow(i));
const stepSizes = distribution.map(d => d.div(BigNumber.sum(...distribution)));
const amounts = stepSizes.map((_s, i) => {
if (i === numSamples - 1) {
return maxFillAmount;
}
return maxFillAmount
.times(BigNumber.sum(...[0, ...stepSizes.slice(0, i + 1)]))
.integerValue(BigNumber.ROUND_UP);
});
return amounts;
const DEFAULT_LIQUIDITY_SAMPLES = 16;
interface TokenInfo {
decimals: number;
address: Address;
gasCost: number;
symbol: string;
}
type BatchedOperationResult<T> = T extends BatchedOperation<infer TResult> ? TResult : never;
export interface Sampler {
chainId: ChainId;
getTokenInfosAsync(tokens: Address[]): Promise<TokenInfo[]>;
getPricesAsync(paths: Address[][], sources: ERC20BridgeSource[]): Promise<BigNumber[]>;
getSellLiquidityAsync(path: Address[], takerAmount: BigNumber, sources: ERC20BridgeSource[], numSamples?: number): Promise<DexSample[][]>;
getBuyLiquidityAsync(path: Address[], makerAmount: BigNumber, sources: ERC20BridgeSource[], numSamples?: number): Promise<DexSample[][]>;
}
/**
* Encapsulates interactions with the `ERC20BridgeSampler` contract.
*/
export class DexOrderSampler extends SamplerOperations {
constructor(
public readonly chainId: ChainId,
_samplerContract: ERC20BridgeSamplerContract,
private readonly _samplerOverrides?: SamplerOverrides,
poolsCaches?: { [key in ERC20BridgeSource]: PoolsCache },
tokenAdjacencyGraph?: TokenAdjacencyGraph,
liquidityProviderRegistry?: LiquidityProviderRegistry,
bancorServiceFn: () => Promise<BancorService | undefined> = async () => undefined,
) {
super(chainId, _samplerContract, poolsCaches, tokenAdjacencyGraph, liquidityProviderRegistry, bancorServiceFn);
export class SamplerClient implements Sampler {
static createFromChainIdAndEndpoint(chainId: ChainId, endpoint: string): SamplerClient {
return new SamplerClient(chainId, new SamplerServiceRpcClient(endpoint));
}
/* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */
// prettier-ignore
public async executeAsync<
T1
>(...ops: [T1]): Promise<[
BatchedOperationResult<T1>
]>;
// prettier-ignore
public async executeAsync<
T1, T2
>(...ops: [T1, T2]): Promise<[
BatchedOperationResult<T1>,
BatchedOperationResult<T2>
]>;
// prettier-ignore
public async executeAsync<
T1, T2, T3
>(...ops: [T1, T2, T3]): Promise<[
BatchedOperationResult<T1>,
BatchedOperationResult<T2>,
BatchedOperationResult<T3>
]>;
// prettier-ignore
public async executeAsync<
T1, T2, T3, T4
>(...ops: [T1, T2, T3, T4]): Promise<[
BatchedOperationResult<T1>,
BatchedOperationResult<T2>,
BatchedOperationResult<T3>,
BatchedOperationResult<T4>
]>;
// prettier-ignore
public async executeAsync<
T1, T2, T3, T4, T5
>(...ops: [T1, T2, T3, T4, T5]): Promise<[
BatchedOperationResult<T1>,
BatchedOperationResult<T2>,
BatchedOperationResult<T3>,
BatchedOperationResult<T4>,
BatchedOperationResult<T5>
]>;
// prettier-ignore
public async executeAsync<
T1, T2, T3, T4, T5, T6
>(...ops: [T1, T2, T3, T4, T5, T6]): Promise<[
BatchedOperationResult<T1>,
BatchedOperationResult<T2>,
BatchedOperationResult<T3>,
BatchedOperationResult<T4>,
BatchedOperationResult<T5>,
BatchedOperationResult<T6>
]>;
// prettier-ignore
public async executeAsync<
T1, T2, T3, T4, T5, T6, T7
>(...ops: [T1, T2, T3, T4, T5, T6, T7]): Promise<[
BatchedOperationResult<T1>,
BatchedOperationResult<T2>,
BatchedOperationResult<T3>,
BatchedOperationResult<T4>,
BatchedOperationResult<T5>,
BatchedOperationResult<T6>,
BatchedOperationResult<T7>
]>;
// prettier-ignore
public async executeAsync<
T1, T2, T3, T4, T5, T6, T7, T8
>(...ops: [T1, T2, T3, T4, T5, T6, T7, T8]): Promise<[
BatchedOperationResult<T1>,
BatchedOperationResult<T2>,
BatchedOperationResult<T3>,
BatchedOperationResult<T4>,
BatchedOperationResult<T5>,
BatchedOperationResult<T6>,
BatchedOperationResult<T7>,
BatchedOperationResult<T8>
]>;
/**
* Run a series of operations from `DexOrderSampler.ops` in a single transaction.
*/
public async executeAsync(...ops: any[]): Promise<any[]> {
return this.executeBatchAsync(ops);
static async createFromEndpointAsync(endpoint: string): Promise<SamplerClient> {
const service = new SamplerServiceRpcClient(endpoint);
const chainId = await service.getChainIdAsync();
return new SamplerClient(
chainId,
service,
);
}
/**
* 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[]> {
const callDatas = ops.map(o => o.encodeCall());
const { overrides, block } = this._samplerOverrides
? this._samplerOverrides
: { overrides: undefined, block: undefined };
private constructor(
private readonly _chainId: number,
private readonly _service: SamplerServiceRpcClient,
) {}
// All operations are NOOPs
if (callDatas.every(cd => cd === NULL_BYTES)) {
return callDatas.map((_callData, i) => ops[i].handleCallResults(NULL_BYTES));
}
// Execute all non-empty calldatas.
const rawCallResults = await this._samplerContract
.batchCall(callDatas.filter(cd => cd !== NULL_BYTES))
.callAsync({ overrides }, block);
// Return the parsed results.
let rawCallResultsIdx = 0;
return 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);
});
public get chainId(): ChainId {
return this._chainId;
}
public async getPricesAsync(
paths: Address[][],
sources: ERC20BridgeSource[],
): Promise<BigNumber[]> {
return this._service.getPricesAsync(paths.map(p => ({
tokenPath: p,
demand: true,
sources,
})));
}
public async getTokenInfosAsync(tokens: Address[]): Promise<TokenInfo[]> {
return this._service.getTokensAsync(tokens);
}
public async getSellLiquidityAsync(
path: Address[],
takerAmount: BigNumber,
sources: ERC20BridgeSource[],
numSamples: number = DEFAULT_LIQUIDITY_SAMPLES,
): Promise<DexSample[][]> {
const liquidity = await this._service.getSellLiquidityAsync(
sources.map(s => ({
numSamples,
tokenPath: path,
inputAmount: takerAmount,
source: s,
demand: true,
})),
);
return liquidity.map(
liq => liq.liquidityCurves.map(
pts =>
pts.map(pt => ({
input: pt.sellAmount,
output: pt.buyAmount,
encodedFillData: pt.encodedFillData,
metadata: pt.metadata,
gasCost: pt.gasCost,
source: liq.source,
}) as DexSample),
)).flat(1);
}
public async getBuyLiquidityAsync(
path: Address[],
makerAmount: BigNumber,
sources: ERC20BridgeSource[],
numSamples: number = DEFAULT_LIQUIDITY_SAMPLES,
): Promise<DexSample[][]> {
const liquidity = await this._service.getBuyLiquidityAsync(
sources.map(s => ({
numSamples,
tokenPath: path,
inputAmount: makerAmount,
source: s,
demand: true,
})),
);
return liquidity.map(
liq => liq.liquidityCurves.map(
pts =>
pts.map(pt => ({
input: pt.buyAmount,
output: pt.sellAmount,
encodedFillData: pt.encodedFillData,
metadata: pt.metadata,
gasCost: pt.gasCost,
source: liq.source,
}) as DexSample),
)).flat(1);
}
}

View File

@@ -1,62 +0,0 @@
import { ContractFunctionObj } from '@0x/base-contract';
import { BigNumber, decodeBytesAsRevertError, logUtils } from '@0x/utils';
import { ERC20BridgeSamplerContract } from '../../wrappers';
import { ERC20BridgeSource, FillData, SourceQuoteOperation } from './types';
export type Parameters<T> = T extends (...args: infer TArgs) => any ? TArgs : never;
export interface SamplerContractCall<
TFunc extends (...args: any[]) => ContractFunctionObj<any>,
TFillData extends FillData = FillData
> {
contract: ERC20BridgeSamplerContract;
function: TFunc;
params: Parameters<TFunc>;
callback?: (callResults: string, fillData: TFillData) => BigNumber[];
}
export class SamplerContractOperation<
TFunc extends (...args: any[]) => ContractFunctionObj<any>,
TFillData extends FillData = FillData
> implements SourceQuoteOperation<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) => BigNumber[];
constructor(opts: { source: ERC20BridgeSource; fillData?: TFillData } & SamplerContractCall<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;
}
public encodeCall(): string {
return this._samplerFunction
.bind(this._samplerContract)(...this._params)
.getABIEncodedTransactionData();
}
public handleCallResults(callResults: string): BigNumber[] {
if (this._callback !== undefined) {
return this._callback(callResults, this.fillData);
} else {
return this._samplerContract.getABIDecodedReturnData<BigNumber[]>(this._samplerFunction.name, callResults);
}
}
public handleRevert(callResults: string): BigNumber[] {
let msg = callResults;
try {
msg = decodeBytesAsRevertError(callResults).toString();
} catch (e) {
// do nothing
}
logUtils.warn(`SamplerContractOperation: ${this.source}.${this._samplerFunction.name} reverted ${msg}`);
return [];
}
}

View File

@@ -0,0 +1,169 @@
import { BigNumber } from '@0x/utils';
import { Client as OpenRpcClient, HTTPTransport, RequestManager } from '@open-rpc/client-js';
import { Address, Bytes } from '../../types';
type DecimalString = string;
export interface LiquidityCurvePoint {
sellAmount: BigNumber;
buyAmount: BigNumber;
encodedFillData: Bytes;
metadata: object;
gasCost: number;
}
interface RpcLiquidityCurvePoint {
sellAmount: DecimalString;
buyAmount: DecimalString;
encodedFillData: Bytes;
jsonMetadata: string;
gasCost: number;
}
export interface LiquidityRequest {
tokenPath: Address[];
inputAmount: BigNumber;
source: string;
demand?: boolean;
numSamples?: number;
}
type RpcLiquidityRequest = Omit<LiquidityRequest, 'inputAmount'> & {
inputAmount: string;
}
export interface PriceRequest {
tokenPath: Address[];
sources?: string[];
demand?: boolean;
}
type RpcPriceRequest = PriceRequest;
export interface LiquidityResponse {
source: string;
liquidityCurves: LiquidityCurvePoint[][];
}
type RpcLiquidityResponse = & Omit<LiquidityResponse, 'liquidityCurves'> & {
source: string;
liquidityCurves: RpcLiquidityCurvePoint[][];
}
export interface TokenResponse {
address: Address;
symbol: string;
decimals: number;
gasCost: number;
}
type RpcTokenResponse = TokenResponse;
export class SamplerServiceRpcClient {
private _rpcClient: OpenRpcClient;
public constructor(url: string) {
const transport = new HTTPTransport(url);
// HACK(dorothy-zbornak): One of AS/API's deps globally registers a version of
// isometric-fetch that doesn't work with open-rpc. It seems to disagree on
// the type of 'headers'.
(transport as any).headers = {'content-type': 'application/json'};
this._rpcClient = new OpenRpcClient(new RequestManager([transport]));
}
private async _requestAsync<TResult, TArgs = any>(method: string, params: TArgs[] = []): Promise<TResult> {
try {
return await this._rpcClient.request({ method, params }) as Promise<TResult>;
} catch (err) {
throw new Error(`Error making RPC request "${method}" to sampler service: ${err}`);
}
}
public async getChainIdAsync(): Promise<number> {
return this._requestAsync<number>('get_chain_id');
}
public async getSellLiquidityAsync(reqs: LiquidityRequest[]): Promise<LiquidityResponse[]> {
const resp = await this._requestAsync<RpcLiquidityResponse[], RpcLiquidityRequest[]>(
'get_sell_liquidity',
[
reqs.map(r => ({
...r,
inputAmount: r.inputAmount.toString(10),
})),
],
);
return resp.map(r => ({
...r,
liquidityCurves: r.liquidityCurves.map(a => a.map(c => ({
...c,
buyAmount: new BigNumber(c.buyAmount),
sellAmount: new BigNumber(c.sellAmount),
metadata: decodeMetadata(c.jsonMetadata),
}))),
}));
}
public async getBuyLiquidityAsync(reqs: LiquidityRequest[]): Promise<LiquidityResponse[]> {
const resp = await this._requestAsync<RpcLiquidityResponse[], RpcLiquidityRequest[]>(
'get_buy_liquidity',
[
reqs.map(r => ({
...r,
inputAmount: r.inputAmount.toString(10),
})),
],
);
return resp.map(r => ({
...r,
liquidityCurves: r.liquidityCurves.map(a => a.map(c => ({
...c,
buyAmount: new BigNumber(c.buyAmount),
sellAmount: new BigNumber(c.sellAmount),
metadata: decodeMetadata(c.jsonMetadata),
}))),
}));
}
public async getPricesAsync(reqs: PriceRequest[]): Promise<BigNumber[]> {
const resp = await this._requestAsync<DecimalString[], RpcPriceRequest[]>(
'get_prices',
[ reqs ],
);
return resp.map(r => new BigNumber(r));
}
public async getTokensAsync(addresses: Address[]): Promise<TokenResponse[]> {
return this._requestAsync<RpcTokenResponse[], Address[]>(
'get_tokens',
[ addresses ],
);
}
}
function decodeMetadata(jsonMetadata: string): any {
if (!jsonMetadata) {
return undefined;
}
return unmarshallMetadata(JSON.parse(jsonMetadata));
}
function unmarshallMetadata(v: any): any {
switch (typeof(v)) {
case 'string':
if (/^\d+n$/.test(v)) {
return new BigNumber(v.slice(0, -1));
}
return v;
case 'object':
if (Array.isArray(v)) {
return v.map(v => unmarshallMetadata(v));
}
return Object.assign(
{},
...Object.entries(v).map(([k, v]) => ({ [k]: v})),
);
}
return v;
}

View File

@@ -2,12 +2,15 @@ import {
FillQuoteTransformerLimitOrderInfo,
FillQuoteTransformerOrderType,
FillQuoteTransformerRfqOrderInfo,
LimitOrderFields,
RfqOrderFields,
Signature,
} from '@0x/protocol-utils';
import { V4RFQIndicativeQuote } from '@0x/quote-server';
import { MarketOperation } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { NativeOrderWithFillableAmounts, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types';
import { Address, Bytes, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types';
import { NativeOrderWithFillableAmounts } from '../native_orders';
import { QuoteRequestor } from '../../utils/quote_requestor';
import { PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
@@ -163,122 +166,57 @@ export interface BalancerV2PoolInfo {
vault: string;
}
// Internal `fillData` field for `Fill` objects.
export interface FillData {}
// `FillData` for native fills. Represents a single native order
export type NativeRfqOrderFillData = FillQuoteTransformerRfqOrderInfo;
export type NativeLimitOrderFillData = FillQuoteTransformerLimitOrderInfo;
export type NativeFillData = NativeRfqOrderFillData | NativeLimitOrderFillData;
// Represents an individual DEX sample from the sampler contract
export interface DexSample<TFillData extends FillData = FillData> {
export interface DexSample {
source: ERC20BridgeSource;
fillData: TFillData;
encodedFillData: Bytes;
metadata?: any;
input: BigNumber;
output: BigNumber;
}
export interface CurveFillData extends FillData {
fromTokenIdx: number;
toTokenIdx: number;
pool: CurveInfo;
}
export interface BalancerFillData extends FillData {
poolAddress: string;
}
export interface BalancerV2FillData extends FillData {
vault: string;
poolId: string;
}
export interface UniswapV2FillData extends FillData {
tokenAddressPath: string[];
router: string;
}
export interface ShellFillData extends FillData {
poolAddress: string;
}
export interface LiquidityProviderFillData extends FillData {
poolAddress: string;
gasCost: number;
}
export interface BancorFillData extends FillData {
path: string[];
networkAddress: string;
export interface BridgeFillData {
encodedFillData: Bytes;
}
export interface KyberFillData extends FillData {
hint: string;
reserveId: string;
networkProxy: string;
export interface UniswapV2FillData extends BridgeFillData {
tokenAddressPath: Address[];
}
export interface MooniswapFillData extends FillData {
poolAddress: string;
export interface UniswapV3FillData extends BridgeFillData {
encodedPath: Bytes;
}
export interface DODOFillData extends FillData {
poolAddress: string;
isSellBase: boolean;
helperAddress: string;
export interface LiquidityProviderFillData extends BridgeFillData {
poolAddress: Address;
}
export interface GenericRouterFillData extends FillData {
router: string;
export interface CurveFillData extends BridgeFillData {
poolAddress: Address;
exchangeFunctionSelector: Bytes;
fromTokenIdx: number;
toTokenIdx: number;
}
export interface MultiHopFillData extends FillData {
firstHopSource: SourceQuoteOperation;
secondHopSource: SourceQuoteOperation;
intermediateToken: string;
export interface MooniswapFillData extends BridgeFillData {
poolAddress: Address;
}
export interface MakerPsmExtendedData {
isSellOperation: boolean;
takerToken: string;
}
export type MakerPsmFillData = FillData & MakerPsmExtendedData & PsmInfo;
export interface HopInfo {
sourceIndex: BigNumber;
returnData: string;
}
export interface UniswapV3FillData extends FillData {
tokenAddressPath: string[];
router: string;
pathAmounts: Array<{ uniswapPath: string; inputAmount: BigNumber }>;
}
export interface KyberDmmFillData extends UniswapV2FillData {
poolsPath: string[];
}
export interface FinalUniswapV3FillData extends Omit<UniswapV3FillData, 'uniswapPaths'> {
// The uniswap-encoded path that can fll the maximum input amount.
uniswapPath: string;
}
export interface LidoFillData extends FillData {
stEthTokenAddress: string;
takerToken: string;
export interface NativeOrderFillData {
order: LimitOrderFields | RfqOrderFields;
signature: Signature;
}
/**
* Represents a node on a fill path.
*/
export interface Fill<TFillData extends FillData = FillData> {
export interface Fill {
// basic data for every fill
source: ERC20BridgeSource;
// TODO jacob people seem to agree that orderType here is more readable
type: FillQuoteTransformerOrderType; // should correspond with TFillData
fillData: TFillData;
data?: any;
// Unique ID of the original source path this fill belongs to.
// This is generated when the path is generated and is useful to distinguish
// paths that have the same `source` IDs but are distinct (e.g., Curves).
@@ -295,15 +233,37 @@ export interface Fill<TFillData extends FillData = FillData> {
parent?: Fill;
// The index of the fill in the original path.
index: number;
// Cumulative gas cost associated with swapping against this source/pool.
gasCost: number;
}
export interface BridgeFill<TData extends BridgeFillData> extends Fill {
data: TData;
}
export interface GenericBridgeFill extends BridgeFill<BridgeFillData> {}
export interface UniswapV2BridgeFill extends BridgeFill<UniswapV2FillData> {}
export interface UniswapV3BridgeFill extends BridgeFill<UniswapV3FillData> {}
export interface LiquidityProviderBridgeFill extends BridgeFill<LiquidityProviderFillData> {}
export interface CurveBridgeFill extends BridgeFill<CurveFillData> {}
export interface MooniswapBridgeFill extends BridgeFill<MooniswapFillData> {}
export interface NativeOrderFill extends Fill {
data: NativeOrderFillData;
}
/**
* Represents continguous fills on a path that have been merged together.
*/
export interface CollapsedFill<TFillData extends FillData = FillData> {
export interface CollapsedFill {
source: ERC20BridgeSource;
type: FillQuoteTransformerOrderType; // should correspond with TFillData
fillData: TFillData;
data?: any;
// Unique ID of the original source path this fill belongs to.
// This is generated when the path is generated and is useful to distinguish
// paths that have the same `source` IDs but are distinct (e.g., Curves).
@@ -323,56 +283,68 @@ export interface CollapsedFill<TFillData extends FillData = FillData> {
input: BigNumber;
output: BigNumber;
}>;
gasCost: number;
isFallback: boolean;
}
/**
* A `CollapsedFill` wrapping a native order.
*/
export interface NativeCollapsedFill extends CollapsedFill<NativeFillData> {}
export interface CollapsedBridgeFill<TData extends BridgeFillData> extends CollapsedFill {
data: TData;
}
export interface OptimizedMarketOrderBase<TFillData extends FillData = FillData> {
export interface CollapsedGenericBridgeFill extends CollapsedBridgeFill<BridgeFillData> {}
export interface CollapsedUniswapV2BridgeFill extends CollapsedBridgeFill<UniswapV2FillData> {}
export interface CollapsedUniswapV3BridgeFill extends CollapsedBridgeFill<UniswapV3FillData> {}
export interface CollapsedLiquidityProviderBridgeFill extends CollapsedBridgeFill<LiquidityProviderFillData> {}
export interface CollapsedCurveBridgeFill extends CollapsedBridgeFill<CurveFillData> {}
export interface CollapsedMooniswapBridgeFill extends CollapsedBridgeFill<MooniswapFillData> {}
export interface CollapsedNativeOrderFill extends CollapsedFill {
data: NativeOrderFillData;
}
export interface OptimizedOrder {
source: ERC20BridgeSource;
fillData: TFillData;
type: FillQuoteTransformerOrderType; // should correspond with TFillData
makerToken: string;
takerToken: string;
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
type: FillQuoteTransformerOrderType;
inputToken: string;
outputToken: string;
gasCost: number;
inputAmount: BigNumber;
outputAmount: BigNumber;
fills: CollapsedFill[];
isFallback: boolean;
fillData: any;
}
export interface OptimizedMarketBridgeOrder<TFillData extends FillData = FillData>
extends OptimizedMarketOrderBase<TFillData> {
export interface OptimizedBridgeOrder<TFillData extends BridgeFillData> extends OptimizedOrder {
type: FillQuoteTransformerOrderType.Bridge;
fillData: TFillData;
sourcePathId: string;
fillData: TFillData;
}
export interface OptimizedLimitOrder extends OptimizedMarketOrderBase<NativeLimitOrderFillData> {
export interface OptimizedGenericBridgeOrder extends OptimizedBridgeOrder<BridgeFillData> {}
export interface OptimizedUniswapV2BridgeOrder extends OptimizedBridgeOrder<UniswapV2FillData> {}
export interface OptimizedLimitOrder extends OptimizedOrder {
type: FillQuoteTransformerOrderType.Limit;
fillData: NativeLimitOrderFillData;
fillData: Omit<NativeOrderFillData, 'type'>;
}
export interface OptimizedRfqOrder extends OptimizedMarketOrderBase<NativeRfqOrderFillData> {
export interface OptimizedRfqOrder extends OptimizedOrder {
type: FillQuoteTransformerOrderType.Rfq;
fillData: NativeRfqOrderFillData;
fillData: Omit<NativeOrderFillData, 'type'>;
}
/**
* Optimized orders to fill.
*/
export type OptimizedMarketOrder =
| OptimizedMarketBridgeOrder<FillData>
| OptimizedMarketOrderBase<NativeLimitOrderFillData>
| OptimizedMarketOrderBase<NativeRfqOrderFillData>;
export interface GetMarketOrdersRfqOpts extends RfqRequestOpts {
quoteRequestor?: QuoteRequestor;
firmQuoteValidator?: RfqFirmQuoteValidator;
}
export type FeeEstimate = (fillData: FillData) => number | BigNumber;
export type FeeSchedule = Partial<{ [key in ERC20BridgeSource]: FeeEstimate }>;
export type ExchangeProxyOverhead = (sourceFlags: bigint) => BigNumber;
/**
@@ -413,26 +385,6 @@ export interface GetMarketOrdersOpts {
* percentage, no fallback quote will be provided.
*/
maxFallbackSlippage: number;
/**
* Number of samples to take for each DEX quote.
*/
numSamples: number;
/**
* The exponential sampling distribution base.
* A value of 1 will result in evenly spaced samples.
* > 1 will result in more samples at lower sizes.
* < 1 will result in more samples at higher sizes.
* Default: 1.25.
*/
sampleDistributionBase: number;
/**
* Fees for each liquidity source, expressed in gas.
*/
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
@@ -473,17 +425,21 @@ export interface BatchedOperation<TResult> {
handleRevert(callResults: string): TResult;
}
export interface SourceQuoteOperation<TFillData extends FillData = FillData> extends BatchedOperation<BigNumber[]> {
readonly source: ERC20BridgeSource;
fillData: TFillData;
export interface OptimizedHop {
inputToken: Address;
outputToken: Address;
inputAmount: BigNumber;
outputAmount: BigNumber;
sourceFlags: bigint;
orders: OptimizedOrder[];
adjustedCompleteRate: BigNumber;
}
export interface OptimizerResult {
optimizedOrders: OptimizedMarketOrder[];
sourceFlags: bigint;
liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
marketSideLiquidity: MarketSideLiquidity;
adjustedRate: BigNumber;
hops: OptimizedHop[];
// liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
marketSideLiquidity: MarketSideLiquidity;
unoptimizedPath?: CollapsedPath;
takerAmountPerEth: BigNumber;
makerAmountPerEth: BigNumber;
@@ -494,7 +450,7 @@ export interface OptimizerResultWithReport extends OptimizerResult {
priceComparisonsReport?: PriceComparisonsReport;
}
export type MarketDepthSide = Array<Array<DexSample<FillData>>>;
export type MarketDepthSide = Array<Array<DexSample>>;
export interface MarketDepth {
bids: MarketDepthSide;
@@ -503,25 +459,30 @@ export interface MarketDepth {
takerTokenDecimals: number;
}
export interface TokenAmountPerEth {
[tokenAddress: string]: BigNumber;
}
export interface MarketSideLiquidity {
side: MarketOperation;
inputAmount: BigNumber;
inputToken: string;
outputToken: string;
outputAmountPerEth: BigNumber;
inputAmountPerEth: BigNumber;
tokenAmountPerEth: TokenAmountPerEth;
quoteSourceFilters: SourceFilters;
makerTokenDecimals: number;
takerTokenDecimals: number;
quotes: RawQuotes;
quotes: RawHopQuotes[];
isRfqSupported: boolean;
gasPrice: BigNumber;
}
export interface RawQuotes {
export interface RawHopQuotes {
tokenPath: Address[];
inputToken: Address;
outputToken: Address;
nativeOrders: NativeOrderWithFillableAmounts[];
rfqtIndicativeQuotes: V4RFQIndicativeQuote[];
twoHopQuotes: Array<DexSample<MultiHopFillData>>;
dexQuotes: Array<Array<DexSample<FillData>>>;
dexQuotes: DexSample[][];
}
export interface TokenAdjacencyGraph {
@@ -541,7 +502,6 @@ export interface GenerateOptimizedOrdersOpts {
bridgeSlippage?: number;
maxFallbackSlippage?: number;
excludedSources?: ERC20BridgeSource[];
feeSchedule: FeeSchedule;
exchangeProxyOverhead?: ExchangeProxyOverhead;
allowFallback?: boolean;
shouldBatchBridgeOrders?: boolean;

View File

@@ -0,0 +1,31 @@
import { BigNumber } from '@0x/utils';
import {
CommonOrderFields,
FillQuoteTransformerOrderType,
LimitOrderFields,
RfqOrderFields,
Signature,
} from '@0x/protocol-utils';
import { SignedNativeOrder } from '../types';
export interface SignedOrder<T> {
order: T;
type: FillQuoteTransformerOrderType.Limit | FillQuoteTransformerOrderType.Rfq;
signature: Signature;
}
export type NativeOrderWithFillableAmounts = SignedNativeOrder & NativeOrderFillableAmountFields & {
gasCost: number;
};
/**
* fillableMakerAmount: Amount of makerAsset that is fillable
* fillableTakerAmount: Amount of takerAsset that is fillable
* fillableTakerFeeAmount: Amount of takerFee paid to fill fillableTakerAmount
*/
export interface NativeOrderFillableAmountFields {
fillableMakerAmount: BigNumber;
fillableTakerAmount: BigNumber;
fillableTakerFeeAmount: BigNumber;
}

View File

@@ -1,5 +1,6 @@
import { BigNumber } from '@0x/utils';
import * as heartbeats from 'heartbeats';
import fetch from 'axios';
import { constants } from '../constants';
import { SwapQuoterError } from '../types';
@@ -61,7 +62,7 @@ export class ProtocolFeeUtils {
private async _getGasPriceFromGasStationOrThrowAsync(): Promise<BigNumber> {
try {
const res = await fetch(this._ethGasStationUrl);
const gasInfo = await res.json();
const gasInfo = res.data;
// Eth Gas Station result is gwei * 10
// tslint:disable-next-line:custom-no-magic-numbers
const BASE_TEN = 10;

View File

@@ -2,26 +2,22 @@ import { FillQuoteTransformerOrderType, RfqOrderFields, Signature } from '@0x/pr
import { BigNumber } from '@0x/utils';
import _ = require('lodash');
import { MarketOperation, NativeOrderWithFillableAmounts } from '../types';
import { MarketOperation } from '../types';
import {
CollapsedFill,
DexSample,
ERC20BridgeSource,
FillData,
MultiHopFillData,
NativeCollapsedFill,
NativeFillData,
NativeLimitOrderFillData,
NativeRfqOrderFillData,
CollapsedNativeOrderFill,
} from './market_operation_utils/types';
import { NativeOrderWithFillableAmounts } from './native_orders';
import { QuoteRequestor } from './quote_requestor';
export interface QuoteReportEntryBase {
liquiditySource: ERC20BridgeSource;
makerAmount: BigNumber;
takerAmount: BigNumber;
fillData: FillData;
fillData: any;
}
export interface BridgeQuoteReportEntry extends QuoteReportEntryBase {
liquiditySource: Exclude<ERC20BridgeSource, ERC20BridgeSource.Native>;
@@ -34,14 +30,14 @@ export interface MultiHopQuoteReportEntry extends QuoteReportEntryBase {
export interface NativeLimitOrderQuoteReportEntry extends QuoteReportEntryBase {
liquiditySource: ERC20BridgeSource.Native;
fillData: NativeFillData;
fillData: any;
fillableTakerAmount: BigNumber;
isRfqt: false;
}
export interface NativeRfqOrderQuoteReportEntry extends QuoteReportEntryBase {
liquiditySource: ERC20BridgeSource.Native;
fillData: NativeFillData;
fillData: any;
fillableTakerAmount: BigNumber;
isRfqt: true;
nativeOrder: RfqOrderFields;
@@ -73,47 +69,48 @@ export interface PriceComparisonsReport {
export function generateQuoteReport(
marketOperation: MarketOperation,
nativeOrders: NativeOrderWithFillableAmounts[],
liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
// liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
comparisonPrice?: BigNumber | undefined,
quoteRequestor?: QuoteRequestor,
): QuoteReport {
const nativeOrderSourcesConsidered = nativeOrders.map(order =>
nativeOrderToReportEntry(order.type, order as any, order.fillableTakerAmount, comparisonPrice, quoteRequestor),
);
const sourcesConsidered = [...nativeOrderSourcesConsidered.filter(order => order.isRfqt)];
let sourcesDelivered;
if (Array.isArray(liquidityDelivered)) {
// create easy way to look up fillable amounts
const nativeOrderSignaturesToFillableAmounts = _.fromPairs(
nativeOrders.map(o => {
return [_nativeDataToId(o), o.fillableTakerAmount];
}),
);
// map sources delivered
sourcesDelivered = liquidityDelivered.map(collapsedFill => {
if (_isNativeOrderFromCollapsedFill(collapsedFill)) {
return nativeOrderToReportEntry(
collapsedFill.type,
collapsedFill.fillData,
nativeOrderSignaturesToFillableAmounts[_nativeDataToId(collapsedFill.fillData)],
comparisonPrice,
quoteRequestor,
);
} else {
return dexSampleToReportSource(collapsedFill, marketOperation);
}
});
} else {
sourcesDelivered = [
// tslint:disable-next-line: no-unnecessary-type-assertion
multiHopSampleToReportSource(liquidityDelivered as DexSample<MultiHopFillData>, marketOperation),
];
}
return {
sourcesConsidered,
sourcesDelivered,
};
throw new Error(`Not implemented`);
// const nativeOrderSourcesConsidered = nativeOrders.map(order =>
// nativeOrderToReportEntry(order.type, order as any, order.fillableTakerAmount, comparisonPrice, quoteRequestor),
// );
// const sourcesConsidered = [...nativeOrderSourcesConsidered.filter(order => order.isRfqt)];
//
// let sourcesDelivered;
// if (Array.isArray(liquidityDelivered)) {
// // create easy way to look up fillable amounts
// const nativeOrderSignaturesToFillableAmounts = _.fromPairs(
// nativeOrders.map(o => {
// return [_nativeDataToId(o), o.fillableTakerAmount];
// }),
// );
// // map sources delivered
// sourcesDelivered = liquidityDelivered.map(collapsedFill => {
// if (_isNativeOrderFromCollapsedFill(collapsedFill)) {
// return nativeOrderToReportEntry(
// collapsedFill.type,
// collapsedFill.fillData,
// nativeOrderSignaturesToFillableAmounts[_nativeDataToId(collapsedFill.fillData)],
// comparisonPrice,
// quoteRequestor,
// );
// } else {
// return dexSampleToReportSource(collapsedFill, marketOperation);
// }
// });
// } else {
// sourcesDelivered = [
// // tslint:disable-next-line: no-unnecessary-type-assertion
// multiHopSampleToReportSource(liquidityDelivered as DexSample<MultiHopFillData>, marketOperation),
// ];
// }
// return {
// sourcesConsidered,
// sourcesDelivered,
// };
}
function _nativeDataToId(data: { signature: Signature }): string {
@@ -126,31 +123,32 @@ function _nativeDataToId(data: { signature: Signature }): string {
* NOTE: this is used for the QuoteReport and quote price comparison data
*/
export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOperation): BridgeQuoteReportEntry {
const liquiditySource = ds.source;
if (liquiditySource === ERC20BridgeSource.Native) {
throw new Error(`Unexpected liquidity source Native`);
}
// input and output map to different values
// based on the market operation
if (marketOperation === MarketOperation.Buy) {
return {
makerAmount: ds.input,
takerAmount: ds.output,
liquiditySource,
fillData: ds.fillData,
};
} else if (marketOperation === MarketOperation.Sell) {
return {
makerAmount: ds.output,
takerAmount: ds.input,
liquiditySource,
fillData: ds.fillData,
};
} else {
throw new Error(`Unexpected marketOperation ${marketOperation}`);
}
throw new Error(`Not implemented`);
// const liquiditySource = ds.source;
//
// if (liquiditySource === ERC20BridgeSource.Native) {
// throw new Error(`Unexpected liquidity source Native`);
// }
//
// // input and output map to different values
// // based on the market operation
// if (marketOperation === MarketOperation.Buy) {
// return {
// makerAmount: ds.input,
// takerAmount: ds.output,
// liquiditySource,
// fillData: ds.fillData,
// };
// } else if (marketOperation === MarketOperation.Sell) {
// return {
// makerAmount: ds.output,
// takerAmount: ds.input,
// liquiditySource,
// fillData: ds.fillData,
// };
// } else {
// throw new Error(`Unexpected marketOperation ${marketOperation}`);
// }
}
/**
@@ -158,34 +156,35 @@ export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOp
* NOTE: this is used for the QuoteReport and quote price comparison data
*/
export function multiHopSampleToReportSource(
ds: DexSample<MultiHopFillData>,
ds: DexSample,
marketOperation: MarketOperation,
): MultiHopQuoteReportEntry {
const { firstHopSource: firstHop, secondHopSource: secondHop } = ds.fillData;
// input and output map to different values
// based on the market operation
if (marketOperation === MarketOperation.Buy) {
return {
liquiditySource: ERC20BridgeSource.MultiHop,
makerAmount: ds.input,
takerAmount: ds.output,
fillData: ds.fillData,
hopSources: [firstHop.source, secondHop.source],
};
} else if (marketOperation === MarketOperation.Sell) {
return {
liquiditySource: ERC20BridgeSource.MultiHop,
makerAmount: ds.output,
takerAmount: ds.input,
fillData: ds.fillData,
hopSources: [firstHop.source, secondHop.source],
};
} else {
throw new Error(`Unexpected marketOperation ${marketOperation}`);
}
throw new Error(`Not implemented`);
// const { firstHopSource: firstHop, secondHopSource: secondHop } = ds.fillData;
// // input and output map to different values
// // based on the market operation
// if (marketOperation === MarketOperation.Buy) {
// return {
// liquiditySource: ERC20BridgeSource.MultiHop,
// makerAmount: ds.input,
// takerAmount: ds.output,
// fillData: ds.fillData,
// hopSources: [firstHop.source, secondHop.source],
// };
// } else if (marketOperation === MarketOperation.Sell) {
// return {
// liquiditySource: ERC20BridgeSource.MultiHop,
// makerAmount: ds.output,
// takerAmount: ds.input,
// fillData: ds.fillData,
// hopSources: [firstHop.source, secondHop.source],
// };
// } else {
// throw new Error(`Unexpected marketOperation ${marketOperation}`);
// }
}
function _isNativeOrderFromCollapsedFill(cf: CollapsedFill): cf is NativeCollapsedFill {
function _isNativeOrderFromCollapsedFill(cf: CollapsedFill): cf is CollapsedNativeOrderFill {
const { type } = cf;
return type === FillQuoteTransformerOrderType.Limit || type === FillQuoteTransformerOrderType.Rfq;
}
@@ -196,41 +195,42 @@ function _isNativeOrderFromCollapsedFill(cf: CollapsedFill): cf is NativeCollaps
*/
export function nativeOrderToReportEntry(
type: FillQuoteTransformerOrderType,
fillData: NativeLimitOrderFillData | NativeRfqOrderFillData,
fillData: any,
fillableAmount: BigNumber,
comparisonPrice?: BigNumber | undefined,
quoteRequestor?: QuoteRequestor,
): NativeRfqOrderQuoteReportEntry | NativeLimitOrderQuoteReportEntry {
const nativeOrderBase = {
makerAmount: fillData.order.makerAmount,
takerAmount: fillData.order.takerAmount,
fillableTakerAmount: fillableAmount,
};
// if we find this is an rfqt order, label it as such and associate makerUri
const isRfqt = type === FillQuoteTransformerOrderType.Rfq;
const rfqtMakerUri =
isRfqt && quoteRequestor ? quoteRequestor.getMakerUriForSignature(fillData.signature) : undefined;
if (isRfqt) {
const nativeOrder = fillData.order as RfqOrderFields;
// tslint:disable-next-line: no-object-literal-type-assertion
return {
liquiditySource: ERC20BridgeSource.Native,
...nativeOrderBase,
isRfqt: true,
makerUri: rfqtMakerUri || '',
...(comparisonPrice ? { comparisonPrice: comparisonPrice.toNumber() } : {}),
nativeOrder,
fillData,
};
} else {
// tslint:disable-next-line: no-object-literal-type-assertion
return {
liquiditySource: ERC20BridgeSource.Native,
...nativeOrderBase,
isRfqt: false,
fillData,
};
}
throw new Error(`Not implemented`);
// const nativeOrderBase = {
// makerAmount: fillData.order.makerAmount,
// takerAmount: fillData.order.takerAmount,
// fillableTakerAmount: fillableAmount,
// };
//
// // if we find this is an rfqt order, label it as such and associate makerUri
// const isRfqt = type === FillQuoteTransformerOrderType.Rfq;
// const rfqtMakerUri =
// isRfqt && quoteRequestor ? quoteRequestor.getMakerUriForSignature(fillData.signature) : undefined;
//
// if (isRfqt) {
// const nativeOrder = fillData.order as RfqOrderFields;
// // tslint:disable-next-line: no-object-literal-type-assertion
// return {
// liquiditySource: ERC20BridgeSource.Native,
// ...nativeOrderBase,
// isRfqt: true,
// makerUri: rfqtMakerUri || '',
// ...(comparisonPrice ? { comparisonPrice: comparisonPrice.toNumber() } : {}),
// nativeOrder,
// fillData,
// };
// } else {
// // tslint:disable-next-line: no-object-literal-type-assertion
// return {
// liquiditySource: ERC20BridgeSource.Native,
// ...nativeOrderBase,
// isRfqt: false,
// fillData,
// };
// }
}

View File

@@ -21,7 +21,7 @@ import {
RfqmRequestOptions,
RfqPairType,
RfqRequestOpts,
SignedNativeOrder,
SignedRfqOrder,
TypedMakerUrl,
} from '../types';
@@ -282,7 +282,7 @@ export class QuoteRequestor {
private readonly _altRfqCreds?: { altRfqApiKey: string; altRfqProfile: string },
private readonly _warningLogger: LogFunction = constants.DEFAULT_WARNING_LOGGER,
private readonly _infoLogger: LogFunction = constants.DEFAULT_INFO_LOGGER,
private readonly _expiryBufferMs: number = constants.DEFAULT_SWAP_QUOTER_OPTS.expiryBufferMs,
private readonly _expiryBufferMs: number = 120e3,
private readonly _metrics?: MetricsProxy,
) {
rfqMakerBlacklist.infoLogger = this._infoLogger;
@@ -295,7 +295,7 @@ export class QuoteRequestor {
marketOperation: MarketOperation,
comparisonPrice: BigNumber | undefined,
options: RfqmRequestOptions,
): Promise<SignedNativeOrder[]> {
): Promise<SignedRfqOrder[]> {
const _opts: RfqRequestOpts = {
...constants.DEFAULT_RFQT_REQUEST_OPTS,
...options,
@@ -319,7 +319,7 @@ export class QuoteRequestor {
marketOperation: MarketOperation,
comparisonPrice: BigNumber | undefined,
options: RfqRequestOpts,
): Promise<SignedNativeOrder[]> {
): Promise<SignedRfqOrder[]> {
const _opts: RfqRequestOpts = { ...constants.DEFAULT_RFQT_REQUEST_OPTS, ...options };
if (!_opts.txOrigin || [undefined, '', '0x', NULL_ADDRESS].includes(_opts.txOrigin)) {
throw new Error('RFQ-T firm quotes require the presence of a tx origin');
@@ -642,7 +642,7 @@ export class QuoteRequestor {
comparisonPrice: BigNumber | undefined,
options: RfqRequestOpts,
assetOfferings: RfqMakerAssetOfferings,
): Promise<SignedNativeOrder[]> {
): Promise<SignedRfqOrder[]> {
const quotesRaw = await this._getQuotesAsync<V4RFQFirmQuote>(
makerToken,
takerToken,
@@ -719,7 +719,7 @@ export class QuoteRequestor {
// Save the maker URI for later and return just the order
const rfqQuotes = validQuotes.map(result => {
const { signature, ...rest } = result.response;
const order: SignedNativeOrder = {
const order: SignedRfqOrder = {
order: {
...rest,
makerAmount: new BigNumber(result.response.makerAmount),

View File

@@ -1,11 +1,8 @@
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
import { FillQuoteTransformerOrderType, LimitOrderFields } from '@0x/protocol-utils';
import { BigNumber } from '@0x/utils';
import { constants } from '../constants';
import { MarketOperation } from '../types';
import { FeeSchedule, NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types';
import { getNativeAdjustedTakerFeeAmount } from './utils';
import { MarketOperation, SwapQuoteLimitOrder, SwapQuoteOrder } from '../types';
const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants;
const { ROUND_DOWN, ROUND_UP } = BigNumber;
@@ -64,7 +61,7 @@ const EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT = {
};
export interface QuoteFillInfo {
orders: OptimizedMarketOrder[];
orders: SwapQuoteOrder[];
fillAmount: BigNumber;
gasPrice: BigNumber;
side: MarketOperation;
@@ -72,19 +69,17 @@ 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,
};
export interface QuoteFillOrderCall {
order: OptimizedMarketOrder;
order: SwapQuoteOrder;
// Total input amount defined in the order.
totalOrderInput: BigNumber;
// Total output amount defined in the order.
@@ -108,7 +103,6 @@ export function simulateBestCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult
createBestCaseFillOrderCalls(quoteInfo),
quoteInfo.fillAmount,
protocolFeePerFillOrder,
opts.gasSchedule,
);
return fromIntermediateQuoteFillResult(result, quoteInfo);
}
@@ -122,9 +116,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 +134,6 @@ export function fillQuoteOrders(
fillOrders: QuoteFillOrderCall[],
inputAmount: BigNumber,
protocolFeePerFillOrder: BigNumber,
gasSchedule: FeeSchedule,
): IntermediateQuoteFillResult {
const result: IntermediateQuoteFillResult = {
...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT,
@@ -151,39 +144,27 @@ export function fillQuoteOrders(
if (remainingInput.lte(0)) {
break;
}
for (const fill of fo.order.fills) {
if (remainingInput.lte(0)) {
break;
}
const { source, fillData } = fill;
const gas = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(fillData);
result.gas += new BigNumber(gas).toNumber();
result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT;
const { source } = fo.order;
result.gas += fo.order.gasCost;
result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT;
// Actual rates are rarely linear, so fill subfills individually to
// get a better approximation of fill size.
for (const subFill of fill.subFills) {
if (remainingInput.lte(0)) {
break;
}
const filledInput = solveForInputFillAmount(
remainingInput,
subFill.input,
fo.totalOrderInput,
fo.totalOrderInputFee,
);
const filledOutput = subFill.output.times(filledInput.div(subFill.input));
const filledInputFee = filledInput.div(fo.totalOrderInput).times(fo.totalOrderInputFee);
const filledOutputFee = filledOutput.div(fo.totalOrderOutput).times(fo.totalOrderOutputFee);
const filledInput = solveForInputFillAmount(
remainingInput,
fo.totalOrderInput,
fo.totalOrderInput,
fo.totalOrderInputFee,
);
const filledOutput = fo.totalOrderOutput.times(filledInput.div(fo.totalOrderInput));
const filledInputFee = filledInput.div(fo.totalOrderInput).times(fo.totalOrderInputFee);
const filledOutputFee = filledOutput.div(fo.totalOrderOutput).times(fo.totalOrderOutputFee);
result.inputBySource[source] = result.inputBySource[source].plus(filledInput);
result.input = result.input.plus(filledInput);
result.output = result.output.plus(filledOutput);
result.inputFee = result.inputFee.plus(filledInputFee);
result.outputFee = result.outputFee.plus(filledOutputFee);
remainingInput = remainingInput.minus(filledInput.plus(filledInputFee));
result.inputBySource[source] = result.inputBySource[source].plus(filledInput);
result.input = result.input.plus(filledInput);
result.output = result.output.plus(filledOutput);
result.inputFee = result.inputFee.plus(filledInputFee);
result.outputFee = result.outputFee.plus(filledOutputFee);
remainingInput = remainingInput.minus(filledInput.plus(filledInputFee));
}
}
// NOTE: V4 Limit orders have Protocol fees
const protocolFee = hasProtocolFee(fo.order) ? protocolFeePerFillOrder : ZERO_AMOUNT;
result.protocolFee = result.protocolFee.plus(protocolFee);
@@ -191,7 +172,7 @@ export function fillQuoteOrders(
return result;
}
function hasProtocolFee(o: OptimizedMarketOrder): boolean {
function hasProtocolFee(o: SwapQuoteOrder): boolean {
return o.type === FillQuoteTransformerOrderType.Limit;
}
@@ -234,13 +215,7 @@ function createBestCaseFillOrderCalls(quoteInfo: QuoteFillInfo): QuoteFillOrderC
? {
totalOrderInput: o.takerAmount,
totalOrderOutput: o.makerAmount,
totalOrderInputFee:
o.type === FillQuoteTransformerOrderType.Limit
? getNativeAdjustedTakerFeeAmount(
(o.fillData as NativeLimitOrderFillData).order,
o.takerAmount,
)
: ZERO_AMOUNT,
totalOrderInputFee: ZERO_AMOUNT, // Limit orders not supported atm
totalOrderOutputFee: ZERO_AMOUNT, // makerToken fees are not supported in v4 (sell output)
}
: // Buy
@@ -248,13 +223,7 @@ function createBestCaseFillOrderCalls(quoteInfo: QuoteFillInfo): QuoteFillOrderC
totalOrderInput: o.makerAmount,
totalOrderOutput: o.takerAmount,
totalOrderInputFee: ZERO_AMOUNT, // makerToken fees are not supported in v4 (buy input)
totalOrderOutputFee:
o.type === FillQuoteTransformerOrderType.Limit
? getNativeAdjustedTakerFeeAmount(
(o.fillData as NativeLimitOrderFillData).order,
o.takerAmount,
)
: ZERO_AMOUNT,
totalOrderOutputFee: ZERO_AMOUNT, // Limit orders not supported atm
}),
}));
}
@@ -314,11 +283,10 @@ function fromIntermediateQuoteFillResult(ir: IntermediateQuoteFillResult, quoteI
};
}
function getTotalGasUsedByFills(fills: OptimizedMarketOrder[], gasSchedule: FeeSchedule): number {
function getTotalGasUsedByFills(fills: SwapQuoteOrder[]): 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 += f.gasCost;
}
return gasUsed;
}

View File

@@ -23,7 +23,7 @@ export const rfqtMocker = {
afterResponseCallback: () => Promise<void>,
axiosClient: AxiosInstance = axios,
): Promise<void> => {
const mockedAxios = new AxiosMockAdapter(axiosClient);
const mockedAxios = new AxiosMockAdapter(axiosClient as any); // TODO (lawrence): why ts?
try {
// Mock out RFQT responses
for (const mockedResponse of mockedResponses) {

View File

@@ -1,100 +1,19 @@
import { CommonOrderFields, FillQuoteTransformerOrderType, LimitOrderFields } from '@0x/protocol-utils';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { ChainId } from '@0x/contract-addresses';
import { constants } from '../constants';
import { NativeOrderFillableAmountFields, SignedNativeOrder } from '../types';
import { ZERO_AMOUNT } from './market_operation_utils/constants';
// tslint:disable: no-unnecessary-type-assertion completed-docs
export function numberPercentageToEtherTokenAmountPercentage(percentage: number): BigNumber {
return Web3Wrapper.toBaseUnitAmount(constants.ONE_AMOUNT, constants.ETHER_TOKEN_DECIMALS).multipliedBy(percentage);
}
export function getAdjustedTakerAmountFromFees<T extends LimitOrderFields>(order: T): BigNumber {
return order.takerAmount.plus(order.takerTokenFeeAmount);
}
/**
* Given an amount of taker asset, calculate the the amount of maker asset
* @param order The order
* @param makerFillAmount the amount of taker asset
*/
export function getNativeAdjustedMakerFillAmount(order: CommonOrderFields, takerFillAmount: BigNumber): BigNumber {
// Round down because exchange rate favors Maker
const makerFillAmount = takerFillAmount
.multipliedBy(order.makerAmount)
.div(order.takerAmount)
.integerValue(BigNumber.ROUND_FLOOR);
return makerFillAmount;
}
/**
* Given an amount of maker asset, calculate the equivalent amount in taker asset
* @param order The order
* @param makerFillAmount the amount of maker asset
*/
export function getNativeAdjustedTakerFillAmount(order: CommonOrderFields, makerFillAmount: BigNumber): BigNumber {
// Round up because exchange rate favors Maker
const takerFillAmount = makerFillAmount
.multipliedBy(order.takerAmount)
.div(order.makerAmount)
.integerValue(BigNumber.ROUND_CEIL);
return takerFillAmount;
}
/**
* Given an amount of taker asset, calculate the fee amount required for the taker
* @param order The order
* @param takerFillAmount the amount of taker asset
*/
export function getNativeAdjustedTakerFeeAmount(order: LimitOrderFields, takerFillAmount: BigNumber): BigNumber {
// Round down because Taker fee rate favors Taker
const takerFeeAmount = takerFillAmount
.multipliedBy(order.takerTokenFeeAmount)
.div(order.takerAmount)
.integerValue(BigNumber.ROUND_FLOOR);
return takerFeeAmount;
}
const EMPTY_FILLABLE_AMOUNTS: NativeOrderFillableAmountFields = {
fillableMakerAmount: ZERO_AMOUNT,
fillableTakerAmount: ZERO_AMOUNT,
fillableTakerFeeAmount: ZERO_AMOUNT,
};
export function getNativeAdjustedFillableAmountsFromTakerAmount(
order: SignedNativeOrder,
takerFillableAmount: BigNumber,
): NativeOrderFillableAmountFields {
if (takerFillableAmount.isZero()) {
return EMPTY_FILLABLE_AMOUNTS;
}
// TODO(kimpers): Consolidate this implementation with the one in @0x/token-metadata
export function valueByChainId<T>(rest: Partial<{ [key in ChainId]: T }>, defaultValue: T): { [key in ChainId]: T } {
// TODO I don't like this but iterating through enums is weird
return {
fillableTakerAmount: takerFillableAmount,
fillableMakerAmount: getNativeAdjustedMakerFillAmount(order.order, takerFillableAmount),
fillableTakerFeeAmount:
order.type === FillQuoteTransformerOrderType.Limit
? getNativeAdjustedTakerFeeAmount(order.order as LimitOrderFields, takerFillableAmount)
: ZERO_AMOUNT,
};
}
export function getNativeAdjustedFillableAmountsFromMakerAmount(
order: SignedNativeOrder,
makerFillableAmount: BigNumber,
): NativeOrderFillableAmountFields {
if (makerFillableAmount.isZero()) {
return EMPTY_FILLABLE_AMOUNTS;
}
const takerFillableAmount = getNativeAdjustedTakerFillAmount(order.order, makerFillableAmount);
return {
fillableMakerAmount: makerFillableAmount,
fillableTakerAmount: takerFillableAmount,
fillableTakerFeeAmount:
order.type === FillQuoteTransformerOrderType.Limit
? getNativeAdjustedTakerFeeAmount(order.order as LimitOrderFields, takerFillableAmount)
: ZERO_AMOUNT,
[ChainId.Mainnet]: defaultValue,
[ChainId.Ropsten]: defaultValue,
[ChainId.Rinkeby]: defaultValue,
[ChainId.Kovan]: defaultValue,
[ChainId.Ganache]: defaultValue,
[ChainId.BSC]: defaultValue,
[ChainId.Polygon]: defaultValue,
[ChainId.PolygonMumbai]: defaultValue,
[ChainId.Avalanche]: defaultValue,
[ChainId.Fantom]: defaultValue,
...(rest || {}),
};
}

View File

@@ -5,87 +5,9 @@
*/
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 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 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 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';
import * as KyberSampler from '../test/generated-artifacts/KyberSampler.json';
import * as LidoSampler from '../test/generated-artifacts/LidoSampler.json';
import * as LiquidityProviderSampler from '../test/generated-artifacts/LiquidityProviderSampler.json';
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 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';
import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json';
import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json';
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,
DODOSampler: DODOSampler as ContractArtifact,
DODOV2Sampler: DODOV2Sampler as ContractArtifact,
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
FakeTaker: FakeTaker as ContractArtifact,
KyberDmmSampler: KyberDmmSampler as ContractArtifact,
KyberSampler: KyberSampler as ContractArtifact,
LidoSampler: LidoSampler as ContractArtifact,
LiquidityProviderSampler: LiquidityProviderSampler as ContractArtifact,
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,
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,
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,
TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact,
TestNativeOrderSampler: TestNativeOrderSampler as ContractArtifact,
};

View File

@@ -3,44 +3,5 @@
* 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/d_o_d_o_sampler';
export * from '../test/generated-wrappers/d_o_d_o_v2_sampler';
export * from '../test/generated-wrappers/dummy_liquidity_provider';
export * from '../test/generated-wrappers/erc20_bridge_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/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';
export * from '../test/generated-wrappers/kyber_sampler';
export * from '../test/generated-wrappers/lido_sampler';
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/test_erc20_bridge_sampler';
export * from '../test/generated-wrappers/test_native_order_sampler';
export * from '../test/generated-wrappers/two_hop_sampler';
export * from '../test/generated-wrappers/uniswap_sampler';
export * from '../test/generated-wrappers/uniswap_v2_sampler';
export * from '../test/generated-wrappers/uniswap_v3_sampler';
export * from '../test/generated-wrappers/utility_sampler';

View File

@@ -1,51 +1,12 @@
{
"extends": "../../tsconfig",
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true, "lib": ["es2019"] },
"include": ["./src/**/*", "./generated-wrappers/**/*"],
"files": [
"generated-artifacts/BalanceChecker.json",
"generated-artifacts/ERC20BridgeSampler.json",
"generated-artifacts/FakeTaker.json",
"test/generated-artifacts/ApproximateBuys.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/DODOSampler.json",
"test/generated-artifacts/DODOV2Sampler.json",
"test/generated-artifacts/DummyLiquidityProvider.json",
"test/generated-artifacts/ERC20BridgeSampler.json",
"test/generated-artifacts/FakeTaker.json",
"test/generated-artifacts/IBalancer.json",
"test/generated-artifacts/IBancor.json",
"test/generated-artifacts/ICurve.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",
"test/generated-artifacts/KyberSampler.json",
"test/generated-artifacts/LidoSampler.json",
"test/generated-artifacts/LiquidityProviderSampler.json",
"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/TestERC20BridgeSampler.json",
"test/generated-artifacts/TestNativeOrderSampler.json",
"test/generated-artifacts/TwoHopSampler.json",
"test/generated-artifacts/UniswapSampler.json",
"test/generated-artifacts/UniswapV2Sampler.json",
"test/generated-artifacts/UniswapV3Sampler.json",
"test/generated-artifacts/UtilitySampler.json"
"test/generated-artifacts/FakeTaker.json"
]
}

View File

@@ -2,7 +2,9 @@
"extends": ["@0x/tslint-config"],
"rules": {
"max-file-line-count": false,
"binary-expression-operand-order": false
"binary-expression-operand-order": false,
"no-bitwise": false,
"completed-docs": false
},
"linterOptions": {
"exclude": ["src/artifacts.ts", "test/artifacts.ts"]

View File

@@ -2607,6 +2607,16 @@
dependencies:
"@types/node" ">= 8"
"@open-rpc/client-js@^1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@open-rpc/client-js/-/client-js-1.7.1.tgz#763d75c046a40f57428b861e16a9a69aaa630cb1"
integrity sha512-DycSYZUGSUwFl+k9T8wLBSGA8f2hYkvS5A9AB94tBOuU8QlP468NS5ZtAxy72dF4g2WW0genwNJdfeFnHnaxXQ==
dependencies:
isomorphic-fetch "^3.0.0"
isomorphic-ws "^4.0.1"
strict-event-emitter-types "^2.0.0"
ws "^7.0.0"
"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@@ -3263,18 +3273,21 @@ aws4@^1.8.0:
version "1.10.1"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428"
axios-mock-adapter@^1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.19.0.tgz#9d72e321a6c5418e1eff067aa99761a86c5188a4"
axios-mock-adapter@^1.20.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.20.0.tgz#21f5b4b625306f43e8c05673616719da86e20dcb"
integrity sha512-shZRhTjLP0WWdcvHKf3rH3iW9deb3UdKbdnKUoHmmsnBhVXN3sjPJM6ZvQ2r/ywgvBVQrMnjrSyQab60G1sr2w==
dependencies:
fast-deep-equal "^3.1.3"
is-buffer "^2.0.3"
is-blob "^2.1.0"
is-buffer "^2.0.5"
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
axios@^0.24.0:
version "0.24.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
dependencies:
follow-redirects "^1.10.0"
follow-redirects "^1.14.4"
babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
version "6.26.0"
@@ -6608,9 +6621,10 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@^1.10.0:
version "1.13.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
follow-redirects@^1.14.4:
version "1.14.5"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381"
integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==
for-each@~0.3.3:
version "0.3.3"
@@ -7617,11 +7631,21 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-blob@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-blob/-/is-blob-2.1.0.tgz#e36cd82c90653f1e1b930f11baf9c64216a05385"
integrity sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==
is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
is-buffer@^2.0.3, is-buffer@~2.0.3:
is-buffer@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
is-buffer@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623"
@@ -7969,6 +7993,11 @@ isomorphic-fetch@^3.0.0:
node-fetch "^2.6.1"
whatwg-fetch "^3.4.1"
isomorphic-ws@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==
isstream@0.1.x, isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -11657,6 +11686,11 @@ stream-to-pull-stream@^1.7.1:
looper "^3.0.0"
pull-stream "^3.2.3"
strict-event-emitter-types@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz#05e15549cb4da1694478a53543e4e2f4abcf277f"
integrity sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==
strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
@@ -13565,6 +13599,11 @@ ws@^5.1.1:
dependencies:
async-limiter "~1.0.0"
ws@^7.0.0:
version "7.5.5"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881"
integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==
wsrun@^5.2.4:
version "5.2.4"
resolved "https://registry.yarnpkg.com/wsrun/-/wsrun-5.2.4.tgz#6eb6c3ccd3327721a8df073a5e3578fb0dea494e"