Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

ERC20BridgeSampler Curve #2483

Merged
merged 9 commits into from
Feb 15, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import "./IERC20BridgeSampler.sol";
import "./IEth2Dai.sol";
import "./IKyberNetwork.sol";
import "./IUniswapExchangeQuotes.sol";
import "./ICurve.sol";


contract ERC20BridgeSampler is
Expand All @@ -43,6 +44,8 @@ contract ERC20BridgeSampler is
uint256 constant internal UNISWAP_CALL_GAS = 150e3; // 150k
/// @dev Base gas limit for Eth2Dai calls.
uint256 constant internal ETH2DAI_CALL_GAS = 1000e3; // 1m
/// @dev Base gas limit for Curve calls.
uint256 constant internal CURVE_CALL_GAS = 150e3; // 150k

/// @dev Call multiple public functions on this contract in a single transaction.
/// @param callDatas ABI-encoded call data for each function call.
Expand Down Expand Up @@ -389,6 +392,44 @@ contract ERC20BridgeSampler is
}
}

/// @dev Sample sell quotes from Curve.
/// @param curveAddress Address of the Curve contract.
/// @param fromTokenIdx Index of the taker token (what to sell).
/// @param toTokenIdx Index of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromCurve(
address curveAddress,
int128 fromTokenIdx,
int128 toTokenIdx,
uint256[] memory takerTokenAmounts
)
public
view
returns (uint256[] memory makerTokenAmounts)
{
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
(bool didSucceed, bytes memory resultData) =
curveAddress.staticcall.gas(CURVE_CALL_GAS)(
abi.encodeWithSelector(
ICurve(0).get_dy_underlying.selector,
fromTokenIdx,
toTokenIdx,
takerTokenAmounts[i]
));
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
} else {
break;
}
makerTokenAmounts[i] = buyAmount;
}
}

/// @dev Overridable way to get token decimals.
/// @param tokenAddress Address of the token.
/// @return decimals The decimal places for the token.
Expand Down
60 changes: 60 additions & 0 deletions contracts/erc20-bridge-sampler/contracts/src/ICurve.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*

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;


// solhint-disable func-name-mixedcase
interface ICurve {

/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
/// @param i The token index being sold.
/// @param j The token index being bought.
/// @param sellAmount The amount of token being bought.
/// @param minBuyAmount The minimum buy amount of the token being bought.
/// @param deadline The time in seconds when this operation should expire.
function exchange_underlying(
int128 i,
int128 j,
uint256 sellAmount,
uint256 minBuyAmount,
uint256 deadline
)
external;

/// @dev Get the amount of `toToken` by selling `sellAmount` of `fromToken`
/// @param i The token index being sold.
/// @param j The token index being bought.
/// @param sellAmount The amount of token being bought.
function get_dy_underlying(
int128 i,
int128 j,
uint256 sellAmount
)
external
returns (uint256 dy);

/// @dev Get the underlying token address from the token index
/// @param i The token index.
function underlying_coins(
int128 i
)
external
returns (address tokenAddress);
}

Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,21 @@ interface IERC20BridgeSampler {
external
view
returns (uint256[] memory takerTokenAmounts);

/// @dev Sample sell quotes from Curve.
/// @param curveAddress Address of the Curve contract.
/// @param fromTokenIdx Index of the taker token (what to sell).
/// @param toTokenIdx Index of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromCurve(
address curveAddress,
int128 fromTokenIdx,
int128 toTokenIdx,
uint256[] calldata takerTokenAmounts
)
external
view
returns (uint256[] memory makerTokenAmounts);
}
2 changes: 1 addition & 1 deletion contracts/erc20-bridge-sampler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"config": {
"publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(ERC20BridgeSampler|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
"abis": "./test/generated-artifacts/@(ERC20BridgeSampler|ICurve|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
},
"repository": {
"type": "git",
Expand Down
2 changes: 2 additions & 0 deletions contracts/erc20-bridge-sampler/test/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { ContractArtifact } from 'ethereum-types';

import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
import * as ICurve from '../test/generated-artifacts/ICurve.json';
import * as IDevUtils from '../test/generated-artifacts/IDevUtils.json';
import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json';
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
Expand All @@ -14,6 +15,7 @@ import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExc
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
export const artifacts = {
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
ICurve: ICurve as ContractArtifact,
IDevUtils: IDevUtils as ContractArtifact,
IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact,
IEth2Dai: IEth2Dai as ContractArtifact,
Expand Down
39 changes: 39 additions & 0 deletions contracts/erc20-bridge-sampler/test/mainnet_tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { blockchainTests, describe, expect, toBaseUnitAmount } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';

import { artifacts } from './artifacts';
import { ERC20BridgeSamplerContract } from './generated-wrappers/erc20_bridge_sampler';

blockchainTests.fork.skip('Mainnet Sampler Tests', env => {
dekz marked this conversation as resolved.
Show resolved Hide resolved
let testContract: ERC20BridgeSamplerContract;
before(async () => {
testContract = await ERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
artifacts.ERC20BridgeSampler,
env.provider,
{ ...env.txDefaults, from: '0x6cc5f688a315f3dc28a7781717a9a798a59fda7b' },
{},
);
});

describe('sampleSellsFromCurve()', () => {
const curveAddress = '0x2e60CF74d81ac34eB21eEff58Db4D385920ef419';
const daiTokenIdx = new BigNumber(0);
const usdcTokenIdx = new BigNumber(1);

it('samples sells from Curve DAI->USDC', async () => {
const samples = await testContract
.sampleSellsFromCurve(curveAddress, daiTokenIdx, usdcTokenIdx, [toBaseUnitAmount(1)])
.callAsync();
expect(samples.length).to.be.bignumber.greaterThan(0);
expect(samples[0]).to.be.bignumber.greaterThan(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess one of the things I get paranoid about is whether these endpoints are expressing the correct units. So maybe we can check that samples[0] > 0 && samples[0] < toBaseUnitAmount(2, 6)

});

it('samples sells from Curve USDC->DAI', async () => {
const samples = await testContract
.sampleSellsFromCurve(curveAddress, usdcTokenIdx, daiTokenIdx, [toBaseUnitAmount(1, 6)])
.callAsync();
expect(samples.length).to.be.bignumber.greaterThan(0);
expect(samples[0]).to.be.bignumber.greaterThan(0);
});
});
});
1 change: 1 addition & 0 deletions contracts/erc20-bridge-sampler/test/wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* -----------------------------------------------------------------------------
*/
export * from '../test/generated-wrappers/erc20_bridge_sampler';
export * from '../test/generated-wrappers/i_curve';
export * from '../test/generated-wrappers/i_dev_utils';
export * from '../test/generated-wrappers/i_erc20_bridge_sampler';
export * from '../test/generated-wrappers/i_eth2_dai';
Expand Down
1 change: 1 addition & 0 deletions contracts/erc20-bridge-sampler/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"generated-artifacts/ERC20BridgeSampler.json",
"generated-artifacts/IERC20BridgeSampler.json",
"test/generated-artifacts/ERC20BridgeSampler.json",
"test/generated-artifacts/ICurve.json",
"test/generated-artifacts/IDevUtils.json",
"test/generated-artifacts/IERC20BridgeSampler.json",
"test/generated-artifacts/IEth2Dai.json",
Expand Down
18 changes: 18 additions & 0 deletions packages/asset-swapper/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from './types';

import { constants as marketOperationUtilConstants } from './utils/market_operation_utils/constants';
import { ERC20BridgeSource } from './utils/market_operation_utils/types';

const ETH_GAS_STATION_API_BASE_URL = 'https://ethgasstation.info';
const NULL_BYTES = '0x';
Expand Down Expand Up @@ -64,6 +65,22 @@ const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = {
...marketOperationUtilConstants.DEFAULT_GET_MARKET_ORDERS_OPTS,
};

// Mainnet Curve configuration
const DEFAULT_CURVE_OPTS: { [source: string]: { curveAddress: string; tokens: string[] } } = {
[ERC20BridgeSource.CurveUsdcDai]: {
curveAddress: '0x2e60cf74d81ac34eb21eeff58db4d385920ef419',
tokens: ['0x6b175474e89094c44da98b954eedeac495271d0f', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'],
},
[ERC20BridgeSource.CurveUsdcDaiUsdt]: {
curveAddress: '0x52ea46506b9cc5ef470c5bf89f17dc28bb35d85c',
tokens: [
'0x6b175474e89094c44da98b954eedeac495271d0f',
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
'0xdac17f958d2ee523a2206206994597c13d831ec7',
],
},
};

export const constants = {
ETH_GAS_STATION_API_BASE_URL,
PROTOCOL_FEE_MULTIPLIER,
Expand All @@ -84,4 +101,5 @@ export const constants = {
PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE,
BRIDGE_ASSET_DATA_PREFIX: '0xdc1600f3',
DEFAULT_CURVE_OPTS,
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ const INFINITE_TIMESTAMP_SEC = new BigNumber(2524604400);
/**
* Valid sources for market sell.
*/
export const SELL_SOURCES = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Kyber];
export const SELL_SOURCES = [
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.Kyber,
ERC20BridgeSource.CurveUsdcDai,
ERC20BridgeSource.CurveUsdcDaiUsdt,
];

/**
* Valid sources for market buy.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ export class CreateOrderUtils {
return this._contractAddress.kyberBridge;
case ERC20BridgeSource.Uniswap:
return this._contractAddress.uniswapBridge;
case ERC20BridgeSource.CurveUsdcDai:
case ERC20BridgeSource.CurveUsdcDaiUsdt:
return this._contractAddress.curveBridge;
default:
break;
}
Expand All @@ -106,13 +109,26 @@ function createBridgeOrder(
slippage: number,
isBuy: boolean = false,
): OptimizedMarketOrder {
return {
makerAddress: bridgeAddress,
makerAssetData: assetDataUtils.encodeERC20BridgeAssetData(
let makerAssetData;
if (fill.source === ERC20BridgeSource.CurveUsdcDai || fill.source === ERC20BridgeSource.CurveUsdcDaiUsdt) {
const { curveAddress, tokens } = constants.DEFAULT_CURVE_OPTS[fill.source];
const fromTokenIdx = tokens.indexOf(takerToken);
const toTokenIdx = tokens.indexOf(takerToken);
dekz marked this conversation as resolved.
Show resolved Hide resolved
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
bridgeAddress,
createCurveBridgeData(curveAddress, fromTokenIdx, toTokenIdx),
);
} else {
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
bridgeAddress,
createBridgeData(takerToken),
),
);
}
return {
makerAddress: bridgeAddress,
makerAssetData,
takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken),
...createCommonOrderFields(orderDomain, fill, slippage, isBuy),
};
Expand All @@ -123,6 +139,15 @@ function createBridgeData(tokenAddress: string): string {
return encoder.encode({ tokenAddress });
}

function createCurveBridgeData(curveAddress: string, fromTokenIdx: number, toTokenIdx: number): string {
const curveBridgeDataEncoder = AbiEncoder.create([
{ name: 'curveAddress', type: 'address' },
{ name: 'fromTokenIdx', type: 'int128' },
{ name: 'toTokenIdx', type: 'int128' },
]);
return curveBridgeDataEncoder.encode([curveAddress, fromTokenIdx, toTokenIdx]);
}

type CommonOrderFields = Pick<
OptimizedMarketOrder,
Exclude<keyof OptimizedMarketOrder, 'makerAddress' | 'makerAssetData' | 'takerAssetData'>
Expand Down
Loading