* Draft. PoC pseudo code showing general idea for resuing SOR path creation logic and adding multihop support. * Add actual Balancer SDK function calls. * Update to handle buys. * Correct taker>maker for buy. * Draft. PoC pseudo code showing general idea for resuing SOR path creation logic and adding multihop support. * make it build * rebase * add BalancerV2Batch protocol * add BalancerV2Batch protocol * get balancer v2 multihop working * fix BalancerV2Batch for sells (buys still iffy) * fix buys, appease linter and prettier * remove unused RPC URL from balancer sdk construction * update changelogs * clean up comments add event loop yield in `BalancerV2SwapInfoCache.loadTopPools()` * add negative result check on balancerv2batch swap output * compiler hack * reintroduce CompilerHack * delete unused multibridge sampler * remove compilerhack * reintroduce compilerhack * try to fix CI compile errors * plz work * plz work * pretty plz work * yay it works, also address feedback * appease linter * deploy new FQTs Co-authored-by: johngrantuk <johngrantuk@googlemail.com> Co-authored-by: Lawrence Forman <me@merklejerk.com>
		
			
				
	
	
		
			386 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			386 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { AbiEncoder, BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
 | |
| import * as ethjs from 'ethereumjs-util';
 | |
| 
 | |
| import { LimitOrder, LimitOrderFields, RfqOrder, RfqOrderFields } from './orders';
 | |
| import { Signature, SIGNATURE_ABI } from './signature_utils';
 | |
| 
 | |
| const BRIDGE_ORDER_ABI_COMPONENTS = [
 | |
|     { name: 'source', type: 'bytes32' },
 | |
|     { name: 'takerTokenAmount', type: 'uint256' },
 | |
|     { name: 'makerTokenAmount', type: 'uint256' },
 | |
|     { name: 'bridgeData', type: 'bytes' },
 | |
| ];
 | |
| 
 | |
| const LIMIT_ORDER_INFO_ABI_COMPONENTS = [
 | |
|     {
 | |
|         name: 'order',
 | |
|         type: 'tuple',
 | |
|         components: LimitOrder.STRUCT_ABI,
 | |
|     },
 | |
|     {
 | |
|         name: 'signature',
 | |
|         type: 'tuple',
 | |
|         components: SIGNATURE_ABI,
 | |
|     },
 | |
|     { name: 'maxTakerTokenFillAmount', type: 'uint256' },
 | |
| ];
 | |
| 
 | |
| const RFQ_ORDER_INFO_ABI_COMPONENTS = [
 | |
|     {
 | |
|         name: 'order',
 | |
|         type: 'tuple',
 | |
|         components: RfqOrder.STRUCT_ABI,
 | |
|     },
 | |
|     {
 | |
|         name: 'signature',
 | |
|         type: 'tuple',
 | |
|         components: SIGNATURE_ABI,
 | |
|     },
 | |
|     { name: 'maxTakerTokenFillAmount', type: 'uint256' },
 | |
| ];
 | |
| 
 | |
| /**
 | |
|  * ABI encoder for `FillQuoteTransformer.TransformData`
 | |
|  */
 | |
| export const fillQuoteTransformerDataEncoder = AbiEncoder.create([
 | |
|     {
 | |
|         name: 'data',
 | |
|         type: 'tuple',
 | |
|         components: [
 | |
|             { name: 'side', type: 'uint8' },
 | |
|             { name: 'sellToken', type: 'address' },
 | |
|             { name: 'buyToken', type: 'address' },
 | |
|             {
 | |
|                 name: 'bridgeOrders',
 | |
|                 type: 'tuple[]',
 | |
|                 components: BRIDGE_ORDER_ABI_COMPONENTS,
 | |
|             },
 | |
|             {
 | |
|                 name: 'limitOrders',
 | |
|                 type: 'tuple[]',
 | |
|                 components: LIMIT_ORDER_INFO_ABI_COMPONENTS,
 | |
|             },
 | |
|             {
 | |
|                 name: 'rfqOrders',
 | |
|                 type: 'tuple[]',
 | |
|                 components: RFQ_ORDER_INFO_ABI_COMPONENTS,
 | |
|             },
 | |
|             { name: 'fillSequence', type: 'uint8[]' },
 | |
|             { name: 'fillAmount', type: 'uint256' },
 | |
|             { name: 'refundReceiver', type: 'address' },
 | |
|         ],
 | |
|     },
 | |
| ]);
 | |
| 
 | |
| /**
 | |
|  * Market operation for `FillQuoteTransformerData`.
 | |
|  */
 | |
| export enum FillQuoteTransformerSide {
 | |
|     Sell,
 | |
|     Buy,
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * `FillQuoteTransformer.OrderType`
 | |
|  */
 | |
| export enum FillQuoteTransformerOrderType {
 | |
|     Bridge,
 | |
|     Limit,
 | |
|     Rfq,
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * `FillQuoteTransformer.TransformData`
 | |
|  */
 | |
| export interface FillQuoteTransformerData {
 | |
|     side: FillQuoteTransformerSide;
 | |
|     sellToken: string;
 | |
|     buyToken: string;
 | |
|     bridgeOrders: FillQuoteTransformerBridgeOrder[];
 | |
|     limitOrders: FillQuoteTransformerLimitOrderInfo[];
 | |
|     rfqOrders: FillQuoteTransformerRfqOrderInfo[];
 | |
|     fillSequence: FillQuoteTransformerOrderType[];
 | |
|     fillAmount: BigNumber;
 | |
|     refundReceiver: string;
 | |
| }
 | |
| 
 | |
| // tslint:disable: enum-naming
 | |
| /**
 | |
|  * Identifies the DEX protocol used to fill a bridge order.
 | |
|  */
 | |
| export enum BridgeProtocol {
 | |
|     Unknown,
 | |
|     Curve,
 | |
|     UniswapV2,
 | |
|     Uniswap,
 | |
|     Balancer,
 | |
|     Kyber,
 | |
|     Mooniswap,
 | |
|     MStable,
 | |
|     Oasis,
 | |
|     Shell,
 | |
|     Dodo,
 | |
|     DodoV2,
 | |
|     CryptoCom,
 | |
|     Bancor,
 | |
|     CoFiX,
 | |
|     Nerve,
 | |
|     MakerPsm,
 | |
|     BalancerV2,
 | |
|     UniswapV3,
 | |
|     KyberDmm,
 | |
|     CurveV2,
 | |
|     Lido,
 | |
|     Clipper, // Not used: Clipper is now using PLP interface
 | |
|     AaveV2,
 | |
|     Compound,
 | |
|     BalancerV2Batch,
 | |
| }
 | |
| // tslint:enable: enum-naming
 | |
| 
 | |
| /**
 | |
|  * `FillQuoteTransformer.BridgeOrder`
 | |
|  */
 | |
| export interface FillQuoteTransformerBridgeOrder {
 | |
|     // A bytes32 hex where the upper 16 bytes are an int128, right-aligned
 | |
|     // protocol ID and the lower 16 bytes are a bytes16, left-aligned,
 | |
|     // ASCII source name.
 | |
|     source: string;
 | |
|     takerTokenAmount: BigNumber;
 | |
|     makerTokenAmount: BigNumber;
 | |
|     bridgeData: string;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Represents either `FillQuoteTransformer.LimitOrderInfo`
 | |
|  * or `FillQuoteTransformer.RfqOrderInfo`
 | |
|  */
 | |
| interface FillQuoteTransformerNativeOrderInfo<T> {
 | |
|     order: T;
 | |
|     signature: Signature;
 | |
|     maxTakerTokenFillAmount: BigNumber;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * `FillQuoteTransformer.LimitOrderInfo`
 | |
|  */
 | |
| export type FillQuoteTransformerLimitOrderInfo = FillQuoteTransformerNativeOrderInfo<LimitOrderFields>;
 | |
| 
 | |
| /**
 | |
|  * `FillQuoteTransformer.RfqOrderInfo`
 | |
|  */
 | |
| export type FillQuoteTransformerRfqOrderInfo = FillQuoteTransformerNativeOrderInfo<RfqOrderFields>;
 | |
| 
 | |
| /**
 | |
|  * ABI-encode a `FillQuoteTransformer.TransformData` type.
 | |
|  */
 | |
| export function encodeFillQuoteTransformerData(data: FillQuoteTransformerData): string {
 | |
|     return fillQuoteTransformerDataEncoder.encode([data]);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI-decode a `FillQuoteTransformer.TransformData` type.
 | |
|  */
 | |
| export function decodeFillQuoteTransformerData(encoded: string): FillQuoteTransformerData {
 | |
|     return fillQuoteTransformerDataEncoder.decode(encoded).data;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI encoder for `WethTransformer.TransformData`
 | |
|  */
 | |
| export const wethTransformerDataEncoder = AbiEncoder.create([
 | |
|     {
 | |
|         name: 'data',
 | |
|         type: 'tuple',
 | |
|         components: [
 | |
|             { name: 'token', type: 'address' },
 | |
|             { name: 'amount', type: 'uint256' },
 | |
|         ],
 | |
|     },
 | |
| ]);
 | |
| 
 | |
| /**
 | |
|  * `WethTransformer.TransformData`
 | |
|  */
 | |
| export interface WethTransformerData {
 | |
|     token: string;
 | |
|     amount: BigNumber;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI-encode a `WethTransformer.TransformData` type.
 | |
|  */
 | |
| export function encodeWethTransformerData(data: WethTransformerData): string {
 | |
|     return wethTransformerDataEncoder.encode([data]);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI-decode a `WethTransformer.TransformData` type.
 | |
|  */
 | |
| export function decodeWethTransformerData(encoded: string): WethTransformerData {
 | |
|     return wethTransformerDataEncoder.decode(encoded).data;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI encoder for `PayTakerTransformer.TransformData`
 | |
|  */
 | |
| export const payTakerTransformerDataEncoder = AbiEncoder.create([
 | |
|     {
 | |
|         name: 'data',
 | |
|         type: 'tuple',
 | |
|         components: [
 | |
|             { name: 'tokens', type: 'address[]' },
 | |
|             { name: 'amounts', type: 'uint256[]' },
 | |
|         ],
 | |
|     },
 | |
| ]);
 | |
| 
 | |
| /**
 | |
|  * `PayTakerTransformer.TransformData`
 | |
|  */
 | |
| export interface PayTakerTransformerData {
 | |
|     tokens: string[];
 | |
|     amounts: BigNumber[];
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI-encode a `PayTakerTransformer.TransformData` type.
 | |
|  */
 | |
| export function encodePayTakerTransformerData(data: PayTakerTransformerData): string {
 | |
|     return payTakerTransformerDataEncoder.encode([data]);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI-decode a `PayTakerTransformer.TransformData` type.
 | |
|  */
 | |
| export function decodePayTakerTransformerData(encoded: string): PayTakerTransformerData {
 | |
|     return payTakerTransformerDataEncoder.decode(encoded).data;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI encoder for `affiliateFeetransformer.TransformData`
 | |
|  */
 | |
| export const affiliateFeeTransformerDataEncoder = AbiEncoder.create({
 | |
|     name: 'data',
 | |
|     type: 'tuple',
 | |
|     components: [
 | |
|         {
 | |
|             name: 'fees',
 | |
|             type: 'tuple[]',
 | |
|             components: [
 | |
|                 { name: 'token', type: 'address' },
 | |
|                 { name: 'amount', type: 'uint256' },
 | |
|                 { name: 'recipient', type: 'address' },
 | |
|             ],
 | |
|         },
 | |
|     ],
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * `AffiliateFeeTransformer.TransformData`
 | |
|  */
 | |
| export interface AffiliateFeeTransformerData {
 | |
|     fees: Array<{
 | |
|         token: string;
 | |
|         amount: BigNumber;
 | |
|         recipient: string;
 | |
|     }>;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI-encode a `AffiliateFeeTransformer.TransformData` type.
 | |
|  */
 | |
| export function encodeAffiliateFeeTransformerData(data: AffiliateFeeTransformerData): string {
 | |
|     return affiliateFeeTransformerDataEncoder.encode(data);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI-decode a `AffiliateFeeTransformer.TransformData` type.
 | |
|  */
 | |
| export function decodeAffiliateFeeTransformerData(encoded: string): AffiliateFeeTransformerData {
 | |
|     return affiliateFeeTransformerDataEncoder.decode(encoded);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Find the nonce for a transformer given its deployer.
 | |
|  * If `deployer` is the null address, zero will always be returned.
 | |
|  */
 | |
| export function findTransformerNonce(
 | |
|     transformer: string,
 | |
|     deployer: string = NULL_ADDRESS,
 | |
|     maxGuesses: number = 1024,
 | |
| ): number {
 | |
|     if (deployer === NULL_ADDRESS) {
 | |
|         return 0;
 | |
|     }
 | |
|     const lowercaseTransformer = transformer.toLowerCase();
 | |
|     // Try to guess the nonce.
 | |
|     for (let nonce = 0; nonce < maxGuesses; ++nonce) {
 | |
|         const deployedAddress = getTransformerAddress(deployer, nonce);
 | |
|         if (deployedAddress === lowercaseTransformer) {
 | |
|             return nonce;
 | |
|         }
 | |
|     }
 | |
|     throw new Error(`${deployer} did not deploy ${transformer}!`);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Compute the deployed address for a transformer given a deployer and nonce.
 | |
|  */
 | |
| export function getTransformerAddress(deployer: string, nonce: number): string {
 | |
|     return ethjs.bufferToHex(
 | |
|         // tslint:disable-next-line: custom-no-magic-numbers
 | |
|         ethjs.rlphash([deployer, nonce] as any).slice(12),
 | |
|     );
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI encoder for `PositiveSlippageFeeTransformer.TransformData`
 | |
|  */
 | |
| export const positiveSlippageFeeTransformerDataEncoder = AbiEncoder.create({
 | |
|     name: 'data',
 | |
|     type: 'tuple',
 | |
|     components: [
 | |
|         { name: 'token', type: 'address' },
 | |
|         { name: 'bestCaseAmount', type: 'uint256' },
 | |
|         { name: 'recipient', type: 'address' },
 | |
|     ],
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * `PositiveSlippageFeeTransformer.TransformData`
 | |
|  */
 | |
| export interface PositiveSlippageFeeTransformerData {
 | |
|     token: string;
 | |
|     bestCaseAmount: BigNumber;
 | |
|     recipient: string;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI-encode a `PositiveSlippageFeeTransformer.TransformData` type.
 | |
|  */
 | |
| export function encodePositiveSlippageFeeTransformerData(data: PositiveSlippageFeeTransformerData): string {
 | |
|     return positiveSlippageFeeTransformerDataEncoder.encode(data);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * ABI-decode a `PositiveSlippageFeeTransformer.TransformData` type.
 | |
|  */
 | |
| export function decodePositiveSlippageFeeTransformerData(encoded: string): PositiveSlippageFeeTransformerData {
 | |
|     return positiveSlippageFeeTransformerDataEncoder.decode(encoded);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Packs a bridge protocol ID and an ASCII DEX name into a single byte32.
 | |
|  */
 | |
| export function encodeBridgeSourceId(protocol: BridgeProtocol, name: string): string {
 | |
|     const nameBuf = Buffer.from(name);
 | |
|     if (nameBuf.length > 16) {
 | |
|         throw new Error(`"${name}" is too long to be a bridge source name (max of 16 ascii chars)`);
 | |
|     }
 | |
|     return hexUtils.concat(
 | |
|         hexUtils.leftPad(hexUtils.toHex(protocol), 16),
 | |
|         hexUtils.rightPad(hexUtils.toHex(Buffer.from(name)), 16),
 | |
|     );
 | |
| }
 |