feat: Swerve Finance and SushiSwap (#2698)

* feat: Swerve Finance

* export SwerveFillData

* test and CHANGELOG

* feat: Sushiswap (#2700)

* feat: SushiSwap

* Changelog

* fix tests

* Deployed SushiSwap bridge

* Fix test

* IGNORED_EXCESSIVE_TYPES
This commit is contained in:
Jacob Evans 2020-09-09 21:35:24 +10:00 committed by GitHub
parent 08ae43aad3
commit 630108ccb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 527 additions and 45 deletions

View File

@ -0,0 +1,136 @@
/*
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/interfaces/IEtherToken.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
import "@0x/contracts-utils/contracts/src/LibAddressArray.sol";
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
import "../interfaces/IUniswapV2Router01.sol";
import "../interfaces/IERC20Bridge.sol";
// solhint-disable space-after-comma
// solhint-disable not-rely-on-time
contract SushiSwapBridge is
IERC20Bridge,
IWallet,
DeploymentConstants
{
struct TransferState {
address[] path;
address router;
uint256 fromTokenBalance;
}
/// @dev Callback for `IERC20Bridge`. Tries to buy `amount` of
/// `toTokenAddress` tokens by selling the entirety of the `fromTokenAddress`
/// token encoded in the bridge data.
/// @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 path of token addresses. Last element must be toTokenAddress
/// @return success The magic bytes if successful.
function bridgeTransferFrom(
address toTokenAddress,
address from,
address to,
uint256 amount,
bytes calldata bridgeData
)
external
returns (bytes4 success)
{
// hold variables to get around stack depth limitations
TransferState memory state;
// Decode the bridge data to get the `fromTokenAddress`.
// solhint-disable indent
(state.path, state.router) = abi.decode(bridgeData, (address[], address));
// solhint-enable indent
require(state.path.length >= 2, "SushiSwapBridge/PATH_LENGTH_MUST_BE_AT_LEAST_TWO");
require(state.path[state.path.length - 1] == toTokenAddress, "SushiSwapBridge/LAST_ELEMENT_OF_PATH_MUST_MATCH_OUTPUT_TOKEN");
// Just transfer the tokens if they're the same.
if (state.path[0] == toTokenAddress) {
LibERC20Token.transfer(state.path[0], to, amount);
return BRIDGE_SUCCESS;
}
// Get our balance of `fromTokenAddress` token.
state.fromTokenBalance = IERC20Token(state.path[0]).balanceOf(address(this));
// Grant the SushiSwap router an allowance.
LibERC20Token.approveIfBelow(
state.path[0],
state.router,
state.fromTokenBalance
);
// Buy as much `toTokenAddress` token with `fromTokenAddress` token
// and transfer it to `to`.
IUniswapV2Router01 router = IUniswapV2Router01(state.router);
uint[] memory amounts = router.swapExactTokensForTokens(
// Sell all tokens we hold.
state.fromTokenBalance,
// Minimum buy amount.
amount,
// Convert `fromTokenAddress` to `toTokenAddress`.
state.path,
// Recipient is `to`.
to,
// Expires after this block.
block.timestamp
);
emit ERC20BridgeTransfer(
// input token
state.path[0],
// output token
toTokenAddress,
// input token amount
state.fromTokenBalance,
// output token amount
amounts[amounts.length - 1],
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 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|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IBancorNetwork|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IMStable|IMooniswap|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MStableBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MooniswapBridge|MultiAssetProxy|Ownable|StaticCallProxy|TestBancorBridge|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|TestUniswapV2Bridge|UniswapBridge|UniswapV2Bridge).json",
"abis": "./test/generated-artifacts/@(BalancerBridge|BancorBridge|ChaiBridge|CurveBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IBancorNetwork|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IMStable|IMooniswap|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MStableBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MooniswapBridge|MultiAssetProxy|Ownable|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

@ -44,6 +44,7 @@ import * as MStableBridge from '../generated-artifacts/MStableBridge.json';
import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
import * as Ownable from '../generated-artifacts/Ownable.json';
import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.json';
import * as SushiSwapBridge from '../generated-artifacts/SushiSwapBridge.json';
import * as TestBancorBridge from '../generated-artifacts/TestBancorBridge.json';
import * as TestChaiBridge from '../generated-artifacts/TestChaiBridge.json';
import * as TestDexForwarderBridge from '../generated-artifacts/TestDexForwarderBridge.json';
@ -77,6 +78,7 @@ export const artifacts = {
MStableBridge: MStableBridge as ContractArtifact,
MixinGasToken: MixinGasToken as ContractArtifact,
MooniswapBridge: MooniswapBridge as ContractArtifact,
SushiSwapBridge: SushiSwapBridge as ContractArtifact,
UniswapBridge: UniswapBridge as ContractArtifact,
UniswapV2Bridge: UniswapV2Bridge as ContractArtifact,
IAssetData: IAssetData as ContractArtifact,

View File

@ -42,6 +42,7 @@ export * from '../generated-wrappers/mooniswap_bridge';
export * from '../generated-wrappers/multi_asset_proxy';
export * from '../generated-wrappers/ownable';
export * from '../generated-wrappers/static_call_proxy';
export * from '../generated-wrappers/sushi_swap_bridge';
export * from '../generated-wrappers/test_bancor_bridge';
export * from '../generated-wrappers/test_chai_bridge';
export * from '../generated-wrappers/test_dex_forwarder_bridge';

View File

@ -44,6 +44,7 @@ import * as MStableBridge from '../test/generated-artifacts/MStableBridge.json';
import * as MultiAssetProxy from '../test/generated-artifacts/MultiAssetProxy.json';
import * as Ownable from '../test/generated-artifacts/Ownable.json';
import * as StaticCallProxy from '../test/generated-artifacts/StaticCallProxy.json';
import * as SushiSwapBridge from '../test/generated-artifacts/SushiSwapBridge.json';
import * as TestBancorBridge from '../test/generated-artifacts/TestBancorBridge.json';
import * as TestChaiBridge from '../test/generated-artifacts/TestChaiBridge.json';
import * as TestDexForwarderBridge from '../test/generated-artifacts/TestDexForwarderBridge.json';
@ -77,6 +78,7 @@ export const artifacts = {
MStableBridge: MStableBridge as ContractArtifact,
MixinGasToken: MixinGasToken as ContractArtifact,
MooniswapBridge: MooniswapBridge as ContractArtifact,
SushiSwapBridge: SushiSwapBridge as ContractArtifact,
UniswapBridge: UniswapBridge as ContractArtifact,
UniswapV2Bridge: UniswapV2Bridge as ContractArtifact,
IAssetData: IAssetData as ContractArtifact,

View File

@ -42,6 +42,7 @@ export * from '../test/generated-wrappers/mooniswap_bridge';
export * from '../test/generated-wrappers/multi_asset_proxy';
export * from '../test/generated-wrappers/ownable';
export * from '../test/generated-wrappers/static_call_proxy';
export * from '../test/generated-wrappers/sushi_swap_bridge';
export * from '../test/generated-wrappers/test_bancor_bridge';
export * from '../test/generated-wrappers/test_chai_bridge';
export * from '../test/generated-wrappers/test_dex_forwarder_bridge';

View File

@ -42,6 +42,7 @@
"generated-artifacts/MultiAssetProxy.json",
"generated-artifacts/Ownable.json",
"generated-artifacts/StaticCallProxy.json",
"generated-artifacts/SushiSwapBridge.json",
"generated-artifacts/TestBancorBridge.json",
"generated-artifacts/TestChaiBridge.json",
"generated-artifacts/TestDexForwarderBridge.json",
@ -93,6 +94,7 @@
"test/generated-artifacts/MultiAssetProxy.json",
"test/generated-artifacts/Ownable.json",
"test/generated-artifacts/StaticCallProxy.json",
"test/generated-artifacts/SushiSwapBridge.json",
"test/generated-artifacts/TestBancorBridge.json",
"test/generated-artifacts/TestChaiBridge.json",
"test/generated-artifacts/TestDexForwarderBridge.json",

View File

@ -288,11 +288,11 @@ contract DeploymentConstants {
}
/// @dev An overridable way to retrieve the Mooniswap registry address.
/// @return musd The Mooniswap registry address.
/// @return registry The Mooniswap registry address.
function _getMooniswapAddress()
internal
view
returns (address registry)
returns (address)
{
return MOONISWAP_REGISTRY;
}

View File

@ -101,6 +101,14 @@
{
"note": "Return Mooniswap pool in sampler and encode it in bridge data",
"pr": 2692
},
{
"note": "Added `Swerve`",
"pr": 2698
},
{
"note": "Added `SushiSwap`",
"pr": 2698
}
]
},

View File

@ -28,9 +28,10 @@ import "./MultiBridgeSampler.sol";
import "./MStableSampler.sol";
import "./MooniswapSampler.sol";
import "./NativeOrderSampler.sol";
import "./SushiSwapSampler.sol";
import "./TwoHopSampler.sol";
import "./UniswapSampler.sol";
import "./UniswapV2Sampler.sol";
import "./TwoHopSampler.sol";
contract ERC20BridgeSampler is
@ -43,6 +44,7 @@ contract ERC20BridgeSampler is
MooniswapSampler,
MultiBridgeSampler,
NativeOrderSampler,
SushiSwapSampler,
TwoHopSampler,
UniswapSampler,
UniswapV2Sampler

View File

@ -0,0 +1,103 @@
/*
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.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
import "./interfaces/IUniswapV2Router01.sol";
contract SushiSwapSampler is
DeploymentConstants
{
/// @dev Gas limit for SushiSwap calls.
uint256 constant private SUSHISWAP_CALL_GAS = 150e3; // 150k
/// @dev Sample sell quotes from SushiSwap.
/// @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 sampleSellsFromSushiSwap(
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++) {
(bool didSucceed, bytes memory resultData) =
router.staticcall.gas(SUSHISWAP_CALL_GAS)(
abi.encodeWithSelector(
IUniswapV2Router01(0).getAmountsOut.selector,
takerTokenAmounts[i],
path
));
uint256 buyAmount = 0;
if (didSucceed) {
// solhint-disable-next-line indent
buyAmount = abi.decode(resultData, (uint256[]))[path.length - 1];
} else {
break;
}
makerTokenAmounts[i] = buyAmount;
}
}
/// @dev Sample buy quotes from SushiSwap
/// @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 sampleBuysFromSushiSwap(
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++) {
(bool didSucceed, bytes memory resultData) =
router.staticcall.gas(SUSHISWAP_CALL_GAS)(
abi.encodeWithSelector(
IUniswapV2Router01(0).getAmountsIn.selector,
makerTokenAmounts[i],
path
));
uint256 sellAmount = 0;
if (didSucceed) {
// solhint-disable-next-line indent
sellAmount = abi.decode(resultData, (uint256[]))[0];
} else {
break;
}
takerTokenAmounts[i] = sellAmount;
}
}
}

View File

@ -38,7 +38,7 @@
"config": {
"publicInterfaceContracts": "ERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json",
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json",
"postpublish": {
"assets": []
}

View File

@ -99,6 +99,7 @@ export {
MarketOperation,
MarketSellSwapQuote,
MockedRfqtFirmQuoteResponse,
OrderPrunerPermittedFeeTypes,
RfqtMakerAssetOfferings,
RfqtRequestOpts,
SamplerOverrides,
@ -148,6 +149,9 @@ export {
OptimizedMarketOrder,
SourceInfo,
SourceQuoteOperation,
SushiSwapFillData,
SwerveFillData,
SwerveInfo,
TokenAdjacencyGraph,
UniswapV2FillData,
} from './utils/market_operation_utils/types';

View File

@ -17,6 +17,8 @@ export const SELL_SOURCES = [
// ERC20BridgeSource.Bancor, // FIXME: Disabled until Bancor SDK supports batch requests
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
ERC20BridgeSource.Swerve,
ERC20BridgeSource.SushiSwap,
];
/**
@ -32,6 +34,8 @@ export const BUY_SOURCES = [
// ERC20BridgeSource.Bancor, // FIXME: Disabled until Bancor SDK supports buy quotes
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
ERC20BridgeSource.Swerve,
ERC20BridgeSource.SushiSwap,
];
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
@ -132,6 +136,20 @@ export const MAINNET_CURVE_INFOS: { [name: string]: CurveInfo } = {
],
},
};
export const MAINNET_SWERVE_INFOS: { [name: string]: CurveInfo } = {
swUSD: {
exchangeFunctionSelector: CurveFunctionSelectors.exchange,
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying,
buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying,
poolAddress: '0x329239599afB305DA0A2eC69c58F8a6697F9F88d',
tokens: [
'0x6b175474e89094c44da98b954eedeac495271d0f',
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
'0xdac17f958d2ee523a2206206994597c13d831ec7',
'0x0000000000085d4780b73119b644ae5ecd22b376',
],
},
};
export const MAINNET_KYBER_RESERVE_IDS: { [name: string]: string } = {
Reserve1: '0xff4b796265722046707200000000000000000000000000000000000000000000',
@ -169,6 +187,8 @@ export const MAINNET_KYBER_TOKEN_RESERVE_IDS: { [token: string]: string } = {
'0xaa42414e44000000000000000000000000000000000000000000000000000000',
};
export const MAINNET_SUSHI_SWAP_ROUTER = '0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F';
export const ERC20_PROXY_ID = '0xf47261b0';
export const WALLET_SIGNATURE = '0x04';
export const ONE_ETHER = new BigNumber(1e18);

View File

@ -1,7 +1,11 @@
import { MAINNET_CURVE_INFOS } from './constants';
import { CurveInfo } from './types';
import { MAINNET_CURVE_INFOS, MAINNET_SWERVE_INFOS } from './constants';
import { CurveInfo, SwerveInfo } from './types';
// tslint:disable completed-docs
export function getCurveInfosForPair(takerToken: string, makerToken: string): CurveInfo[] {
return Object.values(MAINNET_CURVE_INFOS).filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)));
}
export function getSwerveInfosForPair(takerToken: string, makerToken: string): SwerveInfo[] {
return Object.values(MAINNET_SWERVE_INFOS).filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)));
}

View File

@ -35,6 +35,8 @@ import {
NativeCollapsedFill,
OptimizedMarketOrder,
OrderDomain,
SushiSwapFillData,
SwerveFillData,
UniswapV2FillData,
} from './types';
@ -222,8 +224,12 @@ function getBridgeAddressFromFill(fill: CollapsedFill, opts: CreateOrderFromPath
return opts.contractAddresses.uniswapBridge;
case ERC20BridgeSource.UniswapV2:
return opts.contractAddresses.uniswapV2Bridge;
case ERC20BridgeSource.SushiSwap:
return opts.contractAddresses.sushiswapBridge;
case ERC20BridgeSource.Curve:
return opts.contractAddresses.curveBridge;
case ERC20BridgeSource.Swerve:
return opts.contractAddresses.curveBridge;
case ERC20BridgeSource.Bancor:
return opts.contractAddresses.bancorBridge;
case ERC20BridgeSource.Balancer:
@ -266,6 +272,20 @@ function createBridgeOrder(
),
);
break;
case ERC20BridgeSource.Swerve:
const swerveFillData = (fill as CollapsedFill<SwerveFillData>).fillData!; // tslint:disable-line:no-non-null-assertion
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
bridgeAddress,
createCurveBridgeData(
swerveFillData.pool.poolAddress,
swerveFillData.pool.exchangeFunctionSelector,
takerToken,
swerveFillData.fromTokenIdx,
swerveFillData.toTokenIdx,
),
);
break;
case ERC20BridgeSource.Balancer:
const balancerFillData = (fill as CollapsedFill<BalancerFillData>).fillData!; // tslint:disable-line:no-non-null-assertion
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
@ -290,6 +310,14 @@ function createBridgeOrder(
createUniswapV2BridgeData(uniswapV2FillData.tokenAddressPath),
);
break;
case ERC20BridgeSource.SushiSwap:
const sushiSwapFillData = (fill as CollapsedFill<SushiSwapFillData>).fillData!; // tslint:disable-line:no-non-null-assertion
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
bridgeAddress,
createSushiSwapBridgeData(sushiSwapFillData.tokenAddressPath, sushiSwapFillData.router),
);
break;
case ERC20BridgeSource.MultiBridge:
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
@ -431,25 +459,24 @@ function createCurveBridgeData(
fromTokenIdx: number,
toTokenIdx: number,
): string {
const curveBridgeDataEncoder = AbiEncoder.create([
const encoder = AbiEncoder.create([
{ name: 'curveAddress', type: 'address' },
{ name: 'exchangeFunctionSelector', type: 'bytes4' },
{ name: 'fromTokenAddress', type: 'address' },
{ name: 'fromTokenIdx', type: 'int128' },
{ name: 'toTokenIdx', type: 'int128' },
]);
return curveBridgeDataEncoder.encode([
curveAddress,
exchangeFunctionSelector,
takerToken,
fromTokenIdx,
toTokenIdx,
]);
return encoder.encode([curveAddress, exchangeFunctionSelector, takerToken, fromTokenIdx, toTokenIdx]);
}
function createUniswapV2BridgeData(tokenAddressPath: string[]): string {
const uniswapV2BridgeDataEncoder = AbiEncoder.create('(address[])');
return uniswapV2BridgeDataEncoder.encode([tokenAddressPath]);
const encoder = AbiEncoder.create('(address[])');
return encoder.encode([tokenAddressPath]);
}
function createSushiSwapBridgeData(tokenAddressPath: string[], router: string): string {
const encoder = AbiEncoder.create('(address[],address)');
return encoder.encode([tokenAddressPath, router]);
}
function getSlippedBridgeAssetAmounts(fill: CollapsedFill, opts: CreateOrderFromPathOpts): [BigNumber, BigNumber] {

View File

@ -6,8 +6,8 @@ import { ERC20BridgeSamplerContract } from '../../wrappers';
import { BalancerPoolsCache, computeBalancerBuyQuote, computeBalancerSellQuote } from './balancer_utils';
import { BancorService } from './bancor_service';
import { MAX_UINT256, NULL_BYTES, ZERO_AMOUNT } from './constants';
import { getCurveInfosForPair } from './curve_utils';
import { MAINNET_SUSHI_SWAP_ROUTER, MAX_UINT256, NULL_BYTES, ZERO_AMOUNT } from './constants';
import { getCurveInfosForPair, getSwerveInfosForPair } from './curve_utils';
import { getKyberReserveIdsForPair } from './kyber_utils';
import { getMultiBridgeIntermediateToken } from './multibridge_utils';
import { getIntermediateTokens } from './multihop_utils';
@ -27,6 +27,9 @@ import {
MultiBridgeFillData,
MultiHopFillData,
SourceQuoteOperation,
SushiSwapFillData,
SwerveFillData,
SwerveInfo,
TokenAdjacencyGraph,
UniswapV2FillData,
} from './types';
@ -315,6 +318,62 @@ export class SamplerOperations {
});
}
public getSwerveSellQuotes(
pool: SwerveInfo,
fromTokenIdx: number,
toTokenIdx: number,
takerFillAmounts: BigNumber[],
): SourceQuoteOperation<SwerveFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Swerve,
fillData: {
pool,
fromTokenIdx,
toTokenIdx,
},
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromCurve,
params: [
{
poolAddress: pool.poolAddress,
sellQuoteFunctionSelector: pool.sellQuoteFunctionSelector,
buyQuoteFunctionSelector: pool.buyQuoteFunctionSelector,
},
new BigNumber(fromTokenIdx),
new BigNumber(toTokenIdx),
takerFillAmounts,
],
});
}
public getSwerveBuyQuotes(
pool: SwerveInfo,
fromTokenIdx: number,
toTokenIdx: number,
makerFillAmounts: BigNumber[],
): SourceQuoteOperation<SwerveFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Swerve,
fillData: {
pool,
fromTokenIdx,
toTokenIdx,
},
contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromCurve,
params: [
{
poolAddress: pool.poolAddress,
sellQuoteFunctionSelector: pool.sellQuoteFunctionSelector,
buyQuoteFunctionSelector: pool.buyQuoteFunctionSelector,
},
new BigNumber(fromTokenIdx),
new BigNumber(toTokenIdx),
makerFillAmounts,
],
});
}
public getBalancerSellQuotes(
poolAddress: string,
makerToken: string,
@ -621,6 +680,32 @@ export class SamplerOperations {
};
}
public getSushiSwapSellQuotes(
tokenAddressPath: string[],
takerFillAmounts: BigNumber[],
): SourceQuoteOperation<SushiSwapFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.SushiSwap,
fillData: { tokenAddressPath, router: MAINNET_SUSHI_SWAP_ROUTER },
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromSushiSwap,
params: [MAINNET_SUSHI_SWAP_ROUTER, tokenAddressPath, takerFillAmounts],
});
}
public getSushiSwapBuyQuotes(
tokenAddressPath: string[],
makerFillAmounts: BigNumber[],
): SourceQuoteOperation<SushiSwapFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.SushiSwap,
fillData: { tokenAddressPath, router: MAINNET_SUSHI_SWAP_ROUTER },
contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromSushiSwap,
params: [MAINNET_SUSHI_SWAP_ROUTER, tokenAddressPath, makerFillAmounts],
});
}
public getMedianSellRate(
sources: ERC20BridgeSource[],
makerToken: string,
@ -784,6 +869,17 @@ export class SamplerOperations {
);
}
return ops;
case ERC20BridgeSource.SushiSwap:
const sushiOps = [this.getSushiSwapSellQuotes([takerToken, makerToken], takerFillAmounts)];
if (takerToken !== wethAddress && makerToken !== wethAddress) {
sushiOps.push(
this.getSushiSwapSellQuotes(
[takerToken, wethAddress, makerToken],
takerFillAmounts,
),
);
}
return sushiOps;
case ERC20BridgeSource.Kyber:
return getKyberReserveIdsForPair(takerToken, makerToken).map(reserveId =>
this.getKyberSellQuotes(reserveId, makerToken, takerToken, takerFillAmounts),
@ -797,6 +893,15 @@ export class SamplerOperations {
takerFillAmounts,
),
);
case ERC20BridgeSource.Swerve:
return getSwerveInfosForPair(takerToken, makerToken).map(pool =>
this.getSwerveSellQuotes(
pool,
pool.tokens.indexOf(takerToken),
pool.tokens.indexOf(makerToken),
takerFillAmounts,
),
);
case ERC20BridgeSource.LiquidityProvider:
if (liquidityProviderRegistryAddress === undefined) {
throw new Error(
@ -865,6 +970,14 @@ export class SamplerOperations {
);
}
return ops;
case ERC20BridgeSource.SushiSwap:
const sushiOps = [this.getSushiSwapBuyQuotes([takerToken, makerToken], makerFillAmounts)];
if (takerToken !== wethAddress && makerToken !== wethAddress) {
sushiOps.push(
this.getSushiSwapBuyQuotes([takerToken, wethAddress, makerToken], makerFillAmounts),
);
}
return sushiOps;
case ERC20BridgeSource.Kyber:
return getKyberReserveIdsForPair(takerToken, makerToken).map(reserveId =>
this.getKyberBuyQuotes(reserveId, makerToken, takerToken, makerFillAmounts),
@ -878,6 +991,15 @@ export class SamplerOperations {
makerFillAmounts,
),
);
case ERC20BridgeSource.Swerve:
return getSwerveInfosForPair(takerToken, makerToken).map(pool =>
this.getSwerveBuyQuotes(
pool,
pool.tokens.indexOf(takerToken),
pool.tokens.indexOf(makerToken),
makerFillAmounts,
),
);
case ERC20BridgeSource.LiquidityProvider:
if (liquidityProviderRegistryAddress === undefined) {
throw new Error(

View File

@ -41,6 +41,8 @@ export enum ERC20BridgeSource {
MStable = 'mStable',
Mooniswap = 'Mooniswap',
MultiHop = 'MultiHop',
Swerve = 'Swerve',
SushiSwap = 'SushiSwap',
}
// tslint:disable: enum-naming
@ -69,6 +71,8 @@ export interface CurveInfo {
tokens: string[];
}
export interface SwerveInfo extends CurveInfo {}
// Internal `fillData` field for `Fill` objects.
export interface FillData {}
@ -88,6 +92,12 @@ export interface CurveFillData extends FillData {
curve: CurveInfo;
}
export interface SwerveFillData extends FillData {
fromTokenIdx: number;
toTokenIdx: number;
pool: SwerveInfo;
}
export interface BalancerFillData extends FillData {
poolAddress: string;
}
@ -96,6 +106,10 @@ export interface UniswapV2FillData extends FillData {
tokenAddressPath: string[];
}
export interface SushiSwapFillData extends UniswapV2FillData {
router: string;
}
export interface LiquidityProviderFillData extends FillData {
poolAddress: string;
}
@ -103,6 +117,7 @@ export interface LiquidityProviderFillData extends FillData {
export interface MultiBridgeFillData extends FillData {
poolAddress: string;
}
export interface BancorFillData extends FillData {
path: string[];
networkAddress: string;

View File

@ -30,6 +30,7 @@ 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 SushiSwapSampler from '../test/generated-artifacts/SushiSwapSampler.json';
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json';
import * as TwoHopSampler from '../test/generated-artifacts/TwoHopSampler.json';
@ -49,6 +50,7 @@ export const artifacts = {
MultiBridgeSampler: MultiBridgeSampler as ContractArtifact,
NativeOrderSampler: NativeOrderSampler as ContractArtifact,
SamplerUtils: SamplerUtils as ContractArtifact,
SushiSwapSampler: SushiSwapSampler as ContractArtifact,
TwoHopSampler: TwoHopSampler as ContractArtifact,
UniswapSampler: UniswapSampler as ContractArtifact,
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,

View File

@ -27,6 +27,16 @@ const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
const MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN);
const TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN);
const DEFAULT_EXCLUDED = [
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Curve,
ERC20BridgeSource.Balancer,
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
ERC20BridgeSource.Bancor,
ERC20BridgeSource.Swerve,
ERC20BridgeSource.SushiSwap,
];
// tslint:disable: custom-no-magic-numbers promise-function-async
describe('MarketOperationUtils tests', () => {
@ -80,6 +90,8 @@ describe('MarketOperationUtils tests', () => {
return ERC20BridgeSource.MStable;
case contractAddresses.mooniswapBridge.toLowerCase():
return ERC20BridgeSource.Mooniswap;
case contractAddresses.sushiswapBridge.toLowerCase():
return ERC20BridgeSource.SushiSwap;
default:
break;
}
@ -238,11 +250,11 @@ describe('MarketOperationUtils tests', () => {
[source: string]: Numberish[];
}
const DEFAULT_RATES: RatesBySource = {
[ERC20BridgeSource.Native]: createDecreasingRates(NUM_SAMPLES),
[ERC20BridgeSource.Eth2Dai]: createDecreasingRates(NUM_SAMPLES),
[ERC20BridgeSource.Kyber]: createDecreasingRates(NUM_SAMPLES),
[ERC20BridgeSource.Uniswap]: createDecreasingRates(NUM_SAMPLES),
const ZERO_RATES: RatesBySource = {
[ERC20BridgeSource.Native]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Eth2Dai]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Uniswap]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Kyber]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.UniswapV2]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Balancer]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Bancor]: _.times(NUM_SAMPLES, () => 0),
@ -251,6 +263,15 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.MultiBridge]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.MStable]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Mooniswap]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Swerve]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.SushiSwap]: _.times(NUM_SAMPLES, () => 0),
};
const DEFAULT_RATES: RatesBySource = {
...ZERO_RATES,
[ERC20BridgeSource.Native]: createDecreasingRates(NUM_SAMPLES),
[ERC20BridgeSource.Eth2Dai]: createDecreasingRates(NUM_SAMPLES),
[ERC20BridgeSource.Uniswap]: createDecreasingRates(NUM_SAMPLES),
};
interface FillDataBySource {
@ -273,7 +294,19 @@ describe('MarketOperationUtils tests', () => {
fromTokenIdx: 0,
toTokenIdx: 1,
},
[ERC20BridgeSource.Swerve]: {
pool: {
poolAddress: randomAddress(),
tokens: [TAKER_TOKEN, MAKER_TOKEN],
exchangeFunctionSelector: hexUtils.random(4),
sellQuoteFunctionSelector: hexUtils.random(4),
buyQuoteFunctionSelector: hexUtils.random(4),
},
fromTokenIdx: 0,
toTokenIdx: 1,
},
[ERC20BridgeSource.LiquidityProvider]: { poolAddress: randomAddress() },
[ERC20BridgeSource.SushiSwap]: { tokenAddressPath: [] },
};
const DEFAULT_OPS = {
@ -416,14 +449,7 @@ describe('MarketOperationUtils tests', () => {
sampleDistributionBase: 1,
bridgeSlippage: 0,
maxFallbackSlippage: 100,
excludedSources: [
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Curve,
ERC20BridgeSource.Balancer,
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
ERC20BridgeSource.Bancor,
],
excludedSources: DEFAULT_EXCLUDED,
allowFallback: false,
shouldBatchBridgeOrders: false,
};
@ -583,7 +609,7 @@ describe('MarketOperationUtils tests', () => {
});
it('can mix convex sources', async () => {
const rates: RatesBySource = {};
const rates: RatesBySource = { ...DEFAULT_RATES };
rates[ERC20BridgeSource.Native] = [0.4, 0.3, 0.2, 0.1];
rates[ERC20BridgeSource.Uniswap] = [0.5, 0.05, 0.05, 0.05];
rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05];
@ -852,14 +878,7 @@ describe('MarketOperationUtils tests', () => {
sampleDistributionBase: 1,
bridgeSlippage: 0,
maxFallbackSlippage: 100,
excludedSources: [
ERC20BridgeSource.Kyber,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Curve,
ERC20BridgeSource.Balancer,
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
],
excludedSources: DEFAULT_EXCLUDED,
allowFallback: false,
shouldBatchBridgeOrders: false,
};
@ -1019,7 +1038,7 @@ describe('MarketOperationUtils tests', () => {
});
it('can mix convex sources', async () => {
const rates: RatesBySource = {};
const rates: RatesBySource = { ...ZERO_RATES };
rates[ERC20BridgeSource.Native] = [0.4, 0.3, 0.2, 0.1];
rates[ERC20BridgeSource.Uniswap] = [0.5, 0.05, 0.05, 0.05];
rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05];
@ -1049,6 +1068,7 @@ describe('MarketOperationUtils tests', () => {
// dropping their effective rates.
const nativeFeeRate = 0.06;
const rates: RatesBySource = {
...ZERO_RATES,
[ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, ~0.93, ~0.92, ~0.91]
[ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1],
[ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1],
@ -1086,6 +1106,7 @@ describe('MarketOperationUtils tests', () => {
// dropping its effective rates.
const uniswapFeeRate = 0.2;
const rates: RatesBySource = {
...ZERO_RATES,
[ERC20BridgeSource.Native]: [0.95, 0.1, 0.1, 0.1],
// Effectively [0.8, ~0.5, ~0, ~0]
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
@ -1118,7 +1139,7 @@ describe('MarketOperationUtils tests', () => {
});
it('fallback orders use different sources', async () => {
const rates: RatesBySource = {};
const rates: RatesBySource = { ...ZERO_RATES };
rates[ERC20BridgeSource.Native] = [0.9, 0.8, 0.5, 0.5];
rates[ERC20BridgeSource.Uniswap] = [0.6, 0.05, 0.01, 0.01];
rates[ERC20BridgeSource.Eth2Dai] = [0.4, 0.3, 0.01, 0.01];
@ -1138,7 +1159,7 @@ describe('MarketOperationUtils tests', () => {
});
it('does not create a fallback if below maxFallbackSlippage', async () => {
const rates: RatesBySource = {};
const rates: RatesBySource = { ...ZERO_RATES };
rates[ERC20BridgeSource.Native] = [1, 1, 0.01, 0.01];
rates[ERC20BridgeSource.Uniswap] = [1, 1, 0.01, 0.01];
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.49, 0.49, 0.49];
@ -1159,7 +1180,7 @@ describe('MarketOperationUtils tests', () => {
});
it('batches contiguous bridge sources', async () => {
const rates: RatesBySource = {};
const rates: RatesBySource = { ...ZERO_RATES };
rates[ERC20BridgeSource.Native] = [0.5, 0.01, 0.01, 0.01];
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.02, 0.01, 0.01];
rates[ERC20BridgeSource.Uniswap] = [0.48, 0.01, 0.01, 0.01];

View File

@ -28,6 +28,7 @@ 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/sushi_swap_sampler';
export * from '../test/generated-wrappers/test_erc20_bridge_sampler';
export * from '../test/generated-wrappers/test_native_order_sampler';
export * from '../test/generated-wrappers/two_hop_sampler';

View File

@ -33,6 +33,7 @@
"test/generated-artifacts/MultiBridgeSampler.json",
"test/generated-artifacts/NativeOrderSampler.json",
"test/generated-artifacts/SamplerUtils.json",
"test/generated-artifacts/SushiSwapSampler.json",
"test/generated-artifacts/TestERC20BridgeSampler.json",
"test/generated-artifacts/TestNativeOrderSampler.json",
"test/generated-artifacts/TwoHopSampler.json",

View File

@ -42,6 +42,7 @@
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"mStableBridge": "0x2bf04fcea05f0989a14d9afa37aa376baca6b2b3",
"mooniswapBridge": "0x02b7eca484ad960fca3f7709e0b2ac81eec3069c",
"sushiswapBridge": "0x47ed0262a0b688dcb836d254c6a2e96b6c48a9f5",
"transformers": {
"wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e",
"payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7",
@ -92,6 +93,7 @@
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"mStableBridge": "0x0000000000000000000000000000000000000000",
"mooniswapBridge": "0x0000000000000000000000000000000000000000",
"sushiswapBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
"payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
@ -142,6 +144,7 @@
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"mStableBridge": "0x0000000000000000000000000000000000000000",
"mooniswapBridge": "0x0000000000000000000000000000000000000000",
"sushiswapBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
"payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
@ -192,6 +195,7 @@
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"mStableBridge": "0x0000000000000000000000000000000000000000",
"mooniswapBridge": "0x0000000000000000000000000000000000000000",
"sushiswapBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0x9ce35b5ee9e710535e3988e3f8731d9ca9dba17d",
"payTakerTransformer": "0x5a53e7b02a83aa9f60ccf4e424f0442c255bc977",
@ -242,6 +246,7 @@
"exchangeProxyFlashWallet": "0xb9682a8e7920b431f1d412b8510f0077410c8faa",
"mStableBridge": "0x0000000000000000000000000000000000000000",
"mooniswapBridge": "0x0000000000000000000000000000000000000000",
"sushiswapBridge": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0xc6b0d3c45a6b5092808196cb00df5c357d55e1d5",
"payTakerTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",

View File

@ -43,6 +43,7 @@ export interface ContractAddresses {
exchangeProxyFlashWallet: string;
mStableBridge: string;
mooniswapBridge: string;
sushiswapBridge: string;
transformers: {
wethTransformer: string;
payTakerTransformer: string;

View File

@ -400,6 +400,7 @@ export async function runMigrationsAsync(
exchangeProxyGovernor: NULL_ADDRESS,
mStableBridge: NULL_ADDRESS,
mooniswapBridge: NULL_ADDRESS,
sushiswapBridge: NULL_ADDRESS,
exchangeProxy: exchangeProxy.address,
exchangeProxyAllowanceTarget: exchangeProxyAllowanceTargetAddress,
exchangeProxyTransformerDeployer: txDefaults.from,

View File

@ -70,6 +70,7 @@ export const docGenConfigs: DocGenConfigs = {
'MultiAssetData',
'StaticCallAssetData',
'MultiAssetDataWithRecursiveDecoding',
'OrderPrunerPermittedFeeTypes',
],
// Some libraries only export types. In those cases, we cannot check if the exported types are part of the
// "exported public interface". Thus we add them here and skip those checks.