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:
parent
0ba79b060d
commit
3da05f2812
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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],
|
||||
|
@ -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. */
|
||||
|
@ -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(
|
||||
|
@ -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)}`,
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,10 @@
|
||||
{
|
||||
"note": "Update transformer deployer and transformers for champagne-problems deployment",
|
||||
"pr": 2693
|
||||
},
|
||||
{
|
||||
"note": "Deploy `BancorBridge` on Mainnet",
|
||||
"pr": 2699
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -34,7 +34,7 @@
|
||||
"dexForwarderBridge": "0xc47b7094f378e54347e281aab170e8cca69d880a",
|
||||
"multiBridge": "0xc03117a8c9bde203f70aa911cb64a7a0df5ba1e1",
|
||||
"balancerBridge": "0xfe01821ca163844203220cd08e4f2b2fb43ae4e4",
|
||||
"bancorBridge": "0x0000000000000000000000000000000000000000",
|
||||
"bancorBridge": "0x259897d9699553edbdf8538599242354e957fb94",
|
||||
"exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e",
|
||||
"exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
|
||||
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user