* Add BalancerBridge and Sampler functions * Update sampler artifacts/wrappers * Add Balancer support to AssetSwapper + related refactoring * Make use of GraphQL instead of sampler * "fix" build and add mainnet BalancerBridge tests * address some comments * add balancer cache and fix DexSampler tests * lint * wip: tests for balancer sampler ops * Fix market operation utils test * balancer unit tests * Return a buy quote of 0 if the buy amount exceeds the Balancer pool's balance * Dynamic fee estimation * Update contract addresses, export BalancerBridge wrapper * Update changelogs * Fix bugs discovered via simbot * Fix issues in balancer_utils * override `BigNumber.config` in configured_bignumber.ts * Special case Balancer subops in too * Address some more comments * Address Balancer performance issue * Performance improvements * Address comment * Fix tests Co-authored-by: xianny <xianny@gmail.com>
266 lines
8.9 KiB
TypeScript
266 lines
8.9 KiB
TypeScript
import { ContractFunctionObj } from '@0x/base-contract';
|
|
import { IERC20BridgeSamplerContract } from '@0x/contract-wrappers';
|
|
import { constants } from '@0x/contracts-test-utils';
|
|
import { Order } from '@0x/types';
|
|
import { BigNumber, hexUtils } from '@0x/utils';
|
|
|
|
export type GetOrderFillableAssetAmountResult = BigNumber[];
|
|
export type GetOrderFillableAssetAmountHandler = (
|
|
orders: Order[],
|
|
signatures: string[],
|
|
) => GetOrderFillableAssetAmountResult;
|
|
|
|
export type SampleResults = BigNumber[];
|
|
export type SampleSellsHandler = (
|
|
takerToken: string,
|
|
makerToken: string,
|
|
takerTokenAmounts: BigNumber[],
|
|
) => SampleResults;
|
|
export type SampleBuysHandler = (
|
|
takerToken: string,
|
|
makerToken: string,
|
|
makerTokenAmounts: BigNumber[],
|
|
) => SampleResults;
|
|
export type SampleBuysMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
|
|
export type SampleSellsLPHandler = (
|
|
registryAddress: string,
|
|
takerToken: string,
|
|
makerToken: string,
|
|
takerTokenAmounts: BigNumber[],
|
|
) => SampleResults;
|
|
export type SampleSellsMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
|
|
export type SampleSellsMBHandler = (
|
|
multiBridgeAddress: string,
|
|
takerToken: string,
|
|
intermediateToken: string,
|
|
makerToken: string,
|
|
takerTokenAmounts: BigNumber[],
|
|
) => SampleResults;
|
|
|
|
const DUMMY_PROVIDER = {
|
|
sendAsync: (...args: any[]): any => {
|
|
/* no-op */
|
|
},
|
|
};
|
|
|
|
interface Handlers {
|
|
getOrderFillableMakerAssetAmounts: GetOrderFillableAssetAmountHandler;
|
|
getOrderFillableTakerAssetAmounts: GetOrderFillableAssetAmountHandler;
|
|
sampleSellsFromKyberNetwork: SampleSellsHandler;
|
|
sampleSellsFromLiquidityProviderRegistry: SampleSellsLPHandler;
|
|
sampleSellsFromMultiBridge: SampleSellsMBHandler;
|
|
sampleSellsFromEth2Dai: SampleSellsHandler;
|
|
sampleSellsFromUniswap: SampleSellsHandler;
|
|
sampleSellsFromUniswapV2: SampleSellsMultihopHandler;
|
|
sampleBuysFromEth2Dai: SampleBuysHandler;
|
|
sampleBuysFromUniswap: SampleBuysHandler;
|
|
sampleBuysFromUniswapV2: SampleBuysMultihopHandler;
|
|
sampleBuysFromLiquidityProviderRegistry: SampleSellsLPHandler;
|
|
}
|
|
|
|
export class MockSamplerContract extends IERC20BridgeSamplerContract {
|
|
private readonly _handlers: Partial<Handlers> = {};
|
|
|
|
public constructor(handlers: Partial<Handlers> = {}) {
|
|
super(constants.NULL_ADDRESS, DUMMY_PROVIDER);
|
|
this._handlers = handlers;
|
|
}
|
|
|
|
public batchCall(callDatas: string[]): ContractFunctionObj<string[]> {
|
|
return {
|
|
...super.batchCall(callDatas),
|
|
callAsync: async (...callArgs: any[]) => callDatas.map(callData => this._callEncodedFunction(callData)),
|
|
};
|
|
}
|
|
|
|
public getOrderFillableMakerAssetAmounts(
|
|
orders: Order[],
|
|
signatures: string[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.getOrderFillableMakerAssetAmounts,
|
|
this._handlers.getOrderFillableMakerAssetAmounts,
|
|
orders,
|
|
signatures,
|
|
);
|
|
}
|
|
|
|
public getOrderFillableTakerAssetAmounts(
|
|
orders: Order[],
|
|
signatures: string[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.getOrderFillableTakerAssetAmounts,
|
|
this._handlers.getOrderFillableTakerAssetAmounts,
|
|
orders,
|
|
signatures,
|
|
);
|
|
}
|
|
|
|
public sampleSellsFromKyberNetwork(
|
|
takerToken: string,
|
|
makerToken: string,
|
|
takerAssetAmounts: BigNumber[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.sampleSellsFromKyberNetwork,
|
|
this._handlers.sampleSellsFromKyberNetwork,
|
|
takerToken,
|
|
makerToken,
|
|
takerAssetAmounts,
|
|
);
|
|
}
|
|
|
|
public sampleSellsFromEth2Dai(
|
|
takerToken: string,
|
|
makerToken: string,
|
|
takerAssetAmounts: BigNumber[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.sampleSellsFromEth2Dai,
|
|
this._handlers.sampleSellsFromEth2Dai,
|
|
takerToken,
|
|
makerToken,
|
|
takerAssetAmounts,
|
|
);
|
|
}
|
|
|
|
public sampleSellsFromUniswap(
|
|
takerToken: string,
|
|
makerToken: string,
|
|
takerAssetAmounts: BigNumber[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.sampleSellsFromUniswap,
|
|
this._handlers.sampleSellsFromUniswap,
|
|
takerToken,
|
|
makerToken,
|
|
takerAssetAmounts,
|
|
);
|
|
}
|
|
|
|
public sampleSellsFromUniswapV2(
|
|
path: string[],
|
|
takerAssetAmounts: BigNumber[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.sampleSellsFromUniswapV2,
|
|
this._handlers.sampleSellsFromUniswapV2,
|
|
path,
|
|
takerAssetAmounts,
|
|
);
|
|
}
|
|
|
|
public sampleSellsFromLiquidityProviderRegistry(
|
|
registryAddress: string,
|
|
takerToken: string,
|
|
makerToken: string,
|
|
takerAssetAmounts: BigNumber[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.sampleSellsFromLiquidityProviderRegistry,
|
|
this._handlers.sampleSellsFromLiquidityProviderRegistry,
|
|
registryAddress,
|
|
takerToken,
|
|
makerToken,
|
|
takerAssetAmounts,
|
|
);
|
|
}
|
|
|
|
public sampleSellsFromMultiBridge(
|
|
multiBridgeAddress: string,
|
|
takerToken: string,
|
|
intermediateToken: string,
|
|
makerToken: string,
|
|
takerAssetAmounts: BigNumber[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.sampleSellsFromMultiBridge,
|
|
this._handlers.sampleSellsFromMultiBridge,
|
|
multiBridgeAddress,
|
|
takerToken,
|
|
intermediateToken,
|
|
makerToken,
|
|
takerAssetAmounts,
|
|
);
|
|
}
|
|
|
|
public sampleBuysFromEth2Dai(
|
|
takerToken: string,
|
|
makerToken: string,
|
|
makerAssetAmounts: BigNumber[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.sampleBuysFromEth2Dai,
|
|
this._handlers.sampleBuysFromEth2Dai,
|
|
takerToken,
|
|
makerToken,
|
|
makerAssetAmounts,
|
|
);
|
|
}
|
|
|
|
public sampleBuysFromUniswap(
|
|
takerToken: string,
|
|
makerToken: string,
|
|
makerAssetAmounts: BigNumber[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.sampleBuysFromUniswap,
|
|
this._handlers.sampleBuysFromUniswap,
|
|
takerToken,
|
|
makerToken,
|
|
makerAssetAmounts,
|
|
);
|
|
}
|
|
|
|
public sampleBuysFromUniswapV2(
|
|
path: string[],
|
|
makerAssetAmounts: BigNumber[],
|
|
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
|
return this._wrapCall(
|
|
super.sampleBuysFromUniswapV2,
|
|
this._handlers.sampleBuysFromUniswapV2,
|
|
path,
|
|
makerAssetAmounts,
|
|
);
|
|
}
|
|
|
|
private _callEncodedFunction(callData: string): string {
|
|
if (callData === '0x') {
|
|
return callData;
|
|
}
|
|
// tslint:disable-next-line: custom-no-magic-numbers
|
|
const selector = hexUtils.slice(callData, 0, 4);
|
|
for (const [name, handler] of Object.entries(this._handlers)) {
|
|
if (handler && this.getSelector(name) === selector) {
|
|
const args = this.getABIDecodedTransactionData<any>(name, callData);
|
|
const result = (handler as any)(...args);
|
|
return this._lookupAbiEncoder(this.getFunctionSignature(name)).encodeReturnValues([result]);
|
|
}
|
|
}
|
|
if (selector === this.getSelector('batchCall')) {
|
|
const calls = this.getABIDecodedTransactionData<string[]>('batchCall', callData);
|
|
const results = calls.map(cd => this._callEncodedFunction(cd));
|
|
return this._lookupAbiEncoder(this.getFunctionSignature('batchCall')).encodeReturnValues([results]);
|
|
}
|
|
throw new Error(`Unkown selector: ${selector}`);
|
|
}
|
|
|
|
private _wrapCall<TArgs extends any[], TResult>(
|
|
superFn: (this: MockSamplerContract, ...args: TArgs) => ContractFunctionObj<TResult>,
|
|
handler?: (this: MockSamplerContract, ...args: TArgs) => TResult,
|
|
// tslint:disable-next-line: trailing-comma
|
|
...args: TArgs
|
|
): ContractFunctionObj<TResult> {
|
|
return {
|
|
...superFn.call(this, ...args),
|
|
callAsync: async (...callArgs: any[]): Promise<TResult> => {
|
|
if (!handler) {
|
|
throw new Error(`${superFn.name} handler undefined`);
|
|
}
|
|
return handler.call(this, ...args);
|
|
},
|
|
};
|
|
}
|
|
}
|