Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Commit

Permalink
Guess deployment nonce from transformer address.
Browse files Browse the repository at this point in the history
  • Loading branch information
merklejerk committed Jun 9, 2020
1 parent da5d702 commit 033c434
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 18 deletions.
1 change: 1 addition & 0 deletions packages/asset-swapper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@0x/web3-wrapper": "^7.0.7",
"axios": "^0.19.2",
"axios-mock-adapter": "^1.18.1",
"ethereumjs-util": "^5.1.1",
"heartbeats": "^5.0.1",
"lodash": "^4.17.11"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { assetDataUtils, ERC20AssetData } from '@0x/order-utils';
import { AssetProxyId } from '@0x/types';
import { BigNumber, providerUtils } from '@0x/utils';
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
import * as ethjs from 'ethereumjs-util';
import * as _ from 'lodash';

import { constants } from '../constants';
Expand All @@ -30,10 +31,17 @@ import { assert } from '../utils/assert';

// tslint:disable-next-line:custom-no-magic-numbers
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
const { NULL_ADDRESS } = constants;
const MAX_NONCE_GUESSES = 2048;

export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
public readonly provider: ZeroExProvider;
public readonly chainId: number;
public readonly transformerNonces: {
wethTransformer: number;
payTakerTransformer: number;
fillQuoteTransformer: number;
};

private readonly _transformFeature: ITransformERC20Contract;

Expand All @@ -49,6 +57,20 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
this.chainId = chainId;
this.contractAddresses = contractAddresses;
this._transformFeature = new ITransformERC20Contract(contractAddresses.exchangeProxy, supportedProvider);
this.transformerNonces = {
wethTransformer: findTransformerNonce(
contractAddresses.transformers.wethTransformer,
contractAddresses.exchangeProxyTransformerDeployer,
),
payTakerTransformer: findTransformerNonce(
contractAddresses.transformers.payTakerTransformer,
contractAddresses.exchangeProxyTransformerDeployer,
),
fillQuoteTransformer: findTransformerNonce(
contractAddresses.transformers.fillQuoteTransformer,
contractAddresses.exchangeProxyTransformerDeployer,
),
};
}

public async getCalldataOrThrowAsync(
Expand All @@ -73,7 +95,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
if (exchangeProxyOpts.isFromETH) {
// Create a WETH wrapper if coming from ETH.
transforms.push({
transformer: this.contractAddresses.transformers.wethTransformer,
deploymentNonce: this.transformerNonces.wethTransformer,
data: encodeWethTransformerData({
token: ETH_TOKEN_ADDRESS,
amount: quote.worstCaseQuoteInfo.totalTakerAssetAmount,
Expand All @@ -83,7 +105,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {

// This transformer will fill the quote.
transforms.push({
transformer: this.contractAddresses.transformers.fillQuoteTransformer,
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
data: encodeFillQuoteTransformerData({
sellToken,
buyToken,
Expand All @@ -98,7 +120,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
if (exchangeProxyOpts.isToETH) {
// Create a WETH unwrapper if going to ETH.
transforms.push({
transformer: this.contractAddresses.transformers.wethTransformer,
deploymentNonce: this.transformerNonces.wethTransformer,
data: encodeWethTransformerData({
token: this.contractAddresses.etherToken,
amount: MAX_UINT256,
Expand All @@ -108,7 +130,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {

// The final transformer will send all funds to the taker.
transforms.push({
transformer: this.contractAddresses.transformers.payTakerTransformer,
deploymentNonce: this.transformerNonces.payTakerTransformer,
data: encodePayTakerTransformerData({
tokens: [sellToken, buyToken, ETH_TOKEN_ADDRESS],
amounts: [],
Expand Down Expand Up @@ -159,3 +181,32 @@ function getTokenFromAssetData(assetData: string): string {
// tslint:disable-next-line:no-unnecessary-type-assertion
return (data as ERC20AssetData).tokenAddress;
}

/**
* 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): number {
if (deployer === NULL_ADDRESS) {
return 0;
}
const lowercaseTransformer = transformer.toLowerCase();
// Try to guess the nonce.
for (let nonce = 0; nonce < MAX_NONCE_GUESSES; ++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),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import * as _ from 'lodash';
import 'mocha';

import { constants } from '../src/constants';
import { ExchangeProxySwapQuoteConsumer } from '../src/quote_consumers/exchange_proxy_swap_quote_consumer';
import {
ExchangeProxySwapQuoteConsumer,
getTransformerAddress,
} from '../src/quote_consumers/exchange_proxy_swap_quote_consumer';
import { MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types';
import { OptimizedMarketOrder } from '../src/utils/market_operation_utils/types';

Expand All @@ -33,14 +36,16 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
const CHAIN_ID = 1;
const TAKER_TOKEN = randomAddress();
const MAKER_TOKEN = randomAddress();
const TRANSFORMER_DEPLOYER = randomAddress();
const contractAddresses = {
...getContractAddressesForChainOrThrow(CHAIN_ID),
exchangeProxy: randomAddress(),
exchangeProxyAllowanceTarget: randomAddress(),
exchangeProxyTransformerDeployer: TRANSFORMER_DEPLOYER,
transformers: {
wethTransformer: randomAddress(),
payTakerTransformer: randomAddress(),
fillQuoteTransformer: randomAddress(),
wethTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 1),
payTakerTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 2),
fillQuoteTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 3),
},
};
let consumer: ExchangeProxySwapQuoteConsumer;
Expand Down Expand Up @@ -150,7 +155,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
{
type: 'tuple[]',
name: 'transformations',
components: [{ type: 'address', name: 'transformer' }, { type: 'bytes', name: 'data' }],
components: [{ type: 'uint32', name: 'deploymentNonce' }, { type: 'bytes', name: 'data' }],
},
]);

Expand All @@ -160,7 +165,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
inputTokenAmount: BigNumber;
minOutputTokenAmount: BigNumber;
transformations: Array<{
transformer: string;
deploymentNonce: BigNumber;
data: string;
}>;
}
Expand All @@ -175,8 +180,14 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
expect(callArgs.inputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.totalTakerAssetAmount);
expect(callArgs.minOutputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.makerAssetAmount);
expect(callArgs.transformations).to.be.length(2);
expect(callArgs.transformations[0].transformer === contractAddresses.transformers.fillQuoteTransformer);
expect(callArgs.transformations[1].transformer === contractAddresses.transformers.payTakerTransformer);
expect(
callArgs.transformations[0].deploymentNonce.toNumber() ===
consumer.transformerNonces.fillQuoteTransformer,
);
expect(
callArgs.transformations[1].deploymentNonce.toNumber() ===
consumer.transformerNonces.payTakerTransformer,
);
const fillQuoteTransformerData = decodeFillQuoteTransformerData(callArgs.transformations[0].data);
expect(fillQuoteTransformerData.side).to.eq(FillQuoteTransformerSide.Sell);
expect(fillQuoteTransformerData.fillAmount).to.bignumber.eq(quote.takerAssetFillAmount);
Expand All @@ -198,8 +209,14 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
expect(callArgs.inputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.totalTakerAssetAmount);
expect(callArgs.minOutputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.makerAssetAmount);
expect(callArgs.transformations).to.be.length(2);
expect(callArgs.transformations[0].transformer === contractAddresses.transformers.fillQuoteTransformer);
expect(callArgs.transformations[1].transformer === contractAddresses.transformers.payTakerTransformer);
expect(
callArgs.transformations[0].deploymentNonce.toNumber() ===
consumer.transformerNonces.fillQuoteTransformer,
);
expect(
callArgs.transformations[1].deploymentNonce.toNumber() ===
consumer.transformerNonces.payTakerTransformer,
);
const fillQuoteTransformerData = decodeFillQuoteTransformerData(callArgs.transformations[0].data);
expect(fillQuoteTransformerData.side).to.eq(FillQuoteTransformerSide.Buy);
expect(fillQuoteTransformerData.fillAmount).to.bignumber.eq(quote.makerAssetFillAmount);
Expand All @@ -216,8 +233,8 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
const quote = getRandomSellQuote();
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
const transformers = callArgs.transformations.map(t => t.transformer);
expect(transformers).to.not.include(contractAddresses.transformers.wethTransformer);
const nonces = callArgs.transformations.map(t => t.deploymentNonce);
expect(nonces).to.not.include(consumer.transformerNonces.wethTransformer);
});

it('ETH -> ERC20 has a WETH transformer before the fill', async () => {
Expand All @@ -226,7 +243,9 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
extensionContractOpts: { isFromETH: true },
});
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
expect(callArgs.transformations[0].transformer).to.eq(contractAddresses.transformers.wethTransformer);
expect(callArgs.transformations[0].deploymentNonce.toNumber()).to.eq(
consumer.transformerNonces.wethTransformer,
);
const wethTransformerData = decodeWethTransformerData(callArgs.transformations[0].data);
expect(wethTransformerData.amount).to.bignumber.eq(quote.worstCaseQuoteInfo.totalTakerAssetAmount);
expect(wethTransformerData.token).to.eq(ETH_TOKEN_ADDRESS);
Expand All @@ -238,7 +257,9 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
extensionContractOpts: { isToETH: true },
});
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
expect(callArgs.transformations[1].transformer).to.eq(contractAddresses.transformers.wethTransformer);
expect(callArgs.transformations[1].deploymentNonce.toNumber()).to.eq(
consumer.transformerNonces.wethTransformer,
);
const wethTransformerData = decodeWethTransformerData(callArgs.transformations[1].data);
expect(wethTransformerData.amount).to.bignumber.eq(MAX_UINT256);
expect(wethTransformerData.token).to.eq(contractAddresses.etherToken);
Expand Down

0 comments on commit 033c434

Please sign in to comment.