From 3da05f28126558f64e389b4a0ee851c8885df64a Mon Sep 17 00:00:00 2001 From: Xianny <8582774+xianny@users.noreply.github.com> Date: Fri, 11 Sep 2020 11:09:10 -0700 Subject: [PATCH] batch requests with bancor SDK (#2699) * upgrade bancor SDK for batch requests * lint * changes after review * deploy bancor bridge * small fixes --- packages/asset-swapper/package.json | 3 +- packages/asset-swapper/src/swap_quoter.ts | 3 +- .../market_operation_utils/bancor_service.ts | 52 +++++++++------- .../utils/market_operation_utils/constants.ts | 2 +- .../src/utils/market_operation_utils/index.ts | 2 +- .../utils/market_operation_utils/sampler.ts | 6 +- .../sampler_operations.ts | 60 ++++++++++--------- .../asset-swapper/test/bancor_service_test.ts | 37 ++++++++---- .../asset-swapper/test/dex_sampler_test.ts | 17 +++--- .../test/utils/mock_bancor_service.ts | 25 +++++--- packages/contract-addresses/CHANGELOG.json | 4 ++ packages/contract-addresses/addresses.json | 2 +- yarn.lock | 6 +- 13 files changed, 129 insertions(+), 90 deletions(-) diff --git a/packages/asset-swapper/package.json b/packages/asset-swapper/package.json index 9674e291e0..293c1a5ee0 100644 --- a/packages/asset-swapper/package.json +++ b/packages/asset-swapper/package.json @@ -61,7 +61,6 @@ "@0x/base-contract": "^6.2.3", "@0x/contract-addresses": "^4.11.0", "@0x/contract-wrappers": "^13.8.0", - "@0x/contracts-erc20-bridge-sampler": "^1.7.0", "@0x/json-schemas": "^5.1.0", "@0x/order-utils": "^10.3.0", "@0x/orderbook": "^2.2.7", @@ -71,7 +70,7 @@ "@0x/utils": "^5.5.1", "@0x/web3-wrapper": "^7.2.0", "@balancer-labs/sor": "0.3.2", - "@bancor/sdk": "^0.2.5", + "@bancor/sdk": "^0.2.9", "axios": "^0.19.2", "axios-mock-adapter": "^1.18.1", "decimal.js": "^10.2.0", diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 9baae9deab..939aaa5d01 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -25,7 +25,6 @@ import { import { assert } from './utils/assert'; import { calculateLiquidity } from './utils/calculate_liquidity'; import { MarketOperationUtils } from './utils/market_operation_utils'; -import { BancorService } from './utils/market_operation_utils/bancor_service'; import { createDummyOrderForSampler } from './utils/market_operation_utils/orders'; import { DexOrderSampler } from './utils/market_operation_utils/sampler'; import { @@ -204,7 +203,7 @@ export class SwapQuoter { }, ); this._marketOperationUtils = new MarketOperationUtils( - new DexOrderSampler(samplerContract, samplerOverrides, new BancorService(provider)), + new DexOrderSampler(samplerContract, samplerOverrides, provider), this._contractAddresses, { chainId, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/bancor_service.ts b/packages/asset-swapper/src/utils/market_operation_utils/bancor_service.ts index 95ddbb715b..dfb5059da6 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/bancor_service.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/bancor_service.ts @@ -20,40 +20,46 @@ export function token(address: string, blockchainType: BlockchainType = Blockcha export class BancorService { // Bancor recommends setting this value to 2% under the expected return amount public minReturnAmountBufferPercentage = 0.99; - private _sdk?: SDK; - constructor(public provider: SupportedProvider) {} - - public async getSDKAsync(): Promise { - if (!this._sdk) { - this._sdk = await SDK.create({ ethereumNodeEndpoint: this.provider }); - } - return this._sdk; + public static async createAsync(provider: SupportedProvider): Promise { + const sdk = await SDK.create({ ethereumNodeEndpoint: provider }); + const service = new BancorService(sdk); + return service; } - public async getQuoteAsync(fromToken: string, toToken: string, amount: BigNumber): Promise> { - const sdk = await this.getSDKAsync(); + constructor(public sdk: SDK) {} + + public async getQuotesAsync( + fromToken: string, + toToken: string, + amounts: BigNumber[], + ): Promise>> { + const sdk = this.sdk; const blockchain = sdk._core.blockchains[BlockchainType.Ethereum] as Ethereum; const sourceDecimals = await getDecimals(blockchain, fromToken); - const { path, rate } = await sdk.pricing.getPathAndRate( + const quotes = await sdk.pricing.getPathAndRates( token(fromToken), token(toToken), - fromWei(amount.toString(), sourceDecimals), + amounts.map(amt => fromWei(amt.toString(), sourceDecimals)), ); const targetDecimals = await getDecimals(blockchain, toToken); - const output = toWei(rate, targetDecimals); - return { - amount: new BigNumber(output).multipliedBy(this.minReturnAmountBufferPercentage).dp(0), - fillData: { - path: path.map(p => p.blockchainId), - networkAddress: await this.getBancorNetworkAddressAsync(), - }, - }; + const networkAddress = this.getBancorNetworkAddress(); + + return quotes.map(quote => { + const { path, rate } = quote; + const output = toWei(rate, targetDecimals); + return { + amount: new BigNumber(output).multipliedBy(this.minReturnAmountBufferPercentage).dp(0), + fillData: { + path: path.map(p => p.blockchainId), + networkAddress, + }, + }; + }); } - public async getBancorNetworkAddressAsync(): Promise { - const sdk = await this.getSDKAsync(); - const blockchain = sdk._core.blockchains[BlockchainType.Ethereum] as Ethereum; + public getBancorNetworkAddress(): string { + const blockchain = this.sdk._core.blockchains[BlockchainType.Ethereum] as Ethereum; return blockchain.bancorNetwork._address; } } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts index 30d46a49a4..ccb8081290 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -14,7 +14,7 @@ export const SELL_SOURCES = [ ERC20BridgeSource.Kyber, ERC20BridgeSource.Curve, ERC20BridgeSource.Balancer, - // ERC20BridgeSource.Bancor, // FIXME: Disabled until Bancor SDK supports batch requests + ERC20BridgeSource.Bancor, ERC20BridgeSource.MStable, ERC20BridgeSource.Mooniswap, ERC20BridgeSource.Swerve, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/index.ts b/packages/asset-swapper/src/utils/market_operation_utils/index.ts index 3c30f00861..1dea80f1f9 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -178,7 +178,7 @@ export class MarketOperationUtils { const offChainBancorPromise = _opts.excludedSources.includes(ERC20BridgeSource.Bancor) ? Promise.resolve([]) - : this._sampler.getBancorSellQuotesOffChainAsync(makerToken, takerToken, [takerAmount]); + : this._sampler.getBancorSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts); const [ [orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes], diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts index 4f2c9ae3d9..d17bbd6831 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts @@ -1,3 +1,4 @@ +import { SupportedProvider } from '@0x/dev-utils'; import { BigNumber, NULL_BYTES } from '@0x/utils'; import { SamplerOverrides } from '../../types'; @@ -34,10 +35,11 @@ export class DexOrderSampler extends SamplerOperations { constructor( _samplerContract: ERC20BridgeSamplerContract, private readonly _samplerOverrides?: SamplerOverrides, - bancorService?: BancorService, + provider?: SupportedProvider, balancerPoolsCache?: BalancerPoolsCache, + getBancorServiceFn?: () => BancorService, ) { - super(_samplerContract, bancorService, balancerPoolsCache); + super(_samplerContract, provider, balancerPoolsCache, getBancorServiceFn); } /* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */ diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts index 20ca968cf6..71df073f26 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts @@ -1,3 +1,4 @@ +import { SupportedProvider } from '@0x/dev-utils'; import { SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; @@ -41,6 +42,7 @@ import { * for use with `DexOrderSampler.executeAsync()`. */ export class SamplerOperations { + protected _bancorService?: BancorService; public static constant(result: T): BatchedOperation { return { encodeCall: () => { @@ -54,10 +56,24 @@ export class SamplerOperations { constructor( protected readonly _samplerContract: ERC20BridgeSamplerContract, - public readonly bancorService?: BancorService, + public readonly provider?: SupportedProvider, public readonly balancerPoolsCache: BalancerPoolsCache = new BalancerPoolsCache(), + protected readonly getBancorServiceFn?: () => BancorService, // for dependency injection in tests ) {} + public async getBancorServiceAsync(): Promise { + if (this.getBancorServiceFn !== undefined) { + return this.getBancorServiceFn(); + } + if (this.provider === undefined) { + throw new Error('Cannot sample liquidity from Bancor; no provider supplied.'); + } + if (this._bancorService === undefined) { + this._bancorService = await BancorService.createAsync(this.provider); + } + return this._bancorService; + } + public getOrderFillableTakerAmounts(orders: SignedOrder[], exchangeAddress: string): BatchedOperation { return new SamplerContractOperation({ source: ERC20BridgeSource.Native, @@ -467,33 +483,23 @@ export class SamplerOperations { takerToken: string, takerFillAmounts: BigNumber[], ): Promise>> { - if (this.bancorService === undefined) { - throw new Error('Cannot sample liquidity from Bancor; no Bancor service instantiated.'); + const bancorService = await this.getBancorServiceAsync(); + try { + const quotes = await bancorService.getQuotesAsync(takerToken, makerToken, takerFillAmounts); + return quotes.map((quote, i) => ({ + source: ERC20BridgeSource.Bancor, + output: quote.amount, + input: takerFillAmounts[i], + fillData: quote.fillData, + })); + } catch (e) { + return takerFillAmounts.map(input => ({ + source: ERC20BridgeSource.Bancor, + output: ZERO_AMOUNT, + input, + fillData: { path: [], networkAddress: '' }, + })); } - return Promise.all( - takerFillAmounts.map(async amount => { - try { - const { amount: output, fillData } = await this.bancorService!.getQuoteAsync( - takerToken, - makerToken, - amount, - ); - return { - source: ERC20BridgeSource.Bancor, - output, - input: amount, - fillData, - }; - } catch (e) { - return { - source: ERC20BridgeSource.Bancor, - output: ZERO_AMOUNT, - input: amount, - fillData: { path: [], networkAddress: '' }, - }; - } - }), - ); } public getMooniswapSellQuotes( diff --git a/packages/asset-swapper/test/bancor_service_test.ts b/packages/asset-swapper/test/bancor_service_test.ts index 3c4af4450a..e11053ef9d 100644 --- a/packages/asset-swapper/test/bancor_service_test.ts +++ b/packages/asset-swapper/test/bancor_service_test.ts @@ -1,4 +1,7 @@ import { web3Factory, Web3ProviderEngine } from '@0x/dev-utils'; +import { Ethereum, getDecimals } from '@bancor/sdk/dist/blockchains/ethereum'; +import { fromWei, toWei } from '@bancor/sdk/dist/helpers'; +import { BlockchainType } from '@bancor/sdk/dist/types'; import * as chai from 'chai'; import * as _ from 'lodash'; import 'mocha'; @@ -17,41 +20,49 @@ const RPC_URL = `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`; const provider: Web3ProviderEngine = web3Factory.getRpcProvider({ rpcUrl: RPC_URL }); // tslint:disable:custom-no-magic-numbers +let bancorService: BancorService; + // These tests test the bancor SDK against mainnet // TODO (xianny): After we move asset-swapper out of the monorepo, we should add an env variable to circle CI to run this test describe.skip('Bancor Service', () => { - const bancorService = new BancorService(provider); const eth = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; const bnt = '0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c'; it('should retrieve the bancor network address', async () => { - const networkAddress = await bancorService.getBancorNetworkAddressAsync(); + bancorService = await BancorService.createAsync(provider); + const networkAddress = bancorService.getBancorNetworkAddress(); expect(networkAddress).to.match(ADDRESS_REGEX); }); it('should retrieve a quote', async () => { - const amt = new BigNumber(2); - const quote = await bancorService.getQuoteAsync(eth, bnt, amt); - const fillData = quote.fillData as BancorFillData; + const amt = new BigNumber(10e18); + const quotes = await bancorService.getQuotesAsync(eth, bnt, [amt]); + const fillData = quotes[0].fillData as BancorFillData; // get rate from the bancor sdk - const sdk = await bancorService.getSDKAsync(); - const expectedAmt = await sdk.pricing.getRateByPath(fillData.path.map(s => token(s)), amt.toString()); + const blockchain = bancorService.sdk._core.blockchains[BlockchainType.Ethereum] as Ethereum; + const sourceDecimals = await getDecimals(blockchain, token(eth)); + const rate = await bancorService.sdk.pricing.getRateByPath( + fillData.path.map(p => token(p)), + fromWei(amt.toString(), sourceDecimals), + ); + const expectedRate = toWei(rate, await getDecimals(blockchain, token(bnt))); expect(fillData.networkAddress).to.match(ADDRESS_REGEX); - expect(fillData.path).to.be.an.instanceOf(Array); - expect(fillData.path).to.have.lengthOf(3); - expect(quote.amount.dp(0)).to.bignumber.eq( - new BigNumber(expectedAmt).multipliedBy(bancorService.minReturnAmountBufferPercentage).dp(0), + expect(fillData.path[0].toLowerCase()).to.eq(eth); + expect(fillData.path[2].toLowerCase()).to.eq(bnt); + expect(fillData.path.length).to.eq(3); // eth -> bnt should be single hop! + expect(quotes[0].amount.dp(0)).to.bignumber.eq( + new BigNumber(expectedRate).multipliedBy(bancorService.minReturnAmountBufferPercentage).dp(0), ); }); // HACK (xianny): for exploring SDK results it('should retrieve multiple quotes', async () => { const amts = [1, 10, 100, 1000].map(a => new BigNumber(a).multipliedBy(10e18)); - const quotes = await Promise.all(amts.map(async amount => bancorService.getQuoteAsync(eth, bnt, amount))); + const quotes = await bancorService.getQuotesAsync(eth, bnt, amts); quotes.map((q, i) => { // tslint:disable:no-console const fillData = q.fillData as BancorFillData; console.log( - `Input ${amts[i].toExponential()}; Output: ${q.amount}; Path: ${ + `Input ${amts[i].toExponential()}; Output: ${q.amount}; Ratio: ${q.amount.dividedBy(amts[i])}, Path: ${ fillData.path.length }\nPath: ${JSON.stringify(fillData.path)}`, ); diff --git a/packages/asset-swapper/test/dex_sampler_test.ts b/packages/asset-swapper/test/dex_sampler_test.ts index 3fb83b18d6..b267e0d170 100644 --- a/packages/asset-swapper/test/dex_sampler_test.ts +++ b/packages/asset-swapper/test/dex_sampler_test.ts @@ -487,21 +487,24 @@ describe('DexSampler tests', () => { const networkAddress = randomAddress(); const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3); const rate = getRandomFloat(0, 100); - const bancorService = new MockBancorService(provider, { - getQuoteAsync: async (fromToken: string, toToken: string, amount: BigNumber) => { + const bancorService = await MockBancorService.createMockAsync({ + getQuotesAsync: async (fromToken: string, toToken: string, amounts: BigNumber[]) => { expect(fromToken).equal(expectedTakerToken); expect(toToken).equal(expectedMakerToken); - return Promise.resolve({ - fillData: { path: [fromToken, toToken], networkAddress }, - amount: amount.multipliedBy(rate), - }); + return Promise.resolve( + amounts.map(a => ({ + fillData: { path: [fromToken, toToken], networkAddress }, + amount: a.multipliedBy(rate), + })), + ); }, }); const dexOrderSampler = new DexOrderSampler( new MockSamplerContract({}), undefined, // sampler overrides - bancorService, + provider, undefined, // balancer cache + () => bancorService, ); const quotes = await dexOrderSampler.getBancorSellQuotesOffChainAsync( expectedMakerToken, diff --git a/packages/asset-swapper/test/utils/mock_bancor_service.ts b/packages/asset-swapper/test/utils/mock_bancor_service.ts index 5952301190..b3810d8ad5 100644 --- a/packages/asset-swapper/test/utils/mock_bancor_service.ts +++ b/packages/asset-swapper/test/utils/mock_bancor_service.ts @@ -1,24 +1,33 @@ import { BigNumber } from '@0x/utils'; +import { SDK } from '@bancor/sdk'; -import { SupportedProvider } from '../../src'; import { BancorService } from '../../src/utils/market_operation_utils/bancor_service'; import { BancorFillData, Quote } from '../../src/utils/market_operation_utils/types'; export interface Handlers { - getQuoteAsync: (fromToken: string, toToken: string, amount: BigNumber) => Promise>; + getQuotesAsync: (fromToken: string, toToken: string, amount: BigNumber[]) => Promise>>; } export class MockBancorService extends BancorService { // Bancor recommends setting this value to 2% under the expected return amount public minReturnAmountBufferPercentage = 0.98; - constructor(provider: SupportedProvider, public handlers: Partial) { - super(provider); + public static async createMockAsync(handlers: Partial): Promise { + const sdk = new SDK(); + return new MockBancorService(sdk, handlers); } - public async getQuoteAsync(fromToken: string, toToken: string, amount: BigNumber): Promise> { - return this.handlers.getQuoteAsync - ? this.handlers.getQuoteAsync(fromToken, toToken, amount) - : super.getQuoteAsync(fromToken, toToken, amount); + constructor(sdk: SDK, public handlers: Partial) { + super(sdk); + } + + public async getQuotesAsync( + fromToken: string, + toToken: string, + amounts: BigNumber[], + ): Promise>> { + return this.handlers.getQuotesAsync + ? this.handlers.getQuotesAsync(fromToken, toToken, amounts) + : super.getQuotesAsync(fromToken, toToken, amounts); } } diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index 7dfbd991e6..4546251d08 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -45,6 +45,10 @@ { "note": "Update transformer deployer and transformers for champagne-problems deployment", "pr": 2693 + }, + { + "note": "Deploy `BancorBridge` on Mainnet", + "pr": 2699 } ] }, diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index d3e1ad31dd..0791047775 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -34,7 +34,7 @@ "dexForwarderBridge": "0xc47b7094f378e54347e281aab170e8cca69d880a", "multiBridge": "0xc03117a8c9bde203f70aa911cb64a7a0df5ba1e1", "balancerBridge": "0xfe01821ca163844203220cd08e4f2b2fb43ae4e4", - "bancorBridge": "0x0000000000000000000000000000000000000000", + "bancorBridge": "0x259897d9699553edbdf8538599242354e957fb94", "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", diff --git a/yarn.lock b/yarn.lock index 113f56f24e..f5a89efc2b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1061,9 +1061,9 @@ isomorphic-fetch "^2.2.1" typescript "^3.8.3" -"@bancor/sdk@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@bancor/sdk/-/sdk-0.2.5.tgz#dda2d6a91bc130684d83e5d395c00677cd0fdd71" +"@bancor/sdk@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@bancor/sdk/-/sdk-0.2.9.tgz#2e4c168dc9d667709e3ed85eac3b15362c5676d8" dependencies: "@types/text-encoding" "0.0.35" decimal.js "^10.2.0"