Merge branch 'development' of github.com:0xProject/0x-monorepo into refactor_rfq_comparison_price_integration

# Conflicts:
#	packages/asset-swapper/src/utils/market_operation_utils/index.ts
This commit is contained in:
Daniel Pyrathon 2020-10-16 15:16:59 -07:00
commit 0ae1c926d3
30 changed files with 804 additions and 28 deletions

View File

@ -26,6 +26,10 @@
"note": "Reworked `KyberBridge`",
"pr": 2683
},
{
"note": "Added `CreamBridge`",
"pr": 2715
},
{
"note": "Added `ShellBridge`",
"pr": 2722

View File

@ -0,0 +1,103 @@
/*
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.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
import "../interfaces/IERC20Bridge.sol";
import "../interfaces/IBalancerPool.sol";
contract CreamBridge is
IERC20Bridge,
IWallet,
DeploymentConstants
{
/// @dev Callback for `IERC20Bridge`. Tries to buy `amount` of
/// `toTokenAddress` tokens by selling the entirety of the `fromTokenAddress`
/// token encoded in the bridge data, then transfers the bought
/// tokens to `to`.
/// @param toTokenAddress The token to buy and transfer to `to`.
/// @param from The maker (this contract).
/// @param to The recipient of the bought tokens.
/// @param amount Minimum amount of `toTokenAddress` tokens to buy.
/// @param bridgeData The abi-encoded addresses of the "from" token and Balancer pool.
/// @return success The magic bytes if successful.
function bridgeTransferFrom(
address toTokenAddress,
address from,
address to,
uint256 amount,
bytes calldata bridgeData
)
external
returns (bytes4 success)
{
// Decode the bridge data.
(address fromTokenAddress, address poolAddress) = abi.decode(
bridgeData,
(address, address)
);
require(toTokenAddress != fromTokenAddress, "CreamBridge/INVALID_PAIR");
uint256 fromTokenBalance = IERC20Token(fromTokenAddress).balanceOf(address(this));
// Grant an allowance to the exchange to spend `fromTokenAddress` token.
LibERC20Token.approveIfBelow(fromTokenAddress, poolAddress, fromTokenBalance);
// Sell all of this contract's `fromTokenAddress` token balance.
(uint256 boughtAmount,) = IBalancerPool(poolAddress).swapExactAmountIn(
fromTokenAddress, // tokenIn
fromTokenBalance, // tokenAmountIn
toTokenAddress, // tokenOut
amount, // minAmountOut
uint256(-1) // maxPrice
);
// Transfer the converted `toToken`s to `to`.
LibERC20Token.transfer(toTokenAddress, to, boughtAmount);
emit ERC20BridgeTransfer(
fromTokenAddress,
toTokenAddress,
fromTokenBalance,
boughtAmount,
from,
to
);
return BRIDGE_SUCCESS;
}
/// @dev `SignatureType.Wallet` callback, so that this bridge can be the maker
/// and sign for itself in orders. Always succeeds.
/// @return magicValue Magic success bytes, always.
function isValidSignature(
bytes32,
bytes calldata
)
external
view
returns (bytes4 magicValue)
{
return LEGACY_WALLET_MAGIC_VALUE;
}
}

View File

@ -38,7 +38,7 @@
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
},
"config": {
"abis": "./test/generated-artifacts/@(BalancerBridge|BancorBridge|ChaiBridge|CurveBridge|DODOBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IBancorNetwork|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IMStable|IMooniswap|IShell|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MStableBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MooniswapBridge|MultiAssetProxy|Ownable|ShellBridge|StaticCallProxy|SushiSwapBridge|TestBancorBridge|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|TestUniswapV2Bridge|UniswapBridge|UniswapV2Bridge).json",
"abis": "./test/generated-artifacts/@(BalancerBridge|BancorBridge|ChaiBridge|CreamBridge|CurveBridge|DODOBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IBancorNetwork|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IMStable|IMooniswap|IShell|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MStableBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MooniswapBridge|MultiAssetProxy|Ownable|ShellBridge|StaticCallProxy|SushiSwapBridge|TestBancorBridge|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|TestUniswapV2Bridge|UniswapBridge|UniswapV2Bridge).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {

View File

@ -8,6 +8,7 @@ import { ContractArtifact } from 'ethereum-types';
import * as BalancerBridge from '../generated-artifacts/BalancerBridge.json';
import * as BancorBridge from '../generated-artifacts/BancorBridge.json';
import * as ChaiBridge from '../generated-artifacts/ChaiBridge.json';
import * as CreamBridge from '../generated-artifacts/CreamBridge.json';
import * as CurveBridge from '../generated-artifacts/CurveBridge.json';
import * as DexForwarderBridge from '../generated-artifacts/DexForwarderBridge.json';
import * as DODOBridge from '../generated-artifacts/DODOBridge.json';
@ -73,6 +74,7 @@ export const artifacts = {
BalancerBridge: BalancerBridge as ContractArtifact,
BancorBridge: BancorBridge as ContractArtifact,
ChaiBridge: ChaiBridge as ContractArtifact,
CreamBridge: CreamBridge as ContractArtifact,
CurveBridge: CurveBridge as ContractArtifact,
DODOBridge: DODOBridge as ContractArtifact,
DexForwarderBridge: DexForwarderBridge as ContractArtifact,

View File

@ -6,6 +6,7 @@
export * from '../generated-wrappers/balancer_bridge';
export * from '../generated-wrappers/bancor_bridge';
export * from '../generated-wrappers/chai_bridge';
export * from '../generated-wrappers/cream_bridge';
export * from '../generated-wrappers/curve_bridge';
export * from '../generated-wrappers/d_o_d_o_bridge';
export * from '../generated-wrappers/dex_forwarder_bridge';

View File

@ -8,6 +8,7 @@ import { ContractArtifact } from 'ethereum-types';
import * as BalancerBridge from '../test/generated-artifacts/BalancerBridge.json';
import * as BancorBridge from '../test/generated-artifacts/BancorBridge.json';
import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json';
import * as CreamBridge from '../test/generated-artifacts/CreamBridge.json';
import * as CurveBridge from '../test/generated-artifacts/CurveBridge.json';
import * as DexForwarderBridge from '../test/generated-artifacts/DexForwarderBridge.json';
import * as DODOBridge from '../test/generated-artifacts/DODOBridge.json';
@ -73,6 +74,7 @@ export const artifacts = {
BalancerBridge: BalancerBridge as ContractArtifact,
BancorBridge: BancorBridge as ContractArtifact,
ChaiBridge: ChaiBridge as ContractArtifact,
CreamBridge: CreamBridge as ContractArtifact,
CurveBridge: CurveBridge as ContractArtifact,
DODOBridge: DODOBridge as ContractArtifact,
DexForwarderBridge: DexForwarderBridge as ContractArtifact,

View File

@ -6,6 +6,7 @@
export * from '../test/generated-wrappers/balancer_bridge';
export * from '../test/generated-wrappers/bancor_bridge';
export * from '../test/generated-wrappers/chai_bridge';
export * from '../test/generated-wrappers/cream_bridge';
export * from '../test/generated-wrappers/curve_bridge';
export * from '../test/generated-wrappers/d_o_d_o_bridge';
export * from '../test/generated-wrappers/dex_forwarder_bridge';

View File

@ -6,6 +6,7 @@
"generated-artifacts/BalancerBridge.json",
"generated-artifacts/BancorBridge.json",
"generated-artifacts/ChaiBridge.json",
"generated-artifacts/CreamBridge.json",
"generated-artifacts/CurveBridge.json",
"generated-artifacts/DODOBridge.json",
"generated-artifacts/DexForwarderBridge.json",
@ -61,6 +62,7 @@
"test/generated-artifacts/BalancerBridge.json",
"test/generated-artifacts/BancorBridge.json",
"test/generated-artifacts/ChaiBridge.json",
"test/generated-artifacts/CreamBridge.json",
"test/generated-artifacts/CurveBridge.json",
"test/generated-artifacts/DODOBridge.json",
"test/generated-artifacts/DexForwarderBridge.json",

View File

@ -65,6 +65,10 @@
{
"note": "Added `Shell` into FQT",
"pr": 2722
},
{
"note": "Added `CREAM` into FQT",
"pr": 2715
}
]
},

View File

@ -46,6 +46,7 @@ contract BridgeAdapter is
{
address private immutable BALANCER_BRIDGE_ADDRESS;
address private immutable CREAM_BRIDGE_ADDRESS;
address private immutable CURVE_BRIDGE_ADDRESS;
address private immutable KYBER_BRIDGE_ADDRESS;
address private immutable MOONISWAP_BRIDGE_ADDRESS;
@ -93,6 +94,7 @@ contract BridgeAdapter is
SHELL_BRIDGE_ADDRESS = addresses.shellBridge;
UNISWAP_BRIDGE_ADDRESS = addresses.uniswapBridge;
UNISWAP_V2_BRIDGE_ADDRESS = addresses.uniswapV2Bridge;
CREAM_BRIDGE_ADDRESS = addresses.creamBridge;
}
function trade(
@ -134,7 +136,7 @@ contract BridgeAdapter is
sellAmount,
bridgeData
);
} else if (bridgeAddress == BALANCER_BRIDGE_ADDRESS) {
} else if (bridgeAddress == BALANCER_BRIDGE_ADDRESS || bridgeAddress == CREAM_BRIDGE_ADDRESS) {
boughtAmount = _tradeBalancer(
buyToken,
sellAmount,

View File

@ -24,6 +24,7 @@ contract MixinAdapterAddresses
struct AdapterAddresses {
// Bridges
address balancerBridge;
address creamBridge;
address curveBridge;
address kyberBridge;
address mooniswapBridge;

View File

@ -75,6 +75,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
weth: NULL_ADDRESS,
shellBridge: NULL_ADDRESS,
shell: NULL_ADDRESS,
creamBridge: NULL_ADDRESS,
},
);
transformer = await FillQuoteTransformerContract.deployFrom0xArtifactAsync(

View File

@ -153,6 +153,18 @@
{
"note": "Added `DODO`",
"pr": 2701
},
{
"note": "Fix for some edge cases with `includedSources` and `MultiHop`",
"pr": 2730
},
{
"note": "Introduced `excludedFeeSources` to disable sources when determining the price of an asset in ETH",
"pr": 2731
},
{
"note": "Support DODO Trade Allowed parameter to automatically disable the pool",
"pr": 2732
}
]
},

View File

@ -34,6 +34,7 @@ interface IDODOHelper {
interface IDODO {
function querySellBaseToken(uint256 amount) external view returns (uint256);
function _TRADE_ALLOWED_() external view returns (bool);
}
contract DODOSampler is
@ -80,6 +81,11 @@ contract DODOSampler is
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), // taker token data
@ -132,6 +138,11 @@ contract DODOSampler is
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),

View File

@ -71,8 +71,13 @@
"@0x/web3-wrapper": "^7.2.0",
"@balancer-labs/sor": "0.3.2",
"@bancor/sdk": "^0.2.9",
"@ethersproject/abi": "^5.0.1",
"@ethersproject/address": "^5.0.1",
"@ethersproject/contracts": "^5.0.1",
"@ethersproject/providers": "^5.0.4",
"axios": "^0.19.2",
"axios-mock-adapter": "^1.18.1",
"cream-sor": "^0.3.3",
"decimal.js": "^10.2.0",
"ethereum-types": "^3.2.0",
"ethereumjs-util": "^5.1.1",

View File

@ -25,6 +25,7 @@ export const SELL_SOURCE_FILTER = new SourceFilters([
ERC20BridgeSource.Shell,
ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Dodo,
ERC20BridgeSource.Cream,
]);
/**
@ -47,6 +48,7 @@ export const BUY_SOURCE_FILTER = new SourceFilters(
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Dodo,
ERC20BridgeSource.Cream,
],
[ERC20BridgeSource.MultiBridge],
);
@ -55,6 +57,7 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
// tslint:disable-next-line: custom-no-magic-numbers
runLimit: 2 ** 15,
excludedSources: [],
excludedFeeSources: [],
includedSources: [],
bridgeSlippage: 0.005,
maxFallbackSlippage: 0.05,

View File

@ -0,0 +1,104 @@
import { getPoolsWithTokens, parsePoolData } from 'cream-sor';
import { BalancerPool } from './balancer_utils';
// tslint:disable:boolean-naming
interface CacheValue {
timestamp: number;
pools: BalancerPool[];
}
// tslint:disable:custom-no-magic-numbers
const FIVE_SECONDS_MS = 5 * 1000;
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
const DEFAULT_TIMEOUT_MS = 1000;
const MAX_POOLS_FETCHED = 3;
// tslint:enable:custom-no-magic-numbers
export class CreamPoolsCache {
constructor(
private readonly _cache: { [key: string]: CacheValue } = {},
private readonly maxPoolsFetched: number = MAX_POOLS_FETCHED,
) {}
public async getPoolsForPairAsync(
takerToken: string,
makerToken: string,
timeoutMs: number = DEFAULT_TIMEOUT_MS,
): Promise<BalancerPool[]> {
const timeout = new Promise<BalancerPool[]>(resolve => setTimeout(resolve, timeoutMs, []));
return Promise.race([this._getPoolsForPairAsync(takerToken, makerToken), timeout]);
}
public getCachedPoolAddressesForPair(
takerToken: string,
makerToken: string,
cacheExpiryMs?: number,
): string[] | undefined {
const key = JSON.stringify([takerToken, makerToken]);
const value = this._cache[key];
if (cacheExpiryMs === undefined) {
return value === undefined ? [] : value.pools.map(pool => pool.id);
}
const minTimestamp = Date.now() - cacheExpiryMs;
if (value === undefined || value.timestamp < minTimestamp) {
return undefined;
} else {
return value.pools.map(pool => pool.id);
}
}
public howToSampleCream(
takerToken: string,
makerToken: string,
isAllowedSource: boolean,
): { onChain: boolean; offChain: boolean } {
// If CREAM is excluded as a source, do not sample.
if (!isAllowedSource) {
return { onChain: false, offChain: false };
}
const cachedCreamPools = this.getCachedPoolAddressesForPair(takerToken, makerToken, ONE_DAY_MS);
// Sample CREAM on-chain (i.e. via the ERC20BridgeSampler contract) if:
// - Cached values are not stale
// - There is at least one CREAM pool for this pair
const onChain = cachedCreamPools !== undefined && cachedCreamPools.length > 0;
// Sample CREAM off-chain (i.e. via GraphQL query + `computeCreamBuy/SellQuote`)
// if cached values are stale
const offChain = cachedCreamPools === undefined;
return { onChain, offChain };
}
protected async _getPoolsForPairAsync(
takerToken: string,
makerToken: string,
cacheExpiryMs: number = FIVE_SECONDS_MS,
): Promise<BalancerPool[]> {
const key = JSON.stringify([takerToken, makerToken]);
const value = this._cache[key];
const minTimestamp = Date.now() - cacheExpiryMs;
if (value === undefined || value.timestamp < minTimestamp) {
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
const timestamp = Date.now();
this._cache[key] = {
pools,
timestamp,
};
}
return this._cache[key].pools;
}
// tslint:disable-next-line:prefer-function-over-method
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<BalancerPool[]> {
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 [];
}
}
}

View File

@ -141,10 +141,12 @@ export class MarketOperationUtils {
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
const sampleAmounts = getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase);
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
const feeSourceFilters = this._feeSources.merge(requestFilters);
const quoteSourceFilters = this._sellSources.merge(requestFilters);
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
const {
onChain: sampleBalancerOnChain,
offChain: sampleBalancerOffChain,
@ -154,6 +156,20 @@ export class MarketOperationUtils {
quoteSourceFilters.isAllowed(ERC20BridgeSource.Balancer),
);
const {
onChain: sampleCreamOnChain,
offChain: sampleCreamOffChain,
} = this._sampler.creamPoolsCache.howToSampleCream(
takerToken,
makerToken,
quoteSourceFilters.isAllowed(ERC20BridgeSource.Cream),
);
const offChainSources = [
...(!sampleCreamOnChain ? [ERC20BridgeSource.Cream] : []),
...(!sampleBalancerOnChain ? [ERC20BridgeSource.Balancer] : []),
];
// Call the sampler contract.
const samplerPromise = this._sampler.executeAsync(
this._sampler.getTokenDecimals(makerToken, takerToken),
@ -181,7 +197,7 @@ export class MarketOperationUtils {
),
// Get sell quotes for taker -> maker.
this._sampler.getSellQuotes(
quoteSourceFilters.exclude(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer).sources,
quoteSourceFilters.exclude(offChainSources).sources,
makerToken,
takerToken,
sampleAmounts,
@ -204,6 +220,10 @@ export class MarketOperationUtils {
? this._sampler.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
: Promise.resolve([]);
const offChainCreamPromise = sampleCreamOffChain
? this._sampler.getCreamSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
: Promise.resolve([]);
const offChainBancorPromise = quoteSourceFilters.isAllowed(ERC20BridgeSource.Bancor)
? this._sampler.getBancorSellQuotesOffChainAsync(makerToken, takerToken, [takerAmount])
: Promise.resolve([]);
@ -211,8 +231,14 @@ export class MarketOperationUtils {
const [
[tokenDecimals, orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
offChainBalancerQuotes,
offChainCreamQuotes,
offChainBancorQuotes,
] = await Promise.all([samplerPromise, offChainBalancerPromise, offChainBancorPromise]);
] = await Promise.all([
samplerPromise,
offChainBalancerPromise,
offChainCreamPromise,
offChainBancorPromise,
]);
const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
return {
@ -220,7 +246,7 @@ export class MarketOperationUtils {
inputAmount: takerAmount,
inputToken: takerToken,
outputToken: makerToken,
dexQuotes: dexQuotes.concat([...offChainBalancerQuotes, offChainBancorQuotes]),
dexQuotes: dexQuotes.concat([...offChainBalancerQuotes, ...offChainCreamQuotes, offChainBancorQuotes]),
nativeOrders,
orderFillableAmounts,
ethToOutputRate: ethToMakerAssetRate,
@ -251,10 +277,12 @@ export class MarketOperationUtils {
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
const sampleAmounts = getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase);
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
const feeSourceFilters = this._feeSources.merge(requestFilters);
const quoteSourceFilters = this._buySources.merge(requestFilters);
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
const {
onChain: sampleBalancerOnChain,
offChain: sampleBalancerOffChain,
@ -264,6 +292,20 @@ export class MarketOperationUtils {
quoteSourceFilters.isAllowed(ERC20BridgeSource.Balancer),
);
const {
onChain: sampleCreamOnChain,
offChain: sampleCreamOffChain,
} = this._sampler.creamPoolsCache.howToSampleCream(
takerToken,
makerToken,
quoteSourceFilters.isAllowed(ERC20BridgeSource.Cream),
);
const offChainSources = [
...(!sampleCreamOnChain ? [ERC20BridgeSource.Cream] : []),
...(!sampleBalancerOnChain ? [ERC20BridgeSource.Balancer] : []),
];
// Call the sampler contract.
const samplerPromise = this._sampler.executeAsync(
this._sampler.getTokenDecimals(makerToken, takerToken),
@ -291,7 +333,7 @@ export class MarketOperationUtils {
),
// Get buy quotes for taker -> maker.
this._sampler.getBuyQuotes(
quoteSourceFilters.exclude(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer).sources,
quoteSourceFilters.exclude(offChainSources).sources,
makerToken,
takerToken,
sampleAmounts,
@ -313,10 +355,15 @@ export class MarketOperationUtils {
? this._sampler.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
: Promise.resolve([]);
const offChainCreamPromise = sampleCreamOffChain
? this._sampler.getCreamBuyQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
: Promise.resolve([]);
const [
[tokenDecimals, orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
offChainBalancerQuotes,
] = await Promise.all([samplerPromise, offChainBalancerPromise]);
offChainCreamQuotes,
] = await Promise.all([samplerPromise, offChainBalancerPromise, offChainCreamPromise]);
// Attach the MultiBridge address to the sample fillData
(dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.MultiBridge) || []).forEach(
q => (q.fillData = { poolAddress: this._multiBridge }),
@ -327,7 +374,7 @@ export class MarketOperationUtils {
inputAmount: makerAmount,
inputToken: makerToken,
outputToken: takerToken,
dexQuotes: dexQuotes.concat(offChainBalancerQuotes),
dexQuotes: dexQuotes.concat(offChainBalancerQuotes, offChainCreamQuotes),
nativeOrders,
orderFillableAmounts,
ethToOutputRate: ethToTakerAssetRate,
@ -394,9 +441,10 @@ export class MarketOperationUtils {
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
const feeSourceFilters = this._feeSources.merge(requestFilters);
const quoteSourceFilters = this._buySources.merge(requestFilters);
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
const ops = [
...batchNativeOrders.map(orders =>
this._sampler.getOrderFillableMakerAmounts(orders, this.contractAddresses.exchange),
@ -520,11 +568,9 @@ export class MarketOperationUtils {
ethToInputRate,
exchangeProxyOverhead: opts.exchangeProxyOverhead || (() => ZERO_AMOUNT),
};
const optimalPath = await findOptimalPathAsync(side, fills, inputAmount, opts.runLimit, optimizerOpts);
if (optimalPath === undefined) {
throw new Error(AggregationError.NoOptimalPath);
}
const optimalPathRate = optimalPath.adjustedRate();
const optimalPathRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
marketSideLiquidity,
@ -540,6 +586,11 @@ export class MarketOperationUtils {
};
}
// If there is no optimal path AND we didn't return a MultiHop quote, then throw
if (optimalPath === undefined) {
throw new Error(AggregationError.NoOptimalPath);
}
// Generate a fallback path if native orders are in the optimal path.
const nativeFills = optimalPath.fills.filter(f => f.source === ERC20BridgeSource.Native);
if (opts.allowFallback && nativeFills.length !== 0) {

View File

@ -48,26 +48,37 @@ export function getBestTwoHopQuote(
exchangeProxyOverhead?: ExchangeProxyOverhead,
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
const { side, inputAmount, ethToOutputRate, twoHopQuotes } = marketSideLiquidity;
if (twoHopQuotes.length === 0) {
return { adjustedRate: ZERO_AMOUNT, quote: undefined };
// 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 = twoHopQuotes
const best = filteredQuotes
.map(quote =>
getTwoHopAdjustedRate(side, quote, inputAmount, ethToOutputRate, feeSchedule, exchangeProxyOverhead),
)
.reduce(
(prev, curr, i) =>
curr.isGreaterThan(prev.adjustedRate) ? { adjustedRate: curr, quote: twoHopQuotes[i] } : prev,
curr.isGreaterThan(prev.adjustedRate) ? { adjustedRate: curr, quote: filteredQuotes[i] } : prev,
{
adjustedRate: getTwoHopAdjustedRate(
side,
twoHopQuotes[0],
filteredQuotes[0],
inputAmount,
ethToOutputRate,
feeSchedule,
exchangeProxyOverhead,
),
quote: twoHopQuotes[0],
quote: filteredQuotes[0],
},
);
return best;

View File

@ -186,6 +186,8 @@ function getBridgeAddressFromFill(fill: CollapsedFill, opts: CreateOrderFromPath
return opts.contractAddresses.bancorBridge;
case ERC20BridgeSource.Balancer:
return opts.contractAddresses.balancerBridge;
case ERC20BridgeSource.Cream:
return opts.contractAddresses.creamBridge;
case ERC20BridgeSource.LiquidityProvider:
return (fill.fillData as LiquidityProviderFillData).poolAddress;
case ERC20BridgeSource.MultiBridge:
@ -250,6 +252,14 @@ export function createBridgeOrder(
createBalancerBridgeData(takerToken, balancerFillData.poolAddress),
);
break;
case ERC20BridgeSource.Cream:
const creamFillData = (fill as CollapsedFill<BalancerFillData>).fillData!; // tslint:disable-line:no-non-null-assertion
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
bridgeAddress,
createBalancerBridgeData(takerToken, creamFillData.poolAddress),
);
break;
case ERC20BridgeSource.Bancor:
const bancorFillData = (fill as CollapsedFill<BancorFillData>).fillData!; // tslint:disable-line:no-non-null-assertion
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(

View File

@ -6,6 +6,7 @@ import { ERC20BridgeSamplerContract } from '../../wrappers';
import { BalancerPoolsCache } from './balancer_utils';
import { BancorService } from './bancor_service';
import { CreamPoolsCache } from './cream_utils';
import { SamplerOperations } from './sampler_operations';
import { BatchedOperation } from './types';
@ -37,9 +38,10 @@ export class DexOrderSampler extends SamplerOperations {
private readonly _samplerOverrides?: SamplerOverrides,
provider?: SupportedProvider,
balancerPoolsCache?: BalancerPoolsCache,
creamPoolsCache?: CreamPoolsCache,
getBancorServiceFn?: () => BancorService,
) {
super(_samplerContract, provider, balancerPoolsCache, getBancorServiceFn);
super(_samplerContract, provider, balancerPoolsCache, creamPoolsCache, getBancorServiceFn);
}
/* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */

View File

@ -8,6 +8,7 @@ import { ERC20BridgeSamplerContract } from '../../wrappers';
import { BalancerPoolsCache, computeBalancerBuyQuote, computeBalancerSellQuote } from './balancer_utils';
import { BancorService } from './bancor_service';
import { MAINNET_SUSHI_SWAP_ROUTER, MAX_UINT256, NULL_BYTES, ZERO_AMOUNT } from './constants';
import { CreamPoolsCache } from './cream_utils';
import { getCurveInfosForPair, getSwerveInfosForPair } from './curve_utils';
import { getKyberReserveIdsForPair } from './kyber_utils';
import { getMultiBridgeIntermediateToken } from './multibridge_utils';
@ -73,6 +74,7 @@ export class SamplerOperations {
protected readonly _samplerContract: ERC20BridgeSamplerContract,
public readonly provider?: SupportedProvider,
public readonly balancerPoolsCache: BalancerPoolsCache = new BalancerPoolsCache(),
public readonly creamPoolsCache: CreamPoolsCache = new CreamPoolsCache(),
protected readonly getBancorServiceFn?: () => BancorService, // for dependency injection in tests
) {}
@ -419,9 +421,10 @@ export class SamplerOperations {
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
source: ERC20BridgeSource,
): SourceQuoteOperation<BalancerFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Balancer,
source,
fillData: { poolAddress },
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromBalancer,
@ -434,9 +437,10 @@ export class SamplerOperations {
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
source: ERC20BridgeSource,
): SourceQuoteOperation<BalancerFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Balancer,
source,
fillData: { poolAddress },
contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromBalancer,
@ -476,6 +480,38 @@ export class SamplerOperations {
);
}
public async getCreamSellQuotesOffChainAsync(
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): Promise<Array<Array<DexSample<BalancerFillData>>>> {
const pools = await this.creamPoolsCache.getPoolsForPairAsync(takerToken, makerToken);
return pools.map(pool =>
takerFillAmounts.map(amount => ({
source: ERC20BridgeSource.Cream,
output: computeBalancerSellQuote(pool, amount),
input: amount,
fillData: { poolAddress: pool.id },
})),
);
}
public async getCreamBuyQuotesOffChainAsync(
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): Promise<Array<Array<DexSample<BalancerFillData>>>> {
const pools = await this.creamPoolsCache.getPoolsForPairAsync(takerToken, makerToken);
return pools.map(pool =>
makerFillAmounts.map(amount => ({
source: ERC20BridgeSource.Cream,
output: computeBalancerBuyQuote(pool, amount),
input: amount,
fillData: { poolAddress: pool.id },
})),
);
}
public getMStableSellQuotes(
makerToken: string,
takerToken: string,
@ -609,11 +645,12 @@ export class SamplerOperations {
const [firstHop, secondHop, buyAmount] = this._samplerContract.getABIDecodedReturnData<
[HopInfo, HopInfo, BigNumber]
>('sampleTwoHopSell', callResults);
// Ensure the hop sources are set even when the buy amount is zero
fillData.firstHopSource = firstHopOps[firstHop.sourceIndex.toNumber()];
fillData.secondHopSource = secondHopOps[secondHop.sourceIndex.toNumber()];
if (buyAmount.isZero()) {
return [ZERO_AMOUNT];
}
fillData.firstHopSource = firstHopOps[firstHop.sourceIndex.toNumber()];
fillData.secondHopSource = secondHopOps[secondHop.sourceIndex.toNumber()];
fillData.firstHopSource.handleCallResults(firstHop.returnData);
fillData.secondHopSource.handleCallResults(secondHop.returnData);
return [buyAmount];
@ -1047,7 +1084,25 @@ export class SamplerOperations {
return this.balancerPoolsCache
.getCachedPoolAddressesForPair(takerToken, makerToken)!
.map(poolAddress =>
this.getBalancerSellQuotes(poolAddress, makerToken, takerToken, takerFillAmounts),
this.getBalancerSellQuotes(
poolAddress,
makerToken,
takerToken,
takerFillAmounts,
ERC20BridgeSource.Balancer,
),
);
case ERC20BridgeSource.Cream:
return this.creamPoolsCache
.getCachedPoolAddressesForPair(takerToken, makerToken)!
.map(poolAddress =>
this.getBalancerSellQuotes(
poolAddress,
makerToken,
takerToken,
takerFillAmounts,
ERC20BridgeSource.Cream,
),
);
case ERC20BridgeSource.Shell:
return this.getShellSellQuotes(makerToken, takerToken, takerFillAmounts);
@ -1138,7 +1193,25 @@ export class SamplerOperations {
return this.balancerPoolsCache
.getCachedPoolAddressesForPair(takerToken, makerToken)!
.map(poolAddress =>
this.getBalancerBuyQuotes(poolAddress, makerToken, takerToken, makerFillAmounts),
this.getBalancerBuyQuotes(
poolAddress,
makerToken,
takerToken,
makerFillAmounts,
ERC20BridgeSource.Balancer,
),
);
case ERC20BridgeSource.Cream:
return this.creamPoolsCache
.getCachedPoolAddressesForPair(takerToken, makerToken)!
.map(poolAddress =>
this.getBalancerBuyQuotes(
poolAddress,
makerToken,
takerToken,
makerFillAmounts,
ERC20BridgeSource.Cream,
),
);
case ERC20BridgeSource.Shell:
return this.getShellBuyQuotes(makerToken, takerToken, makerFillAmounts);

View File

@ -39,6 +39,7 @@ export enum ERC20BridgeSource {
LiquidityProvider = 'LiquidityProvider',
MultiBridge = 'MultiBridge',
Balancer = 'Balancer',
Cream = 'CREAM',
Bancor = 'Bancor',
MStable = 'mStable',
Mooniswap = 'Mooniswap',
@ -243,6 +244,11 @@ export interface GetMarketOrdersOpts {
* Liquidity sources to exclude. Default is none.
*/
excludedSources: ERC20BridgeSource[];
/**
* Liquidity sources to exclude when used to calculate the cost of the route.
* Default is none.
*/
excludedFeeSources: ERC20BridgeSource[];
/**
* Liquidity sources to include. Default is none, which allows all supported
* sources that aren't excluded by `excludedSources`.

View File

@ -504,6 +504,7 @@ describe('DexSampler tests', () => {
undefined, // sampler overrides
provider,
undefined, // balancer cache
undefined, // cream cache
() => bancorService,
);
const quotes = await dexOrderSampler.getBancorSellQuotesOffChainAsync(

View File

@ -26,6 +26,7 @@ import {
SOURCE_FLAGS,
ZERO_AMOUNT,
} from '../src/utils/market_operation_utils/constants';
import { CreamPoolsCache } from '../src/utils/market_operation_utils/cream_utils';
import { createFills } from '../src/utils/market_operation_utils/fills';
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
@ -55,6 +56,7 @@ const DEFAULT_EXCLUDED = [
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Shell,
ERC20BridgeSource.Cream,
ERC20BridgeSource.Dodo,
];
const BUY_SOURCES = BUY_SOURCE_FILTER.sources;
@ -323,6 +325,7 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.SushiSwap]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.MultiHop]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Shell]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Cream]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Dodo]: _.times(NUM_SAMPLES, () => 0),
};
@ -370,6 +373,7 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Native]: { order: createOrder() },
[ERC20BridgeSource.MultiHop]: {},
[ERC20BridgeSource.Shell]: {},
[ERC20BridgeSource.Cream]: { poolAddress: randomAddress() },
[ERC20BridgeSource.Dodo]: {},
};
@ -411,6 +415,22 @@ describe('MarketOperationUtils tests', () => {
DEFAULT_FILL_DATA[ERC20BridgeSource.Balancer],
),
],
getCreamSellQuotesOffChainAsync: (_makerToken: string, _takerToken: string, takerFillAmounts: BigNumber[]) => [
createSamplesFromRates(
ERC20BridgeSource.Cream,
takerFillAmounts,
createDecreasingRates(takerFillAmounts.length),
DEFAULT_FILL_DATA[ERC20BridgeSource.Cream],
),
],
getCreamBuyQuotesOffChainAsync: (_makerToken: string, _takerToken: string, makerFillAmounts: BigNumber[]) => [
createSamplesFromRates(
ERC20BridgeSource.Cream,
makerFillAmounts,
createDecreasingRates(makerFillAmounts.length).map(r => new BigNumber(1).div(r)),
DEFAULT_FILL_DATA[ERC20BridgeSource.Cream],
),
],
getBancorSellQuotesOffChainAsync: (_makerToken: string, _takerToken: string, takerFillAmounts: BigNumber[]) =>
createSamplesFromRates(
ERC20BridgeSource.Bancor,
@ -430,6 +450,7 @@ describe('MarketOperationUtils tests', () => {
return ops;
},
balancerPoolsCache: new BalancerPoolsCache(),
creamPoolsCache: new CreamPoolsCache(),
} as any) as DexOrderSampler;
function replaceSamplerOps(ops: Partial<typeof DEFAULT_OPS> = {}): void {
@ -535,6 +556,14 @@ describe('MarketOperationUtils tests', () => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
},
getCreamSellQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
) => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
return DEFAULT_OPS.getCreamSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
},
});
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
...DEFAULT_OPTS,
@ -565,6 +594,14 @@ describe('MarketOperationUtils tests', () => {
args.sources = args.sources.concat(ERC20BridgeSource.Balancer);
return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
},
getCreamSellQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
) => {
args.sources = args.sources.concat(ERC20BridgeSource.Cream);
return DEFAULT_OPS.getCreamSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
},
});
const registryAddress = randomAddress();
const newMarketOperationUtils = new MarketOperationUtils(
@ -606,6 +643,14 @@ describe('MarketOperationUtils tests', () => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
},
getCreamSellQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
) => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
return DEFAULT_OPS.getCreamSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
},
});
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
...DEFAULT_OPTS,
@ -637,6 +682,14 @@ describe('MarketOperationUtils tests', () => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
},
getCreamSellQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
) => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
return DEFAULT_OPS.getCreamSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
},
});
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
...DEFAULT_OPTS,
@ -1380,6 +1433,14 @@ describe('MarketOperationUtils tests', () => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
},
getCreamBuyQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
) => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
return DEFAULT_OPS.getCreamBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
},
});
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
...DEFAULT_OPTS,
@ -1410,6 +1471,14 @@ describe('MarketOperationUtils tests', () => {
args.sources = args.sources.concat(ERC20BridgeSource.Balancer);
return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
},
getCreamBuyQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
) => {
args.sources = args.sources.concat(ERC20BridgeSource.Cream);
return DEFAULT_OPS.getCreamBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
},
});
const registryAddress = randomAddress();
const newMarketOperationUtils = new MarketOperationUtils(
@ -1451,6 +1520,14 @@ describe('MarketOperationUtils tests', () => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
},
getCreamBuyQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
) => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
return DEFAULT_OPS.getCreamBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
},
});
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
...DEFAULT_OPTS,
@ -1482,6 +1559,14 @@ describe('MarketOperationUtils tests', () => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
},
getCreamBuyQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
) => {
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
return DEFAULT_OPS.getCreamBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
},
});
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
...DEFAULT_OPTS,

View File

@ -57,6 +57,10 @@
{
"note": "Deploy `DodoBridge` on Mainnet",
"pr": 2701
},
{
"note": "Deploy `CreamBridge` on Mainnet",
"pr": 2715
}
]
},

View File

@ -45,6 +45,7 @@
"sushiswapBridge": "0x47ed0262a0b688dcb836d254c6a2e96b6c48a9f5",
"shellBridge": "0x21fb3862eed7911e0f8219a077247b849846728d",
"dodoBridge": "0xe9da66965a9344aab2167e6813c03f043cc7a6ca",
"creamBridge": "0xb9d4bf2c8dab828f4ffb656acdb6c2b497d44f25",
"transformers": {
"wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e",
"payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7",
@ -98,6 +99,7 @@
"sushiswapBridge": "0x0000000000000000000000000000000000000000",
"shellBridge": "0x0000000000000000000000000000000000000000",
"dodoBridge": "0x0000000000000000000000000000000000000000",
"creamBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
"payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
@ -151,6 +153,7 @@
"sushiswapBridge": "0x0000000000000000000000000000000000000000",
"shellBridge": "0x0000000000000000000000000000000000000000",
"dodoBridge": "0x0000000000000000000000000000000000000000",
"creamBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
"payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
@ -204,6 +207,7 @@
"sushiswapBridge": "0x0000000000000000000000000000000000000000",
"shellBridge": "0x0000000000000000000000000000000000000000",
"dodoBridge": "0x0000000000000000000000000000000000000000",
"creamBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0x9ce35b5ee9e710535e3988e3f8731d9ca9dba17d",
"payTakerTransformer": "0x5a53e7b02a83aa9f60ccf4e424f0442c255bc977",
@ -257,6 +261,7 @@
"sushiswapBridge": "0x0000000000000000000000000000000000000000",
"shellBridge": "0x0000000000000000000000000000000000000000",
"dodoBridge": "0x0000000000000000000000000000000000000000",
"creamBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0xc6b0d3c45a6b5092808196cb00df5c357d55e1d5",
"payTakerTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",

View File

@ -46,6 +46,7 @@ export interface ContractAddresses {
sushiswapBridge: string;
shellBridge: string;
dodoBridge: string;
creamBridge: string;
transformers: {
wethTransformer: string;
payTakerTransformer: string;

View File

@ -325,6 +325,7 @@ export async function runMigrationsAsync(
uniswapExchangeFactory: NULL_ADDRESS,
mStable: NULL_ADDRESS,
shellBridge: NULL_ADDRESS,
creamBridge: NULL_ADDRESS,
shell: NULL_ADDRESS,
weth: etherToken.address,
},
@ -405,6 +406,7 @@ export async function runMigrationsAsync(
sushiswapBridge: NULL_ADDRESS,
shellBridge: NULL_ADDRESS,
dodoBridge: NULL_ADDRESS,
creamBridge: NULL_ADDRESS,
exchangeProxy: exchangeProxy.address,
exchangeProxyAllowanceTarget: exchangeProxyAllowanceTargetAddress,
exchangeProxyTransformerDeployer: txDefaults.from,

267
yarn.lock
View File

@ -1094,6 +1094,242 @@
version "0.7.2"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.2.tgz#b180097bb44a40c3b59efea9cf28df2fe542d518"
"@ethersproject/abi@^5.0.1", "@ethersproject/abi@^5.0.5":
version "5.0.7"
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.7.tgz#79e52452bd3ca2956d0e1c964207a58ad1a0ee7b"
integrity sha512-Cqktk+hSIckwP/W8O47Eef60VwmoSC/L3lY0+dIBhQPCNn9E4V7rwmm2aFrNRRDJfFlGuZ1khkQUOc3oBX+niw==
dependencies:
"@ethersproject/address" "^5.0.4"
"@ethersproject/bignumber" "^5.0.7"
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/constants" "^5.0.4"
"@ethersproject/hash" "^5.0.4"
"@ethersproject/keccak256" "^5.0.3"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/properties" "^5.0.3"
"@ethersproject/strings" "^5.0.4"
"@ethersproject/abstract-provider@^5.0.4":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.0.5.tgz#797a32a8707830af1ad8f833e9c228994d5572b9"
integrity sha512-i/CjElAkzV7vQBAeoz+IpjGfcFYEP9eD7j3fzZ0fzTq03DO7PPnR+xkEZ1IoDXGwDS+55aLM1xvLDwB/Lx6IOQ==
dependencies:
"@ethersproject/bignumber" "^5.0.7"
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/networks" "^5.0.3"
"@ethersproject/properties" "^5.0.3"
"@ethersproject/transactions" "^5.0.5"
"@ethersproject/web" "^5.0.6"
"@ethersproject/abstract-signer@^5.0.4":
version "5.0.6"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.0.6.tgz#c01211665ab9c9e93988c4783b789712fd93a388"
integrity sha512-h8TZBX3pL2Xx9tmsRxfWcaaI+FcJFHWvZ/vNvFjLp8zJ0kPD501LKTt2jo44LZ20N3EW68JMoyEmRQ6bpsn+iA==
dependencies:
"@ethersproject/abstract-provider" "^5.0.4"
"@ethersproject/bignumber" "^5.0.7"
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/properties" "^5.0.3"
"@ethersproject/address@^5.0.1", "@ethersproject/address@^5.0.4":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.5.tgz#2caa65f6b7125015395b1b54c985ee0b27059cc7"
integrity sha512-DpkQ6rwk9jTefrRsJzEm6nhRiJd9pvhn1xN0rw5N/jswXG5r7BLk/GVA0mMAVWAsYfvi2xSc5L41FMox43RYEA==
dependencies:
"@ethersproject/bignumber" "^5.0.7"
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/keccak256" "^5.0.3"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/rlp" "^5.0.3"
bn.js "^4.4.0"
"@ethersproject/base64@^5.0.3":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.0.4.tgz#b0d8fdbf3dda977cf546dcd35725a7b1d5256caa"
integrity sha512-4KRykQ7BQMeOXfvio1YITwHjxwBzh92UoXIdzxDE1p53CK28bbHPdsPNYo0wl0El7lJAMpT2SOdL0hhbWRnyIA==
dependencies:
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/basex@^5.0.3":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.0.4.tgz#93e1cd11f9a47281da2389de24f88e13e9d90847"
integrity sha512-ixIr/kKiAoSzOnSc777AGIOAhKai5Ivqr4HO/Gz+YG+xkfv6kqD6AW4ga9vM20Wwb0QBhh3LoRWTu4V1K+x9Ew==
dependencies:
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/properties" "^5.0.3"
"@ethersproject/bignumber@^5.0.7":
version "5.0.8"
resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.8.tgz#cee33bd8eb0266176def0d371b45274b1d2c4ec0"
integrity sha512-KXFVAFKS1jdTXYN8BE5Oj+ZfPMh28iRdFeNGBVT6cUFdtiPVqeXqc0ggvBqA3A1VoFFGgM7oAeaagA393aORHA==
dependencies:
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
bn.js "^4.4.0"
"@ethersproject/bytes@^5.0.4":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.0.5.tgz#688b70000e550de0c97a151a21f15b87d7f97d7c"
integrity sha512-IEj9HpZB+ACS6cZ+QQMTqmu/cnUK2fYNE6ms/PVxjoBjoxc6HCraLpam1KuRvreMy0i523PLmjN8OYeikRdcUQ==
dependencies:
"@ethersproject/logger" "^5.0.5"
"@ethersproject/constants@^5.0.4":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.5.tgz#0ed19b002e8404bdf6d135234dc86a7d9bcf9b71"
integrity sha512-foaQVmxp2+ik9FrLUCtVrLZCj4M3Ibgkqvh+Xw/vFRSerkjVSYePApaVE5essxhoSlF1U9oXfWY09QI2AXtgKA==
dependencies:
"@ethersproject/bignumber" "^5.0.7"
"@ethersproject/contracts@^5.0.1":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.0.5.tgz#64831a341ec8ca225e83ff3e9437c26b970fd5d7"
integrity sha512-tFI255lFbmbqMkgnuyhDWHl3yWqttPlReplYuVvDCT/SuvBjLR4ad2uipBlh1fh5X1ipK9ettAoV4S0HKim4Kw==
dependencies:
"@ethersproject/abi" "^5.0.5"
"@ethersproject/abstract-provider" "^5.0.4"
"@ethersproject/abstract-signer" "^5.0.4"
"@ethersproject/address" "^5.0.4"
"@ethersproject/bignumber" "^5.0.7"
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/constants" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/properties" "^5.0.3"
"@ethersproject/hash@^5.0.4":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.5.tgz#e383ba2c7941834266fa6e2cf543d2b0c50a9d59"
integrity sha512-GpI80/h2HDpfNKpCZoxQJCjOQloGnlD5hM1G+tZe8FQDJhEvFjJoPDuWv+NaYjJfOciKS2Axqc4Q4WamdLoUgg==
dependencies:
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/keccak256" "^5.0.3"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/strings" "^5.0.4"
"@ethersproject/keccak256@^5.0.3":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.4.tgz#36ca0a7d1ae2a272da5654cb886776d0c680ef3a"
integrity sha512-GNpiOUm9PGUxFNqOxYKDQBM0u68bG9XC9iOulEQ8I0tOx/4qUpgVzvgXL6ugxr0RY554Gz/NQsVqknqPzUcxpQ==
dependencies:
"@ethersproject/bytes" "^5.0.4"
js-sha3 "0.5.7"
"@ethersproject/logger@^5.0.5":
version "5.0.6"
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.6.tgz#faa484203e86e08be9e07fef826afeef7183fe88"
integrity sha512-FrX0Vnb3JZ1md/7GIZfmJ06XOAA8r3q9Uqt9O5orr4ZiksnbpXKlyDzQtlZ5Yv18RS8CAUbiKH9vwidJg1BPmQ==
"@ethersproject/networks@^5.0.3":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.4.tgz#6d320a5e15a0cda804f5da88be0ba846156f6eec"
integrity sha512-/wHDTRms5mpJ09BoDrbNdFWINzONe05wZRgohCXvEv39rrH/Gd/yAnct8wC0RsW3tmFOgjgQxuBvypIxuUynTw==
dependencies:
"@ethersproject/logger" "^5.0.5"
"@ethersproject/properties@^5.0.3":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.0.4.tgz#a67a1f5a52c30850b5062c861631e73d131f666e"
integrity sha512-UdyX3GqBxFt15B0uSESdDNmhvEbK3ACdDXl2soshoPcneXuTswHDeA0LoPlnaZzhbgk4p6jqb4GMms5C26Qu6A==
dependencies:
"@ethersproject/logger" "^5.0.5"
"@ethersproject/providers@^5.0.4":
version "5.0.12"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.12.tgz#de05e865e130709ea1e0870511eb892bda7d41cb"
integrity sha512-bRUEVNth+wGlm2Q0cQprVlixBWumfP9anrgAc3V2CbIh+GKvCwisVO8uRLrZOfOvTNSy6PUJi/Z4D5L+k3NAog==
dependencies:
"@ethersproject/abstract-provider" "^5.0.4"
"@ethersproject/abstract-signer" "^5.0.4"
"@ethersproject/address" "^5.0.4"
"@ethersproject/basex" "^5.0.3"
"@ethersproject/bignumber" "^5.0.7"
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/constants" "^5.0.4"
"@ethersproject/hash" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/networks" "^5.0.3"
"@ethersproject/properties" "^5.0.3"
"@ethersproject/random" "^5.0.3"
"@ethersproject/rlp" "^5.0.3"
"@ethersproject/sha2" "^5.0.3"
"@ethersproject/strings" "^5.0.4"
"@ethersproject/transactions" "^5.0.5"
"@ethersproject/web" "^5.0.6"
bech32 "1.1.4"
ws "7.2.3"
"@ethersproject/random@^5.0.3":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.0.4.tgz#98f7cf65b0e588cec39ef24843e391ed5004556f"
integrity sha512-AIZJhqs6Ba4/+U3lOjt3QZbP6b/kuuGLJUYFUonAgWmkTHwqsCwYnFvnHKQSUuHbXHvErp7WFXFlztx+yMn3kQ==
dependencies:
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/rlp@^5.0.3":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.0.4.tgz#0090a0271e84ea803016a112a79f5cfd80271a77"
integrity sha512-5qrrZad7VTjofxSsm7Zg/7Dr4ZOln4S2CqiDdOuTv6MBKnXj0CiBojXyuDy52M8O3wxH0CyE924hXWTDV1PQWQ==
dependencies:
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/sha2@^5.0.3":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.0.4.tgz#40f639721a27dbe034b3dee021ba20b054586fec"
integrity sha512-0yFhf1mspxAfWdXXoPtK94adUeu1R7/FzAa+DfEiZTc76sz/vHXf0LSIazoR3znYKFny6haBxME+usbvvEcF3A==
dependencies:
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
hash.js "1.1.3"
"@ethersproject/signing-key@^5.0.4":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.5.tgz#acfd06fc05a14180df7e027688bbd23fc4baf782"
integrity sha512-Z1wY7JC1HVO4CvQWY2TyTTuAr8xK3bJijZw1a9G92JEmKdv1j255R/0YLBBcFTl2J65LUjtXynNJ2GbArPGi5g==
dependencies:
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/properties" "^5.0.3"
elliptic "6.5.3"
"@ethersproject/strings@^5.0.4":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.5.tgz#ed7e99a282a02f40757691b04a24cd83f3752195"
integrity sha512-JED6WaIV00xM/gvj8vSnd+0VWtDYdidTmavFRCTQakqfz+4tDo6Jz5LHgG+dd45h7ah7ykCHW0C7ZXWEDROCXQ==
dependencies:
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/constants" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/transactions@^5.0.5":
version "5.0.6"
resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.0.6.tgz#b8b27938be6e9ed671dbdd35fe98af8b14d0df7c"
integrity sha512-htsFhOD+NMBxx676A8ehSuwVV49iqpSB+CkjPZ02tpNew0K6p8g0CZ46Z1ZP946gIHAU80xQ0NACHYrjIUaCFA==
dependencies:
"@ethersproject/address" "^5.0.4"
"@ethersproject/bignumber" "^5.0.7"
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/constants" "^5.0.4"
"@ethersproject/keccak256" "^5.0.3"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/properties" "^5.0.3"
"@ethersproject/rlp" "^5.0.3"
"@ethersproject/signing-key" "^5.0.4"
"@ethersproject/web@^5.0.6":
version "5.0.9"
resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.0.9.tgz#b08f8295f4bfd4777c8723fe9572f5453b9f03cb"
integrity sha512-//QNlv1MSkOII1hv3+HQwWoiVFS+BMVGI0KYeUww4cyrEktnx1QIez5bTSab9s9fWTFaWKNmQNBwMbxAqPuYDw==
dependencies:
"@ethersproject/base64" "^5.0.3"
"@ethersproject/bytes" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
"@ethersproject/properties" "^5.0.3"
"@ethersproject/strings" "^5.0.4"
"@evocateur/libnpmaccess@^3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845"
@ -3912,6 +4148,11 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
bech32@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9"
integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==
before-after-hook@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
@ -5511,6 +5752,14 @@ coveralls@^3.0.0:
minimist "^1.2.0"
request "^2.79.0"
cream-sor@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/cream-sor/-/cream-sor-0.3.3.tgz#ae7ab50c68cfd36a89e2101187ceebbb79e1b14c"
integrity sha512-taQSvCUunPhwyebwbjxh1l9NDp39lsre+Q2oRMK+gqzbu+Wlbg5GAquwoV2/GLgzia70gi4x1rJCJsUmc5Kygg==
dependencies:
bignumber.js "^9.0.0"
isomorphic-fetch "^2.2.1"
create-ecdh@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.1.tgz#44223dfed533193ba5ba54e0df5709b89acf1f82"
@ -6357,6 +6606,19 @@ elliptic@6.5.2:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
elliptic@6.5.3:
version "6.5.3"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
dependencies:
bn.js "^4.4.0"
brorand "^1.0.1"
hash.js "^1.0.0"
hmac-drbg "^1.0.0"
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
elliptic@^6.0.0, elliptic@^6.2.3, elliptic@^6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
@ -18442,6 +18704,11 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
ws@7.2.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46"
integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==
ws@^3.0.0:
version "3.3.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"