Compare commits
8 Commits
developmen
...
feat/as/sa
Author | SHA1 | Date | |
---|---|---|---|
|
da240653f4 | ||
|
22ec626870 | ||
|
2f60eb1c79 | ||
|
313420473a | ||
|
89a9424ae1 | ||
|
eabca7a2ee | ||
|
a5912c293e | ||
|
83cae575fa |
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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];
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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];
|
||||
}
|
||||
|
||||
}
|
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IMultiBridge.sol";
|
||||
|
||||
|
||||
contract MultiBridgeSampler {
|
||||
|
||||
/// @dev Default gas limit for multibridge calls.
|
||||
uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k
|
||||
|
||||
/// @dev Sample sell quotes from MultiBridge.
|
||||
/// @param multibridge Address of the MultiBridge contract.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param intermediateToken The address of the intermediate token to
|
||||
/// use in an indirect route.
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromMultiBridge(
|
||||
address multibridge,
|
||||
address takerToken,
|
||||
address intermediateToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
// If no address provided, return all zeros.
|
||||
if (multibridge == address(0)) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
multibridge.staticcall.gas(DEFAULT_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IMultiBridge(0).getSellQuote.selector,
|
||||
takerToken,
|
||||
intermediateToken,
|
||||
makerToken,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
// Exit early if the amount is too high for the source to serve
|
||||
if (buyAmount == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,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);
|
||||
}
|
||||
}
|
@@ -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");
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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];
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IBalancer {
|
||||
function isBound(address t) external view returns (bool);
|
||||
function getDenormalizedWeight(address token) external view returns (uint256);
|
||||
function getBalance(address token) external view returns (uint256);
|
||||
function getSwapFee() external view returns (uint256);
|
||||
function calcOutGivenIn(
|
||||
uint256 tokenBalanceIn,
|
||||
uint256 tokenWeightIn,
|
||||
uint256 tokenBalanceOut,
|
||||
uint256 tokenWeightOut,
|
||||
uint256 tokenAmountIn,
|
||||
uint256 swapFee
|
||||
) external pure returns (uint256 tokenAmountOut);
|
||||
function calcInGivenOut(
|
||||
uint256 tokenBalanceIn,
|
||||
uint256 tokenWeightIn,
|
||||
uint256 tokenBalanceOut,
|
||||
uint256 tokenWeightOut,
|
||||
uint256 tokenAmountOut,
|
||||
uint256 swapFee
|
||||
) external pure returns (uint256 tokenAmountIn);
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IBancor {}
|
||||
|
||||
interface IBancorNetwork {
|
||||
function conversionPath(address _sourceToken, address _targetToken) external view returns (address[] memory);
|
||||
function rateByPath(address[] memory _path, uint256 _amount) external view returns (uint256);
|
||||
}
|
||||
|
||||
interface IBancorRegistry {
|
||||
function getAddress(bytes32 _contractName) external view returns (address);
|
||||
function BANCOR_NETWORK() external view returns (bytes32);
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
// solhint-disable func-name-mixedcase
|
||||
interface ICurve {
|
||||
|
||||
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
|
||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param sellAmount The amount of token being bought.
|
||||
/// @param minBuyAmount The minimum buy amount of the token being bought.
|
||||
function exchange_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount
|
||||
)
|
||||
external;
|
||||
|
||||
/// @dev Get the amount of `toToken` by selling `sellAmount` of `fromToken`
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param sellAmount The amount of token being bought.
|
||||
function get_dy_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 sellAmount
|
||||
)
|
||||
external
|
||||
returns (uint256 dy);
|
||||
|
||||
/// @dev Get the amount of `fromToken` by buying `buyAmount` of `toToken`
|
||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param buyAmount The amount of token being bought.
|
||||
function get_dx_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 buyAmount
|
||||
)
|
||||
external
|
||||
returns (uint256 dx);
|
||||
|
||||
/// @dev Get the underlying token address from the token index
|
||||
/// @param i The token index.
|
||||
function underlying_coins(
|
||||
int128 i
|
||||
)
|
||||
external
|
||||
returns (address tokenAddress);
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IMStable {
|
||||
|
||||
function getSwapOutput(
|
||||
address _input,
|
||||
address _output,
|
||||
uint256 _quantity
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 swapOutput);
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IMooniswapRegistry {
|
||||
|
||||
function pools(address token1, address token2) external view returns(address);
|
||||
}
|
||||
|
||||
interface IMooniswap {
|
||||
|
||||
function getReturn(
|
||||
address fromToken,
|
||||
address destToken,
|
||||
uint256 amount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns(uint256 returnAmount);
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IMultiBridge {
|
||||
|
||||
/// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
|
||||
/// @param tokenAddress The address of the ERC20 token to transfer.
|
||||
/// @param from Address to transfer asset from.
|
||||
/// @param to Address to transfer asset to.
|
||||
/// @param amount Amount of asset to transfer.
|
||||
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
|
||||
/// @return success The magic bytes `0xdc1600f3` if successful.
|
||||
function bridgeTransferFrom(
|
||||
address tokenAddress,
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata bridgeData
|
||||
)
|
||||
external
|
||||
returns (bytes4 success);
|
||||
|
||||
/// @dev Quotes the amount of `makerToken` that would be obtained by
|
||||
/// selling `sellAmount` of `takerToken`.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param intermediateToken The address of the intermediate token to
|
||||
/// use in an indirect route.
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param sellAmount Amount of `takerToken` to sell.
|
||||
/// @return makerTokenAmount Amount of `makerToken` that would be obtained.
|
||||
function getSellQuote(
|
||||
address takerToken,
|
||||
address intermediateToken,
|
||||
address makerToken,
|
||||
uint256 sellAmount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 makerTokenAmount);
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface IShell {
|
||||
|
||||
function viewOriginSwap (
|
||||
address from,
|
||||
address to,
|
||||
uint256 fromAmount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 toAmount);
|
||||
|
||||
function viewTargetSwap (
|
||||
address from,
|
||||
address to,
|
||||
uint256 toAmount
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 fromAmount);
|
||||
}
|
||||
|
@@ -1,45 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
|
||||
|
||||
interface ISmoothy {
|
||||
|
||||
function getBalance (
|
||||
uint256 tid
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 balance);
|
||||
|
||||
function _yBalances (
|
||||
uint256 tid
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 balance);
|
||||
|
||||
function getTokenStats (
|
||||
uint256 tid
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 softWeight, uint256 hardWeight, uint256 balance, uint256 decimals);
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
@@ -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",
|
||||
|
@@ -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,
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
@@ -348,7 +366,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
if (this.chainId === ChainId.Mainnet && isMultiplexMultiHopFillCompatible(quote, optsWithDefaults)) {
|
||||
return {
|
||||
calldataHexString: this._encodeMultiplexMultiHopFillCalldata(
|
||||
{ ...quote, orders: slippedOrders },
|
||||
quote.hops,
|
||||
optsWithDefaults,
|
||||
),
|
||||
ethAmount,
|
||||
@@ -371,47 +389,28 @@ 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 quote.hops.entries()) {
|
||||
let fillAmount = !isBuyQuote(quote)
|
||||
? shouldSellEntireBalance ? MAX_UINT256 : hop.takerAmount
|
||||
: hop.makerAmount;
|
||||
let side = !isBuyQuote(quote) ? FillQuoteTransformerSide.Sell : FillQuoteTransformerSide.Buy;
|
||||
if (quote.hops.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,
|
||||
}),
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
if (isToETH) {
|
||||
@@ -475,10 +474,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 +514,109 @@ 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,
|
||||
sellAmount: hop.maxTakerAmount,
|
||||
data: multiplexBatchSellEncoder.encode(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 +637,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 +647,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 +694,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));
|
||||
}
|
||||
|
@@ -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' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
@@ -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');
|
||||
|
@@ -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);
|
||||
|
@@ -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,7 @@ function createSwapQuote(
|
||||
makerToken,
|
||||
takerToken,
|
||||
gasPrice,
|
||||
orders: optimizedOrders,
|
||||
orders: hops.map(h => h.orders).flat(1),
|
||||
bestCaseQuoteInfo,
|
||||
worstCaseQuoteInfo,
|
||||
sourceBreakdown,
|
||||
@@ -532,116 +491,216 @@ 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) {
|
||||
tokenPath.push(hop.takerToken);
|
||||
}
|
||||
if (i === tokenPath.length - 1) {
|
||||
tokenPath.push(hop.makerToken);
|
||||
}
|
||||
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(bestCaseFillResults);
|
||||
const combinedWorstCaseFillResult = combineQuoteFillResults(worstCaseFillResults);
|
||||
const sourceBreakdown = getSwapQuoteOrdersBreakdown(side, tokenPath, bestCaseFillResults);
|
||||
return {
|
||||
sourceBreakdown,
|
||||
bestCaseQuoteInfo: fillResultsToQuoteInfo(combinedBestCaseFillResult),
|
||||
worstCaseQuoteInfo: fillResultsToQuoteInfo(combinedWorstCaseFillResult),
|
||||
};
|
||||
}
|
||||
|
||||
function combineQuoteFillResults(fillResults: QuoteFillResult[]): QuoteFillResult {
|
||||
if (fillResults.length === 0) {
|
||||
throw new Error(`Empty fillResults array`);
|
||||
}
|
||||
const lastResult = fillResults[fillResults.length - 1];
|
||||
const r = {
|
||||
...fillResults[0],
|
||||
makerAssetAmount: lastResult.makerAssetAmount,
|
||||
totalMakerAssetAmount: lastResult.totalMakerAssetAmount,
|
||||
};
|
||||
for (const fr of fillResults.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: hopBreakdowns,
|
||||
},
|
||||
};
|
||||
}
|
||||
return breakdown;
|
||||
}
|
||||
|
||||
|
@@ -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,7 +48,9 @@ export interface SignedOrder<T> {
|
||||
signature: Signature;
|
||||
}
|
||||
|
||||
export type SignedNativeOrder = SignedOrder<LimitOrderFields> | SignedOrder<RfqOrderFields>;
|
||||
export type SignedRfqOrder = SignedOrder<RfqOrderFields>;
|
||||
export type SignedLimitOrder = SignedOrder<LimitOrderFields>;
|
||||
export type SignedNativeOrder = SignedLimitOrder | SignedRfqOrder;
|
||||
export type NativeOrderWithFillableAmounts = SignedNativeOrder & NativeOrderFillableAmountFields;
|
||||
|
||||
/**
|
||||
@@ -166,19 +179,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 +286,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 +394,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 +479,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',
|
||||
|
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
@@ -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));
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||
|
||||
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
|
||||
import { DexSample, Fill } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||
|
||||
@@ -18,12 +18,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 +32,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,63 +92,67 @@ export function nativeOrdersToFills(
|
||||
targetInput: BigNumber = POSITIVE_INF,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
gasPrice: BigNumber,
|
||||
): Fill[] {
|
||||
const sourcePathId = hexUtils.random();
|
||||
// Create a single path from all orders.
|
||||
let fills: Array<Fill & { 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 outputPenalty = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
// targetInput can be less than the order size
|
||||
// whilst the penalty is constant, it affects the adjusted output
|
||||
// only up until the target has been exhausted.
|
||||
// A large order and an order at the exact target should be penalized
|
||||
// the same.
|
||||
const clippedInput = BigNumber.min(targetInput, input);
|
||||
// scale the clipped output inline with the input
|
||||
const clippedOutput = clippedInput.dividedBy(input).times(output);
|
||||
const adjustedOutput =
|
||||
side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty);
|
||||
const adjustedRate =
|
||||
side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput);
|
||||
// Skip orders with rates that are <= 0.
|
||||
if (adjustedRate.lte(0)) {
|
||||
continue;
|
||||
}
|
||||
fills.push({
|
||||
sourcePathId,
|
||||
adjustedRate,
|
||||
adjustedOutput,
|
||||
input: clippedInput,
|
||||
output: clippedOutput,
|
||||
flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
||||
index: 0, // TBD
|
||||
parent: undefined, // TBD
|
||||
source: ERC20BridgeSource.Native,
|
||||
type,
|
||||
fillData: { ...o },
|
||||
});
|
||||
if (orders.length === 0) {
|
||||
return [];
|
||||
}
|
||||
// Sort by descending adjusted rate.
|
||||
fills = fills.sort((a, b) => b.adjustedRate.comparedTo(a.adjustedRate));
|
||||
// Re-index fills.
|
||||
for (let i = 0; i < fills.length; ++i) {
|
||||
fills[i].parent = i === 0 ? undefined : fills[i - 1];
|
||||
fills[i].index = i;
|
||||
}
|
||||
return fills;
|
||||
throw new Error(`Not implemented`);
|
||||
// const sourcePathId = hexUtils.random();
|
||||
// // Create a single path from all orders.
|
||||
// let fills: Array<Fill & { 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 outputPenalty = ethToOutputAmount({
|
||||
// input,
|
||||
// output,
|
||||
// inputAmountPerEth,
|
||||
// outputAmountPerEth,
|
||||
// ethAmount: fee,
|
||||
// });
|
||||
// // targetInput can be less than the order size
|
||||
// // whilst the penalty is constant, it affects the adjusted output
|
||||
// // only up until the target has been exhausted.
|
||||
// // A large order and an order at the exact target should be penalized
|
||||
// // the same.
|
||||
// const clippedInput = BigNumber.min(targetInput, input);
|
||||
// // scale the clipped output inline with the input
|
||||
// const clippedOutput = clippedInput.dividedBy(input).times(output);
|
||||
// const adjustedOutput =
|
||||
// side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty);
|
||||
// const adjustedRate =
|
||||
// side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput);
|
||||
// // Skip orders with rates that are <= 0.
|
||||
// if (adjustedRate.lte(0)) {
|
||||
// continue;
|
||||
// }
|
||||
// fills.push({
|
||||
// sourcePathId,
|
||||
// adjustedRate,
|
||||
// adjustedOutput,
|
||||
// input: clippedInput,
|
||||
// output: clippedOutput,
|
||||
// flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
||||
// index: 0, // TBD
|
||||
// parent: undefined, // TBD
|
||||
// source: ERC20BridgeSource.Native,
|
||||
// type,
|
||||
// fillData: { ...o },
|
||||
// });
|
||||
// }
|
||||
// // Sort by descending adjusted rate.
|
||||
// fills = fills.sort((a, b) => b.adjustedRate.comparedTo(a.adjustedRate));
|
||||
// // Re-index fills.
|
||||
// for (let i = 0; i < fills.length; ++i) {
|
||||
// fills[i].parent = i === 0 ? undefined : fills[i - 1];
|
||||
// fills[i].index = i;
|
||||
// }
|
||||
// return fills;
|
||||
}
|
||||
|
||||
export function dexSamplesToFills(
|
||||
@@ -159,7 +160,7 @@ export function dexSamplesToFills(
|
||||
samples: DexSample[],
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
gasPrice: BigNumber,
|
||||
): Fill[] {
|
||||
const sourcePathId = hexUtils.random();
|
||||
const fills: Fill[] = [];
|
||||
@@ -171,10 +172,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 +195,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;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
@@ -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 };
|
||||
}
|
||||
|
@@ -1,18 +1,20 @@
|
||||
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 {
|
||||
BridgeFill,
|
||||
CollapsedGenericBridgeFill,
|
||||
CollapsedFill,
|
||||
CollapsedNativeOrderFill,
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
Fill,
|
||||
NativeCollapsedFill,
|
||||
OptimizedMarketOrder,
|
||||
OptimizedOrder,
|
||||
} from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||
@@ -38,10 +40,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 +110,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;
|
||||
}
|
||||
@@ -214,7 +209,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 +217,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 +245,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 +253,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 +263,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 +295,5 @@ export class Path {
|
||||
|
||||
export interface CollapsedPath extends Path {
|
||||
readonly collapsedFills: ReadonlyArray<CollapsedFill>;
|
||||
readonly orders: OptimizedMarketOrder[];
|
||||
readonly orders: OptimizedOrder[];
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID } from '../market_operation_utils/
|
||||
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 +42,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, source, encodedFillData } = sampleOrNativeOrder;
|
||||
const fee = gasPrice.times(sampleOrNativeOrder.gasCost);
|
||||
const outputFee = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
@@ -56,16 +56,16 @@ 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,
|
||||
});
|
||||
return outputFee;
|
||||
throw new Error(`No implementado`);
|
||||
// const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
|
||||
// const outputFee = ethToOutputAmount({
|
||||
// input,
|
||||
// output,
|
||||
// inputAmountPerEth,
|
||||
// outputAmountPerEth,
|
||||
// ethAmount: fee,
|
||||
// });
|
||||
// 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
|
||||
|
@@ -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;
|
||||
}
|
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
@@ -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';
|
@@ -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[]>;
|
||||
}
|
@@ -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.
|
||||
|
@@ -1,167 +1,87 @@
|
||||
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;
|
||||
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[]): 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[],
|
||||
): Promise<DexSample[][]> {
|
||||
const liquidity = await this._service.getSellLiquidityAsync(
|
||||
sources.map(s => ({
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -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 [];
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
@@ -3,11 +3,10 @@ import {
|
||||
FillQuoteTransformerOrderType,
|
||||
FillQuoteTransformerRfqOrderInfo,
|
||||
} 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, NativeOrderWithFillableAmounts, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types';
|
||||
import { QuoteRequestor } from '../../utils/quote_requestor';
|
||||
import { PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
|
||||
|
||||
@@ -163,122 +162,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 {
|
||||
type: FillQuoteTransformerOrderType.Limit | FillQuoteTransformerOrderType.Rfq;
|
||||
orderInfo: FillQuoteTransformerLimitOrderInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +229,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 +279,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 +381,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 +421,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 +446,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 +455,29 @@ 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 {
|
||||
inputToken: Address;
|
||||
outputToken: Address;
|
||||
nativeOrders: NativeOrderWithFillableAmounts[];
|
||||
rfqtIndicativeQuotes: V4RFQIndicativeQuote[];
|
||||
twoHopQuotes: Array<DexSample<MultiHopFillData>>;
|
||||
dexQuotes: Array<Array<DexSample<FillData>>>;
|
||||
dexQuotes: DexSample[][];
|
||||
}
|
||||
|
||||
export interface TokenAdjacencyGraph {
|
||||
@@ -541,7 +497,6 @@ export interface GenerateOptimizedOrdersOpts {
|
||||
bridgeSlippage?: number;
|
||||
maxFallbackSlippage?: number;
|
||||
excludedSources?: ERC20BridgeSource[];
|
||||
feeSchedule: FeeSchedule;
|
||||
exchangeProxyOverhead?: ExchangeProxyOverhead;
|
||||
allowFallback?: boolean;
|
||||
shouldBatchBridgeOrders?: boolean;
|
||||
|
@@ -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;
|
||||
|
@@ -8,12 +8,7 @@ import {
|
||||
CollapsedFill,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
FillData,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
NativeFillData,
|
||||
NativeLimitOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
CollapsedNativeOrderFill,
|
||||
} from './market_operation_utils/types';
|
||||
import { QuoteRequestor } from './quote_requestor';
|
||||
|
||||
@@ -21,7 +16,7 @@ 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 +29,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 +68,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 +122,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 +155,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 +194,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,
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
@@ -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),
|
||||
|
@@ -2,9 +2,8 @@ import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import { MarketOperation } from '../types';
|
||||
import { MarketOperation, SwapQuoteLimitOrder, SwapQuoteOrder } from '../types';
|
||||
|
||||
import { FeeSchedule, NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types';
|
||||
import { getNativeAdjustedTakerFeeAmount } from './utils';
|
||||
|
||||
const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants;
|
||||
@@ -64,7 +63,7 @@ const EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT = {
|
||||
};
|
||||
|
||||
export interface QuoteFillInfo {
|
||||
orders: OptimizedMarketOrder[];
|
||||
orders: SwapQuoteOrder[];
|
||||
fillAmount: BigNumber;
|
||||
gasPrice: BigNumber;
|
||||
side: MarketOperation;
|
||||
@@ -72,19 +71,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 +105,6 @@ export function simulateBestCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult
|
||||
createBestCaseFillOrderCalls(quoteInfo),
|
||||
quoteInfo.fillAmount,
|
||||
protocolFeePerFillOrder,
|
||||
opts.gasSchedule,
|
||||
);
|
||||
return fromIntermediateQuoteFillResult(result, quoteInfo);
|
||||
}
|
||||
@@ -122,9 +118,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 +136,6 @@ export function fillQuoteOrders(
|
||||
fillOrders: QuoteFillOrderCall[],
|
||||
inputAmount: BigNumber,
|
||||
protocolFeePerFillOrder: BigNumber,
|
||||
gasSchedule: FeeSchedule,
|
||||
): IntermediateQuoteFillResult {
|
||||
const result: IntermediateQuoteFillResult = {
|
||||
...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT,
|
||||
@@ -151,39 +146,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 +174,7 @@ export function fillQuoteOrders(
|
||||
return result;
|
||||
}
|
||||
|
||||
function hasProtocolFee(o: OptimizedMarketOrder): boolean {
|
||||
function hasProtocolFee(o: SwapQuoteOrder): boolean {
|
||||
return o.type === FillQuoteTransformerOrderType.Limit;
|
||||
}
|
||||
|
||||
@@ -237,7 +220,7 @@ function createBestCaseFillOrderCalls(quoteInfo: QuoteFillInfo): QuoteFillOrderC
|
||||
totalOrderInputFee:
|
||||
o.type === FillQuoteTransformerOrderType.Limit
|
||||
? getNativeAdjustedTakerFeeAmount(
|
||||
(o.fillData as NativeLimitOrderFillData).order,
|
||||
(o as SwapQuoteLimitOrder).fillData.orderInfo.order,
|
||||
o.takerAmount,
|
||||
)
|
||||
: ZERO_AMOUNT,
|
||||
@@ -248,13 +231,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,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
@@ -314,11 +291,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;
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { CommonOrderFields, FillQuoteTransformerOrderType, LimitOrderFields } from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
@@ -98,3 +99,21 @@ export function getNativeAdjustedFillableAmountsFromMakerAmount(
|
||||
: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
// 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 {
|
||||
[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 || {}),
|
||||
};
|
||||
}
|
||||
|
@@ -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,
|
||||
};
|
||||
|
@@ -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';
|
||||
|
@@ -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"
|
||||
]
|
||||
}
|
||||
|
@@ -2,7 +2,8 @@
|
||||
"extends": ["@0x/tslint-config"],
|
||||
"rules": {
|
||||
"max-file-line-count": false,
|
||||
"binary-expression-operand-order": false
|
||||
"binary-expression-operand-order": false,
|
||||
"no-bitwise": false,
|
||||
},
|
||||
"linterOptions": {
|
||||
"exclude": ["src/artifacts.ts", "test/artifacts.ts"]
|
||||
|
63
yarn.lock
63
yarn.lock
@@ -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"
|
||||
|
Reference in New Issue
Block a user