batch requests with bancor SDK (#2699)

* upgrade bancor SDK for batch requests

* lint

* changes after review

* deploy bancor bridge

* small fixes
This commit is contained in:
Xianny 2020-09-11 11:09:10 -07:00 committed by GitHub
parent 0ba79b060d
commit 3da05f2812
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 129 additions and 90 deletions

View File

@ -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",

View File

@ -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,

View File

@ -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<SDK> {
if (!this._sdk) {
this._sdk = await SDK.create({ ethereumNodeEndpoint: this.provider });
}
return this._sdk;
public static async createAsync(provider: SupportedProvider): Promise<BancorService> {
const sdk = await SDK.create({ ethereumNodeEndpoint: provider });
const service = new BancorService(sdk);
return service;
}
public async getQuoteAsync(fromToken: string, toToken: string, amount: BigNumber): Promise<Quote<BancorFillData>> {
const sdk = await this.getSDKAsync();
constructor(public sdk: SDK) {}
public async getQuotesAsync(
fromToken: string,
toToken: string,
amounts: BigNumber[],
): Promise<Array<Quote<BancorFillData>>> {
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<string> {
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;
}
}

View File

@ -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,

View File

@ -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],

View File

@ -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. */

View File

@ -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<T>(result: T): BatchedOperation<T> {
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<BancorService> {
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<BigNumber[]> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Native,
@ -467,33 +483,23 @@ export class SamplerOperations {
takerToken: string,
takerFillAmounts: BigNumber[],
): Promise<Array<DexSample<BancorFillData>>> {
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(

View File

@ -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)}`,
);

View File

@ -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,

View File

@ -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<Quote<BancorFillData>>;
getQuotesAsync: (fromToken: string, toToken: string, amount: BigNumber[]) => Promise<Array<Quote<BancorFillData>>>;
}
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<Handlers>) {
super(provider);
public static async createMockAsync(handlers: Partial<Handlers>): Promise<MockBancorService> {
const sdk = new SDK();
return new MockBancorService(sdk, handlers);
}
public async getQuoteAsync(fromToken: string, toToken: string, amount: BigNumber): Promise<Quote<BancorFillData>> {
return this.handlers.getQuoteAsync
? this.handlers.getQuoteAsync(fromToken, toToken, amount)
: super.getQuoteAsync(fromToken, toToken, amount);
constructor(sdk: SDK, public handlers: Partial<Handlers>) {
super(sdk);
}
public async getQuotesAsync(
fromToken: string,
toToken: string,
amounts: BigNumber[],
): Promise<Array<Quote<BancorFillData>>> {
return this.handlers.getQuotesAsync
? this.handlers.getQuotesAsync(fromToken, toToken, amounts)
: super.getQuotesAsync(fromToken, toToken, amounts);
}
}

View File

@ -45,6 +45,10 @@
{
"note": "Update transformer deployer and transformers for champagne-problems deployment",
"pr": 2693
},
{
"note": "Deploy `BancorBridge` on Mainnet",
"pr": 2699
}
]
},

View File

@ -34,7 +34,7 @@
"dexForwarderBridge": "0xc47b7094f378e54347e281aab170e8cca69d880a",
"multiBridge": "0xc03117a8c9bde203f70aa911cb64a7a0df5ba1e1",
"balancerBridge": "0xfe01821ca163844203220cd08e4f2b2fb43ae4e4",
"bancorBridge": "0x0000000000000000000000000000000000000000",
"bancorBridge": "0x259897d9699553edbdf8538599242354e957fb94",
"exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e",
"exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",

View File

@ -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"