Merge pull request #2591 from 0xProject/feat/asset-swapper/exchange-proxy-support

asset-swapper: Exchange Proxy support
This commit is contained in:
Lawrence Forman
2020-06-12 01:52:07 -04:00
committed by GitHub
38 changed files with 1867 additions and 88 deletions

View File

@@ -77,7 +77,7 @@ jobs:
- restore_cache:
keys:
- repo-{{ .Environment.CIRCLE_SHA1 }}
- run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange-forwarder @0x/contracts-coordinator @0x/contracts-erc20-bridge-sampler @0x/contracts-zero-ex
- run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange-forwarder @0x/contracts-coordinator @0x/contracts-erc20-bridge-sampler @0x/contracts-broker @0x/contracts-zero-ex
test-publish:
resource_class: medium+
docker:

View File

@@ -1,4 +1,13 @@
[
{
"version": "1.1.5",
"changes": [
{
"note": "Fix broken tests.",
"pr": 2591
}
]
},
{
"timestamp": 1583220306,
"version": "1.1.4",

View File

@@ -44,13 +44,13 @@ blockchainTests.resets('GodsUnchainedValidator unit tests', env => {
const tokenId = getRandomInteger(0, constants.MAX_UINT256);
await godsUnchained.setTokenProperties(tokenId, proto.plus(1), quality).awaitTransactionSuccessAsync();
const tx = validator.checkBrokerAsset(tokenId, propertyData).callAsync();
expect(tx).to.revertWith('PROTO_MISMATCH');
expect(tx).to.revertWith('GodsUnchainedValidator/PROTO_MISMATCH');
});
it("reverts if assetData quality doesn't match proeprtyData", async () => {
const tokenId = getRandomInteger(0, constants.MAX_UINT256);
await godsUnchained.setTokenProperties(tokenId, proto, quality.plus(1)).awaitTransactionSuccessAsync();
const tx = validator.checkBrokerAsset(tokenId, propertyData).callAsync();
expect(tx).to.revertWith('QUALITY_MISMATCH');
expect(tx).to.revertWith('GodsUnchainedValidator/QUALITY_MISMATCH');
});
});
});

View File

@@ -69,6 +69,13 @@ export function encodeFillQuoteTransformerData(data: FillQuoteTransformerData):
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`
*/
@@ -95,6 +102,13 @@ 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`
*/
@@ -121,6 +135,13 @@ export function encodePayTakerTransformerData(data: PayTakerTransformerData): st
return payTakerTransformerDataEncoder.encode([data]);
}
/**
* ABI-decode a `PayTakerTransformer.TransformData` type.
*/
export function decodePayTakerTransformerData(encoded: string): PayTakerTransformerData {
return payTakerTransformerDataEncoder.decode(encoded).data;
}
/**
* ABI encoder for `PayTakerTransformer.TransformData`
*/
@@ -157,3 +178,10 @@ export interface AffiliateFeeTransformerData {
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).data;
}

View File

@@ -1,4 +1,13 @@
[
{
"version": "5.3.0",
"changes": [
{
"note": "Allow identifiers with leading underscores in python wrappers",
"pr": 2591
}
]
},
{
"version": "5.2.2",
"changes": [

View File

@@ -146,7 +146,16 @@ if (args.language === 'TypeScript') {
registerPartials();
function makeLanguageSpecificName(methodName: string): string {
return args.language === 'Python' ? changeCase.snake(methodName) : methodName;
if (args.language === 'Python') {
let snakeCased = changeCase.snake(methodName);
// Move leading underscores to the end.
const m = /^(_*).+?(_*)$/.exec(methodName);
if (m) {
snakeCased = `${snakeCased}${m[1] || m[2]}`;
}
return snakeCased;
}
return methodName;
}
if (_.isEmpty(abiFileNames)) {

View File

@@ -153,10 +153,15 @@ export function registerPythonHelpers(): void {
}
return '';
});
Handlebars.registerHelper(
'toPythonClassname',
(sourceName: string) => new Handlebars.SafeString(changeCase.pascal(sourceName)),
);
Handlebars.registerHelper('toPythonClassname', (sourceName: string) => {
let pascalCased = changeCase.pascal(sourceName);
// Retain trailing underscores.
const m = /^.+?(_*)$/.exec(sourceName);
if (m) {
pascalCased = `${pascalCased}${m[1]}`;
}
return new Handlebars.SafeString(pascalCased);
});
Handlebars.registerHelper(
'makeOutputsValue',
/**

View File

@@ -331,13 +331,14 @@ export const utils = {
'max',
'round',
];
if (
pythonReservedWords.includes(snakeCased) ||
pythonBuiltins.includes(snakeCased) ||
/*changeCase strips leading underscores :(*/ input[0] === '_'
) {
if (pythonReservedWords.includes(snakeCased) || pythonBuiltins.includes(snakeCased)) {
snakeCased = `_${snakeCased}`;
}
// Retain trailing underscores.
const m = /^.+?(_*)$/.exec(input);
if (m) {
snakeCased = `${snakeCased}${m[1]}`;
}
return snakeCased;
},
/**

View File

@@ -1,5 +1,5 @@
class {{toPythonClassname this.languageSpecificName}}Method(ContractMethod):
class {{toPythonClassname this.languageSpecificName}}Method(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the {{this.name}} method."""
def __init__(self, web3_or_provider: Union[Web3, BaseProvider], contract_address: str, contract_function: ContractFunction{{#if inputs}}, validator: Validator=None{{/if}}):

View File

@@ -178,7 +178,9 @@ class AbiGenDummyNestedStruct(TypedDict):
description: str
class AcceptsAnArrayOfBytesMethod(ContractMethod):
class AcceptsAnArrayOfBytesMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the acceptsAnArrayOfBytes method."""
def __init__(
@@ -225,7 +227,7 @@ class AcceptsAnArrayOfBytesMethod(ContractMethod):
return self._underlying_method(a).estimateGas(tx_params.as_dict())
class AcceptsBytesMethod(ContractMethod):
class AcceptsBytesMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the acceptsBytes method."""
def __init__(
@@ -267,7 +269,9 @@ class AcceptsBytesMethod(ContractMethod):
return self._underlying_method(a).estimateGas(tx_params.as_dict())
class ComplexInputComplexOutputMethod(ContractMethod):
class ComplexInputComplexOutputMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the complexInputComplexOutput method."""
def __init__(
@@ -329,7 +333,7 @@ class ComplexInputComplexOutputMethod(ContractMethod):
)
class EcrecoverFnMethod(ContractMethod):
class EcrecoverFnMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the ecrecoverFn method."""
def __init__(
@@ -414,7 +418,7 @@ class EcrecoverFnMethod(ContractMethod):
)
class EmitSimpleEventMethod(ContractMethod):
class EmitSimpleEventMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the emitSimpleEvent method."""
def __init__(
@@ -457,7 +461,9 @@ class EmitSimpleEventMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class MethodAcceptingArrayOfArrayOfStructsMethod(ContractMethod):
class MethodAcceptingArrayOfArrayOfStructsMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the methodAcceptingArrayOfArrayOfStructs method."""
def __init__(
@@ -509,7 +515,9 @@ class MethodAcceptingArrayOfArrayOfStructsMethod(ContractMethod):
)
class MethodAcceptingArrayOfStructsMethod(ContractMethod):
class MethodAcceptingArrayOfStructsMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the methodAcceptingArrayOfStructs method."""
def __init__(
@@ -559,7 +567,9 @@ class MethodAcceptingArrayOfStructsMethod(ContractMethod):
)
class MethodReturningArrayOfStructsMethod(ContractMethod):
class MethodReturningArrayOfStructsMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the methodReturningArrayOfStructs method."""
def __init__(
@@ -598,7 +608,9 @@ class MethodReturningArrayOfStructsMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class MethodReturningMultipleValuesMethod(ContractMethod):
class MethodReturningMultipleValuesMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the methodReturningMultipleValues method."""
def __init__(
@@ -632,7 +644,7 @@ class MethodReturningMultipleValuesMethod(ContractMethod):
class MethodUsingNestedStructWithInnerStructNotUsedElsewhereMethod(
ContractMethod
):
): # pylint: disable=invalid-name
"""Various interfaces to the methodUsingNestedStructWithInnerStructNotUsedElsewhere method."""
def __init__(
@@ -665,7 +677,9 @@ class MethodUsingNestedStructWithInnerStructNotUsedElsewhereMethod(
return self._underlying_method().estimateGas(tx_params.as_dict())
class MultiInputMultiOutputMethod(ContractMethod):
class MultiInputMultiOutputMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the multiInputMultiOutput method."""
def __init__(
@@ -747,7 +761,7 @@ class MultiInputMultiOutputMethod(ContractMethod):
)
class NestedStructInputMethod(ContractMethod):
class NestedStructInputMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the nestedStructInput method."""
def __init__(
@@ -791,7 +805,7 @@ class NestedStructInputMethod(ContractMethod):
return self._underlying_method(n).estimateGas(tx_params.as_dict())
class NestedStructOutputMethod(ContractMethod):
class NestedStructOutputMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the nestedStructOutput method."""
def __init__(
@@ -824,7 +838,7 @@ class NestedStructOutputMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class NoInputNoOutputMethod(ContractMethod):
class NoInputNoOutputMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the noInputNoOutput method."""
def __init__(
@@ -854,7 +868,9 @@ class NoInputNoOutputMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class NoInputSimpleOutputMethod(ContractMethod):
class NoInputSimpleOutputMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the noInputSimpleOutput method."""
def __init__(
@@ -885,7 +901,7 @@ class NoInputSimpleOutputMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class NonPureMethodMethod(ContractMethod):
class NonPureMethodMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the nonPureMethod method."""
def __init__(
@@ -929,7 +945,9 @@ class NonPureMethodMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class NonPureMethodThatReturnsNothingMethod(ContractMethod):
class NonPureMethodThatReturnsNothingMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the nonPureMethodThatReturnsNothing method."""
def __init__(
@@ -972,7 +990,7 @@ class NonPureMethodThatReturnsNothingMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class OverloadedMethod2Method(ContractMethod):
class OverloadedMethod2Method(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the overloadedMethod method."""
def __init__(
@@ -1014,7 +1032,7 @@ class OverloadedMethod2Method(ContractMethod):
return self._underlying_method(a).estimateGas(tx_params.as_dict())
class OverloadedMethod1Method(ContractMethod):
class OverloadedMethod1Method(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the overloadedMethod method."""
def __init__(
@@ -1056,7 +1074,9 @@ class OverloadedMethod1Method(ContractMethod):
return self._underlying_method(a).estimateGas(tx_params.as_dict())
class PureFunctionWithConstantMethod(ContractMethod):
class PureFunctionWithConstantMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the pureFunctionWithConstant method."""
def __init__(
@@ -1085,7 +1105,9 @@ class PureFunctionWithConstantMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class RequireWithConstantMethod(ContractMethod):
class RequireWithConstantMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the requireWithConstant method."""
def __init__(
@@ -1113,7 +1135,7 @@ class RequireWithConstantMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class RevertWithConstantMethod(ContractMethod):
class RevertWithConstantMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the revertWithConstant method."""
def __init__(
@@ -1141,7 +1163,9 @@ class RevertWithConstantMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class SimpleInputNoOutputMethod(ContractMethod):
class SimpleInputNoOutputMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the simpleInputNoOutput method."""
def __init__(
@@ -1189,7 +1213,9 @@ class SimpleInputNoOutputMethod(ContractMethod):
)
class SimpleInputSimpleOutputMethod(ContractMethod):
class SimpleInputSimpleOutputMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the simpleInputSimpleOutput method."""
def __init__(
@@ -1238,7 +1264,7 @@ class SimpleInputSimpleOutputMethod(ContractMethod):
)
class SimplePureFunctionMethod(ContractMethod):
class SimplePureFunctionMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the simplePureFunction method."""
def __init__(
@@ -1267,7 +1293,9 @@ class SimplePureFunctionMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class SimplePureFunctionWithInputMethod(ContractMethod):
class SimplePureFunctionWithInputMethod(
ContractMethod
): # pylint: disable=invalid-name
"""Various interfaces to the simplePureFunctionWithInput method."""
def __init__(
@@ -1312,7 +1340,7 @@ class SimplePureFunctionWithInputMethod(ContractMethod):
return self._underlying_method(x).estimateGas(tx_params.as_dict())
class SimpleRequireMethod(ContractMethod):
class SimpleRequireMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the simpleRequire method."""
def __init__(
@@ -1340,7 +1368,7 @@ class SimpleRequireMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class SimpleRevertMethod(ContractMethod):
class SimpleRevertMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the simpleRevert method."""
def __init__(
@@ -1368,7 +1396,7 @@ class SimpleRevertMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class StructInputMethod(ContractMethod):
class StructInputMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the structInput method."""
def __init__(
@@ -1410,7 +1438,7 @@ class StructInputMethod(ContractMethod):
return self._underlying_method(s).estimateGas(tx_params.as_dict())
class StructOutputMethod(ContractMethod):
class StructOutputMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the structOutput method."""
def __init__(
@@ -1446,7 +1474,7 @@ class StructOutputMethod(ContractMethod):
return self._underlying_method().estimateGas(tx_params.as_dict())
class WithAddressInputMethod(ContractMethod):
class WithAddressInputMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the withAddressInput method."""
def __init__(
@@ -1537,7 +1565,7 @@ class WithAddressInputMethod(ContractMethod):
)
class WithdrawMethod(ContractMethod):
class WithdrawMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the withdraw method."""
def __init__(

View File

@@ -90,7 +90,7 @@ class LibDummy:
try:
for middleware in MIDDLEWARE:
web3.middleware_onion.inject(
middleware["function"], layer=middleware["layer"]
middleware["function"], layer=middleware["layer"],
)
except ValueError as value_error:
if value_error.args == (

View File

@@ -46,7 +46,7 @@ except ImportError:
pass
class PublicAddConstantMethod(ContractMethod):
class PublicAddConstantMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the publicAddConstant method."""
def __init__(
@@ -91,7 +91,7 @@ class PublicAddConstantMethod(ContractMethod):
return self._underlying_method(x).estimateGas(tx_params.as_dict())
class PublicAddOneMethod(ContractMethod):
class PublicAddOneMethod(ContractMethod): # pylint: disable=invalid-name
"""Various interfaces to the publicAddOne method."""
def __init__(

View File

@@ -89,6 +89,10 @@
{
"note": "Fix Uniswap V2 path ordering",
"pr": 2601
},
{
"note": "Add exchange proxy support",
"pr": 2591
}
]
},

View File

@@ -49,6 +49,7 @@
"@0x/assert": "^3.0.7",
"@0x/contract-addresses": "^4.9.0",
"@0x/contract-wrappers": "^13.6.3",
"@0x/contracts-zero-ex": "^0.1.0",
"@0x/json-schemas": "^5.0.7",
"@0x/order-utils": "^10.2.4",
"@0x/orderbook": "^2.2.5",
@@ -56,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"
},

View File

@@ -60,6 +60,7 @@ export {
SwapQuoteConsumerError,
SignedOrderWithFillableAmounts,
SwapQuoteOrdersBreakdown,
ExchangeProxyContractOpts,
} from './types';
export {
ERC20BridgeSource,

View File

@@ -0,0 +1,211 @@
import { ContractAddresses } from '@0x/contract-addresses';
import { ITransformERC20Contract } from '@0x/contract-wrappers';
import {
encodeFillQuoteTransformerData,
encodePayTakerTransformerData,
encodeWethTransformerData,
ETH_TOKEN_ADDRESS,
FillQuoteTransformerSide,
} from '@0x/contracts-zero-ex';
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';
import {
CalldataInfo,
MarketBuySwapQuote,
MarketOperation,
MarketSellSwapQuote,
SwapQuote,
SwapQuoteConsumerBase,
SwapQuoteConsumerOpts,
SwapQuoteExecutionOpts,
SwapQuoteGetOutputOpts,
} from '../types';
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;
constructor(
supportedProvider: SupportedProvider,
public readonly contractAddresses: ContractAddresses,
options: Partial<SwapQuoteConsumerOpts> = {},
) {
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
assert.isNumber('chainId', chainId);
const provider = providerUtils.standardizeOrThrow(supportedProvider);
this.provider = provider;
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(
quote: MarketBuySwapQuote | MarketSellSwapQuote,
opts: Partial<SwapQuoteGetOutputOpts> = {},
): Promise<CalldataInfo> {
assert.isValidSwapQuote('quote', quote);
const { isFromETH, isToETH } = {
...constants.DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS,
extensionContractOpts: {
isFromETH: false,
isToETH: false,
},
...opts,
}.extensionContractOpts;
const sellToken = getTokenFromAssetData(quote.takerAssetData);
const buyToken = getTokenFromAssetData(quote.makerAssetData);
// Build up the transforms.
const transforms = [];
if (isFromETH) {
// Create a WETH wrapper if coming from ETH.
transforms.push({
deploymentNonce: this.transformerNonces.wethTransformer,
data: encodeWethTransformerData({
token: ETH_TOKEN_ADDRESS,
amount: quote.worstCaseQuoteInfo.totalTakerAssetAmount,
}),
});
}
// This transformer will fill the quote.
transforms.push({
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
data: encodeFillQuoteTransformerData({
sellToken,
buyToken,
side: isBuyQuote(quote) ? FillQuoteTransformerSide.Buy : FillQuoteTransformerSide.Sell,
fillAmount: isBuyQuote(quote) ? quote.makerAssetFillAmount : quote.takerAssetFillAmount,
maxOrderFillAmounts: [],
orders: quote.orders,
signatures: quote.orders.map(o => o.signature),
}),
});
if (isToETH) {
// Create a WETH unwrapper if going to ETH.
transforms.push({
deploymentNonce: this.transformerNonces.wethTransformer,
data: encodeWethTransformerData({
token: this.contractAddresses.etherToken,
amount: MAX_UINT256,
}),
});
}
// The final transformer will send all funds to the taker.
transforms.push({
deploymentNonce: this.transformerNonces.payTakerTransformer,
data: encodePayTakerTransformerData({
tokens: [sellToken, buyToken, ETH_TOKEN_ADDRESS],
amounts: [],
}),
});
const calldataHexString = this._transformFeature
.transformERC20(
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
quote.worstCaseQuoteInfo.totalTakerAssetAmount,
quote.worstCaseQuoteInfo.makerAssetAmount,
transforms,
)
.getABIEncodedTransactionData();
let ethAmount = quote.worstCaseQuoteInfo.protocolFeeInWeiAmount;
if (isFromETH) {
ethAmount = ethAmount.plus(quote.worstCaseQuoteInfo.takerAssetAmount);
}
return {
calldataHexString,
ethAmount,
toAddress: this._transformFeature.address,
allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
};
}
// tslint:disable-next-line:prefer-function-over-method
public async executeSwapQuoteOrThrowAsync(
_quote: SwapQuote,
_opts: Partial<SwapQuoteExecutionOpts>,
): Promise<string> {
throw new Error('Execution not supported for Exchange Proxy quotes');
}
}
function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
return quote.type === MarketOperation.Buy;
}
function getTokenFromAssetData(assetData: string): string {
const data = assetDataUtils.decodeAssetDataOrThrow(assetData);
if (data.assetProxyId !== AssetProxyId.ERC20) {
throw new Error(`Unsupported exchange proxy quote asset type: ${data.assetProxyId}`);
}
// 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),
);
}

View File

@@ -25,7 +25,7 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase {
constructor(
supportedProvider: SupportedProvider,
contractAddresses: ContractAddresses,
public readonly contractAddresses: ContractAddresses,
options: Partial<SwapQuoteConsumerOpts> = {},
) {
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
@@ -59,6 +59,7 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase {
calldataHexString,
ethAmount: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount,
toAddress: this._exchangeContract.address,
allowanceTarget: this.contractAddresses.erc20Proxy,
};
}

View File

@@ -19,16 +19,17 @@ import { affiliateFeeUtils } from '../utils/affiliate_fee_utils';
import { assert } from '../utils/assert';
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
const { NULL_ADDRESS } = constants;
export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase {
public readonly provider: ZeroExProvider;
public readonly chainId: number;
private readonly _contractAddresses: ContractAddresses;
private readonly _forwarder: ForwarderContract;
constructor(
supportedProvider: SupportedProvider,
contractAddresses: ContractAddresses,
public readonly contractAddresses: ContractAddresses,
options: Partial<SwapQuoteConsumerOpts> = {},
) {
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
@@ -36,7 +37,6 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase {
const provider = providerUtils.standardizeOrThrow(supportedProvider);
this.provider = provider;
this.chainId = chainId;
this._contractAddresses = contractAddresses;
this._forwarder = new ForwarderContract(contractAddresses.forwarder, supportedProvider);
}
@@ -90,6 +90,7 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase {
calldataHexString,
toAddress: this._forwarder.address,
ethAmount: ethAmountWithFees,
allowanceTarget: NULL_ADDRESS,
};
}
@@ -160,6 +161,6 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase {
}
private _getEtherTokenAssetDataOrThrow(): string {
return assetDataUtils.encodeERC20AssetData(this._contractAddresses.etherToken);
return assetDataUtils.encodeERC20AssetData(this.contractAddresses.etherToken);
}
}

View File

@@ -17,6 +17,7 @@ import {
import { assert } from '../utils/assert';
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
import { ExchangeProxySwapQuoteConsumer } from './exchange_proxy_swap_quote_consumer';
import { ExchangeSwapQuoteConsumer } from './exchange_swap_quote_consumer';
import { ForwarderSwapQuoteConsumer } from './forwarder_swap_quote_consumer';
@@ -27,6 +28,7 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
private readonly _exchangeConsumer: ExchangeSwapQuoteConsumer;
private readonly _forwarderConsumer: ForwarderSwapQuoteConsumer;
private readonly _contractAddresses: ContractAddresses;
private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer;
public static getSwapQuoteConsumer(
supportedProvider: SupportedProvider,
@@ -45,6 +47,11 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
this._exchangeConsumer = new ExchangeSwapQuoteConsumer(supportedProvider, this._contractAddresses, options);
this._forwarderConsumer = new ForwarderSwapQuoteConsumer(supportedProvider, this._contractAddresses, options);
this._exchangeProxyConsumer = new ExchangeProxySwapQuoteConsumer(
supportedProvider,
this._contractAddresses,
options,
);
}
/**
@@ -93,9 +100,13 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
}
private async _getConsumerForSwapQuoteAsync(opts: Partial<SwapQuoteGetOutputOpts>): Promise<SwapQuoteConsumerBase> {
if (opts.useExtensionContract === ExtensionContractType.Forwarder) {
switch (opts.useExtensionContract) {
case ExtensionContractType.Forwarder:
return this._forwarderConsumer;
}
case ExtensionContractType.ExchangeProxy:
return this._exchangeProxyConsumer;
default:
return this._exchangeConsumer;
}
}
}

View File

@@ -50,19 +50,22 @@ export interface SignedOrderWithFillableAmounts extends SignedOrder {
* calldataHexString: The hexstring of the calldata.
* toAddress: The contract address to call.
* ethAmount: The eth amount in wei to send with the smart contract call.
* allowanceTarget: The address the taker should grant an allowance to.
*/
export interface CalldataInfo {
calldataHexString: string;
toAddress: string;
ethAmount: BigNumber;
allowanceTarget: string;
}
/**
* Represents the varying smart contracts that can consume a valid swap quote
*/
export enum ExtensionContractType {
Forwarder = 'FORWARDER',
None = 'NONE',
Forwarder = 'FORWARDER',
ExchangeProxy = 'EXCHANGE_PROXY',
}
/**
@@ -97,7 +100,7 @@ export interface SwapQuoteConsumerOpts {
*/
export interface SwapQuoteGetOutputOpts {
useExtensionContract: ExtensionContractType;
extensionContractOpts?: ForwarderExtensionContractOpts | any;
extensionContractOpts?: ForwarderExtensionContractOpts | ExchangeProxyContractOpts | any;
}
/**
@@ -112,7 +115,6 @@ export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts {
}
/**
* ethAmount: The amount of eth (in Wei) sent to the forwarder contract.
* feePercentage: percentage (up to 5%) of the taker asset paid to feeRecipient
* feeRecipient: address of the receiver of the feePercentage of taker asset
*/
@@ -121,6 +123,15 @@ export interface ForwarderExtensionContractOpts {
feeRecipient: string;
}
/**
* @param isFromETH Whether the input token is ETH.
* @param isToETH Whether the output token is ETH.
*/
export interface ExchangeProxyContractOpts {
isFromETH: boolean;
isToETH: boolean;
}
export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
export interface GetExtensionContractTypeOpts {

View File

@@ -0,0 +1,268 @@
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
import { constants as contractConstants, getRandomInteger, Numberish, randomAddress } from '@0x/contracts-test-utils';
import {
decodeFillQuoteTransformerData,
decodePayTakerTransformerData,
decodeWethTransformerData,
ETH_TOKEN_ADDRESS,
FillQuoteTransformerSide,
} from '@0x/contracts-zero-ex';
import { assetDataUtils } from '@0x/order-utils';
import { Order } from '@0x/types';
import { AbiEncoder, BigNumber, hexUtils } from '@0x/utils';
import * as chai from 'chai';
import * as _ from 'lodash';
import 'mocha';
import { constants } from '../src/constants';
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';
import { chaiSetup } from './utils/chai_setup';
chaiSetup.configure();
const expect = chai.expect;
const { NULL_ADDRESS } = constants;
const { MAX_UINT256 } = contractConstants;
// tslint:disable: custom-no-magic-numbers
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: getTransformerAddress(TRANSFORMER_DEPLOYER, 1),
payTakerTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 2),
fillQuoteTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 3),
},
};
let consumer: ExchangeProxySwapQuoteConsumer;
before(async () => {
const fakeProvider = {
async sendAsync(): Promise<void> {
/* noop */
},
};
consumer = new ExchangeProxySwapQuoteConsumer(fakeProvider, contractAddresses, { chainId: CHAIN_ID });
});
function getRandomAmount(maxAmount: Numberish = '1e18'): BigNumber {
return getRandomInteger(1, maxAmount);
}
function createAssetData(token?: string): string {
return assetDataUtils.encodeERC20AssetData(token || randomAddress());
}
function getRandomOrder(): OptimizedMarketOrder {
return {
fillableMakerAssetAmount: getRandomAmount(),
fillableTakerFeeAmount: getRandomAmount(),
fillableTakerAssetAmount: getRandomAmount(),
fills: [],
chainId: CHAIN_ID,
exchangeAddress: contractAddresses.exchange,
expirationTimeSeconds: getRandomInteger(1, 2e9),
feeRecipientAddress: randomAddress(),
makerAddress: randomAddress(),
makerAssetAmount: getRandomAmount(),
takerAssetAmount: getRandomAmount(),
makerFee: getRandomAmount(),
takerFee: getRandomAmount(),
salt: getRandomAmount(2e9),
signature: hexUtils.random(66),
senderAddress: NULL_ADDRESS,
takerAddress: NULL_ADDRESS,
makerAssetData: createAssetData(MAKER_TOKEN),
takerAssetData: createAssetData(TAKER_TOKEN),
makerFeeAssetData: createAssetData(),
takerFeeAssetData: createAssetData(),
};
}
function getRandomQuote(side: MarketOperation): MarketBuySwapQuote | MarketSellSwapQuote {
return {
gasPrice: getRandomInteger(1, 1e9),
type: side,
makerAssetData: createAssetData(MAKER_TOKEN),
takerAssetData: createAssetData(TAKER_TOKEN),
orders: [getRandomOrder()],
bestCaseQuoteInfo: {
feeTakerAssetAmount: getRandomAmount(),
makerAssetAmount: getRandomAmount(),
gas: Math.floor(Math.random() * 8e6),
protocolFeeInWeiAmount: getRandomAmount(),
takerAssetAmount: getRandomAmount(),
totalTakerAssetAmount: getRandomAmount(),
},
worstCaseQuoteInfo: {
feeTakerAssetAmount: getRandomAmount(),
makerAssetAmount: getRandomAmount(),
gas: Math.floor(Math.random() * 8e6),
protocolFeeInWeiAmount: getRandomAmount(),
takerAssetAmount: getRandomAmount(),
totalTakerAssetAmount: getRandomAmount(),
},
...(side === MarketOperation.Buy
? { makerAssetFillAmount: getRandomAmount() }
: { takerAssetFillAmount: getRandomAmount() }),
} as any;
}
function getRandomSellQuote(): MarketSellSwapQuote {
return getRandomQuote(MarketOperation.Sell) as MarketSellSwapQuote;
}
function getRandomBuyQuote(): MarketBuySwapQuote {
return getRandomQuote(MarketOperation.Buy) as MarketBuySwapQuote;
}
type PlainOrder = Exclude<Order, ['chainId', 'exchangeAddress']>;
function cleanOrders(orders: OptimizedMarketOrder[]): PlainOrder[] {
return orders.map(
o =>
_.omit(o, [
'chainId',
'exchangeAddress',
'fillableMakerAssetAmount',
'fillableTakerAssetAmount',
'fillableTakerFeeAmount',
'fills',
'signature',
]) as PlainOrder,
);
}
const callDataEncoder = AbiEncoder.createMethod('transformERC20', [
{ type: 'address', name: 'inputToken' },
{ type: 'address', name: 'outputToken' },
{ type: 'uint256', name: 'inputTokenAmount' },
{ type: 'uint256', name: 'minOutputTokenAmount' },
{
type: 'tuple[]',
name: 'transformations',
components: [{ type: 'uint32', name: 'deploymentNonce' }, { type: 'bytes', name: 'data' }],
},
]);
interface CallArgs {
inputToken: string;
outputToken: string;
inputTokenAmount: BigNumber;
minOutputTokenAmount: BigNumber;
transformations: Array<{
deploymentNonce: BigNumber;
data: string;
}>;
}
describe('getCalldataOrThrow()', () => {
it('can produce a sell quote', async () => {
const quote = getRandomSellQuote();
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
expect(callArgs.inputToken).to.eq(TAKER_TOKEN);
expect(callArgs.outputToken).to.eq(MAKER_TOKEN);
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].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);
expect(fillQuoteTransformerData.orders).to.deep.eq(cleanOrders(quote.orders));
expect(fillQuoteTransformerData.signatures).to.deep.eq(quote.orders.map(o => o.signature));
expect(fillQuoteTransformerData.sellToken).to.eq(TAKER_TOKEN);
expect(fillQuoteTransformerData.buyToken).to.eq(MAKER_TOKEN);
const payTakerTransformerData = decodePayTakerTransformerData(callArgs.transformations[1].data);
expect(payTakerTransformerData.amounts).to.deep.eq([]);
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, MAKER_TOKEN, ETH_TOKEN_ADDRESS]);
});
it('can produce a buy quote', async () => {
const quote = getRandomBuyQuote();
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
expect(callArgs.inputToken).to.eq(TAKER_TOKEN);
expect(callArgs.outputToken).to.eq(MAKER_TOKEN);
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].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);
expect(fillQuoteTransformerData.orders).to.deep.eq(cleanOrders(quote.orders));
expect(fillQuoteTransformerData.signatures).to.deep.eq(quote.orders.map(o => o.signature));
expect(fillQuoteTransformerData.sellToken).to.eq(TAKER_TOKEN);
expect(fillQuoteTransformerData.buyToken).to.eq(MAKER_TOKEN);
const payTakerTransformerData = decodePayTakerTransformerData(callArgs.transformations[1].data);
expect(payTakerTransformerData.amounts).to.deep.eq([]);
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, MAKER_TOKEN, ETH_TOKEN_ADDRESS]);
});
it('ERC20 -> ERC20 does not have a WETH transformer', async () => {
const quote = getRandomSellQuote();
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
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 () => {
const quote = getRandomSellQuote();
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
extensionContractOpts: { isFromETH: true },
});
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
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);
});
it('ERC20 -> ETH has a WETH transformer after the fill', async () => {
const quote = getRandomSellQuote();
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
extensionContractOpts: { isToETH: true },
});
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
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);
});
});
});

View File

@@ -65,6 +65,10 @@
{
"note": "Add UniswapV2Bridge address on Mainnet (new field)",
"pr": 2599
},
{
"note": "Add Exchange Proxy addresses",
"pr": 2591
}
]
},

View File

@@ -32,7 +32,16 @@
"curveBridge": "0x6dc7950423ada9f56fb2c93a23edb787f1e29088",
"maximumGasPrice": "0xe2bfd35306495d11e3c9db0d8de390cda24563cf",
"dexForwarderBridge": "0x5591360f8c7640fea5771c9682d6b5ecb776e1f8",
"multiBridge": "0xc03117a8c9bde203f70aa911cb64a7a0df5ba1e1"
"multiBridge": "0xc03117a8c9bde203f70aa911cb64a7a0df5ba1e1",
"exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e",
"exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",
"exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da",
"transformers": {
"wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8",
"payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841",
"fillQuoteTransformer": "0x9b81a08ef144e7aa4925f7fd77da1e1b3990e59a"
}
},
"3": {
"erc20Proxy": "0xb1408f4c245a23c31b98d2c626777d4c0d766caa",
@@ -67,7 +76,16 @@
"curveBridge": "0x0000000000000000000000000000000000000000",
"maximumGasPrice": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a",
"dexForwarderBridge": "0x3be8e59038d8c4e8d8776ca40ef2f024bad95ad1",
"multiBridge": "0x0000000000000000000000000000000000000000"
"multiBridge": "0x0000000000000000000000000000000000000000",
"exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e",
"exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",
"exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da",
"transformers": {
"wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8",
"payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841",
"fillQuoteTransformer": "0x9b81a08ef144e7aa4925f7fd77da1e1b3990e59a"
}
},
"4": {
"exchangeV2": "0xbff9493f92a3df4b0429b6d00743b3cfb4c85831",
@@ -102,7 +120,16 @@
"curveBridge": "0x0000000000000000000000000000000000000000",
"maximumGasPrice": "0x47697b44bd89051e93b4d5857ba8e024800a74ac",
"dexForwarderBridge": "0x0000000000000000000000000000000000000000",
"multiBridge": "0x0000000000000000000000000000000000000000"
"multiBridge": "0x0000000000000000000000000000000000000000",
"exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e",
"exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",
"exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da",
"transformers": {
"wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8",
"payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841",
"fillQuoteTransformer": "0x9b81a08ef144e7aa4925f7fd77da1e1b3990e59a"
}
},
"42": {
"erc20Proxy": "0xf1ec01d6236d3cd881a0bf0130ea25fe4234003e",
@@ -137,7 +164,16 @@
"curveBridge": "0x90c62c91a9f655f4f739e6cee85c84f9ccf47323",
"maximumGasPrice": "0x67a094cf028221ffdd93fc658f963151d05e2a74",
"dexForwarderBridge": "0x6cce442a48ab07635462a40594054f34f44195ff",
"multiBridge": "0x0000000000000000000000000000000000000000"
"multiBridge": "0x0000000000000000000000000000000000000000",
"exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e",
"exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",
"exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da",
"transformers": {
"wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8",
"payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841",
"fillQuoteTransformer": "0x9b81a08ef144e7aa4925f7fd77da1e1b3990e59a"
}
},
"1337": {
"erc20Proxy": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48",
@@ -172,6 +208,15 @@
"curveBridge": "0x0000000000000000000000000000000000000000",
"maximumGasPrice": "0x0000000000000000000000000000000000000000",
"dexForwarderBridge": "0x0000000000000000000000000000000000000000",
"multiBridge": "0x0000000000000000000000000000000000000000"
"multiBridge": "0x0000000000000000000000000000000000000000",
"exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e",
"exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",
"exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da",
"transformers": {
"wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8",
"payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841",
"fillQuoteTransformer": "0x9b81a08ef144e7aa4925f7fd77da1e1b3990e59a"
}
}
}

View File

@@ -34,6 +34,15 @@ export interface ContractAddresses {
maximumGasPrice: string;
dexForwarderBridge: string;
multiBridge: string;
exchangeProxyGovernor: string;
exchangeProxy: string;
exchangeProxyAllowanceTarget: string;
exchangeProxyTransformerDeployer: string;
transformers: {
wethTransformer: string;
payTakerTransformer: string;
fillQuoteTransformer: string;
};
}
export enum ChainId {

View File

@@ -29,6 +29,10 @@
{
"note": "Added `ERC20BridgeSampler.sampleSellsFromMultiBridge",
"pr": 2593
},
{
"note": "Added `ITransformERC20`",
"pr": 2591
}
]
},

View File

@@ -0,0 +1,171 @@
{
"schemaVersion": "2.0.0",
"contractName": "ITransformERC20",
"compilerOutput": {
"abi": [
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "address", "name": "taker", "type": "address" },
{ "indexed": false, "internalType": "address", "name": "inputToken", "type": "address" },
{ "indexed": false, "internalType": "address", "name": "outputToken", "type": "address" },
{ "indexed": false, "internalType": "uint256", "name": "inputTokenAmount", "type": "uint256" },
{ "indexed": false, "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" }
],
"name": "TransformedERC20",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": false, "internalType": "address", "name": "transformerDeployer", "type": "address" }
],
"name": "TransformerDeployerUpdated",
"type": "event"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "callDataHash", "type": "bytes32" },
{ "internalType": "address payable", "name": "taker", "type": "address" },
{ "internalType": "contract IERC20TokenV06", "name": "inputToken", "type": "address" },
{ "internalType": "contract IERC20TokenV06", "name": "outputToken", "type": "address" },
{ "internalType": "uint256", "name": "inputTokenAmount", "type": "uint256" },
{ "internalType": "uint256", "name": "minOutputTokenAmount", "type": "uint256" },
{
"components": [
{ "internalType": "uint32", "name": "deploymentNonce", "type": "uint32" },
{ "internalType": "bytes", "name": "data", "type": "bytes" }
],
"internalType": "struct ITransformERC20.Transformation[]",
"name": "transformations",
"type": "tuple[]"
}
],
"name": "_transformERC20",
"outputs": [{ "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" }],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "createTransformWallet",
"outputs": [{ "internalType": "contract IFlashWallet", "name": "wallet", "type": "address" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getTransformWallet",
"outputs": [{ "internalType": "contract IFlashWallet", "name": "wallet", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getTransformerDeployer",
"outputs": [{ "internalType": "address", "name": "deployer", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "transformerDeployer", "type": "address" }],
"name": "setTransformerDeployer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "contract IERC20TokenV06", "name": "inputToken", "type": "address" },
{ "internalType": "contract IERC20TokenV06", "name": "outputToken", "type": "address" },
{ "internalType": "uint256", "name": "inputTokenAmount", "type": "uint256" },
{ "internalType": "uint256", "name": "minOutputTokenAmount", "type": "uint256" },
{
"components": [
{ "internalType": "uint32", "name": "deploymentNonce", "type": "uint32" },
{ "internalType": "bytes", "name": "data", "type": "bytes" }
],
"internalType": "struct ITransformERC20.Transformation[]",
"name": "transformations",
"type": "tuple[]"
}
],
"name": "transformERC20",
"outputs": [{ "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" }],
"stateMutability": "payable",
"type": "function"
}
],
"devdoc": {
"details": "Feature to composably transform between ERC20 tokens.",
"methods": {
"_transformERC20(bytes32,address,address,address,uint256,uint256,(uint32,bytes)[])": {
"details": "Internal version of `transformERC20()`. Only callable from within.",
"params": {
"callDataHash": "Hash of the ingress calldata.",
"inputToken": "The token being provided by the taker. If `0xeee...`, ETH is implied and should be provided with the call.`",
"inputTokenAmount": "The amount of `inputToken` to take from the taker.",
"minOutputTokenAmount": "The minimum amount of `outputToken` the taker must receive for the entire transformation to succeed.",
"outputToken": "The token to be acquired by the taker. `0xeee...` implies ETH.",
"taker": "The taker address.",
"transformations": "The transformations to execute on the token balance(s) in sequence."
},
"returns": { "outputTokenAmount": "The amount of `outputToken` received by the taker." }
},
"createTransformWallet()": {
"details": "Deploy a new flash wallet instance and replace the current one with it. Useful if we somehow break the current wallet instance. Anyone can call this.",
"returns": { "wallet": "The new wallet instance." }
},
"getTransformWallet()": {
"details": "Return the current wallet instance that will serve as the execution context for transformations.",
"returns": { "wallet": "The wallet instance." }
},
"getTransformerDeployer()": {
"details": "Return the allowed deployer for transformers.",
"returns": { "deployer": "The transform deployer address." }
},
"setTransformerDeployer(address)": {
"details": "Replace the allowed deployer for transformers. Only callable by the owner.",
"params": { "transformerDeployer": "The address of the trusted deployer for transformers." }
},
"transformERC20(address,address,uint256,uint256,(uint32,bytes)[])": {
"details": "Executes a series of transformations to convert an ERC20 `inputToken` to an ERC20 `outputToken`.",
"params": {
"inputToken": "The token being provided by the sender. If `0xeee...`, ETH is implied and should be provided with the call.`",
"inputTokenAmount": "The amount of `inputToken` to take from the sender.",
"minOutputTokenAmount": "The minimum amount of `outputToken` the sender must receive for the entire transformation to succeed.",
"outputToken": "The token to be acquired by the sender. `0xeee...` implies ETH.",
"transformations": "The transformations to execute on the token balance(s) in sequence."
},
"returns": { "outputTokenAmount": "The amount of `outputToken` received by the sender." }
}
}
},
"evm": { "bytecode": { "object": "0x" }, "deployedBytecode": { "immutableReferences": {}, "object": "0x" } }
},
"compiler": {
"name": "solc",
"version": "0.6.9+commit.3e3065ac",
"settings": {
"optimizer": {
"enabled": true,
"runs": 1000000,
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
},
"outputSelection": {
"*": {
"*": [
"abi",
"devdoc",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
},
"evmVersion": "istanbul"
}
},
"chains": {}
}

View File

@@ -26,6 +26,7 @@ import * as ZRXToken from '../artifacts/ZRXToken.json';
import * as ERC20BridgeProxy from '../artifacts/ERC20BridgeProxy.json';
import * as ZrxVault from '../artifacts/ZrxVault.json';
import * as IERC20BridgeSampler from '../artifacts/IERC20BridgeSampler.json';
import * as ITransformERC20 from '../artifacts/ITransformERC20.json';
export {
AssetProxyOwner,
@@ -56,4 +57,5 @@ export {
StakingProxy,
ZrxVault,
IERC20BridgeSampler,
ITransformERC20,
};

View File

@@ -36,6 +36,7 @@
"./artifacts/StakingProxy.json",
"./artifacts/ZrxVault.json",
"./artifacts/ERC20BridgeProxy.json",
"./artifacts/IERC20BridgeSampler.json"
"./artifacts/IERC20BridgeSampler.json",
"./artifacts/ITransformERC20.json"
]
}

View File

@@ -33,6 +33,10 @@
{
"note": "Added `ERC20BridgeSampler.sampleSellsFromMultiBridge",
"pr": 2593
},
{
"note": "Add `ITransformERC20`",
"pr": 2591
}
]
},

View File

@@ -32,7 +32,7 @@
"wrappers:generate": "abi-gen --abis ${npm_package_config_abis} --output src/generated-wrappers --backend ethers"
},
"config": {
"abis": "../contract-artifacts/artifacts/@(DevUtils|ERC20Token|ERC721Token|Exchange|Forwarder|IAssetData|LibTransactionDecoder|WETH9|Coordinator|Staking|StakingProxy|IERC20BridgeSampler|ERC20BridgeSampler|GodsUnchainedValidator|Broker|ILiquidityProvider|ILiquidityProviderRegistry|MaximumGasPrice).json"
"abis": "../contract-artifacts/artifacts/@(DevUtils|ERC20Token|ERC721Token|Exchange|Forwarder|IAssetData|LibTransactionDecoder|WETH9|Coordinator|Staking|StakingProxy|IERC20BridgeSampler|ERC20BridgeSampler|GodsUnchainedValidator|Broker|ILiquidityProvider|ILiquidityProviderRegistry|MaximumGasPrice|ITransformERC20).json"
},
"gitpkg": {
"registry": "git@github.com:0xProject/gitpkg-registry.git"

View File

@@ -0,0 +1,910 @@
// tslint:disable:no-consecutive-blank-lines ordered-imports align trailing-comma enum-naming
// tslint:disable:whitespace no-unbound-method no-trailing-whitespace
// tslint:disable:no-unused-variable
import {
AwaitTransactionSuccessOpts,
ContractFunctionObj,
ContractTxFunctionObj,
SendTransactionOpts,
BaseContract,
SubscriptionManager,
PromiseWithTransactionHash,
methodAbiToFunctionSignature,
linkLibrariesInBytecode,
} from '@0x/base-contract';
import { schemas } from '@0x/json-schemas';
import {
BlockParam,
BlockParamLiteral,
BlockRange,
CallData,
ContractAbi,
ContractArtifact,
DecodedLogArgs,
LogWithDecodedArgs,
MethodAbi,
TransactionReceiptWithDecodedLogs,
TxData,
TxDataPayable,
SupportedProvider,
} from 'ethereum-types';
import { BigNumber, classUtils, hexUtils, logUtils, providerUtils } from '@0x/utils';
import { EventCallback, IndexedFilterValues, SimpleContractArtifact } from '@0x/types';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { assert } from '@0x/assert';
import * as ethers from 'ethers';
// tslint:enable:no-unused-variable
export type ITransformERC20EventArgs =
| ITransformERC20TransformedERC20EventArgs
| ITransformERC20TransformerDeployerUpdatedEventArgs;
export enum ITransformERC20Events {
TransformedERC20 = 'TransformedERC20',
TransformerDeployerUpdated = 'TransformerDeployerUpdated',
}
export interface ITransformERC20TransformedERC20EventArgs extends DecodedLogArgs {
taker: string;
inputToken: string;
outputToken: string;
inputTokenAmount: BigNumber;
outputTokenAmount: BigNumber;
}
export interface ITransformERC20TransformerDeployerUpdatedEventArgs extends DecodedLogArgs {
transformerDeployer: string;
}
/* istanbul ignore next */
// tslint:disable:array-type
// tslint:disable:no-parameter-reassignment
// tslint:disable-next-line:class-name
export class ITransformERC20Contract extends BaseContract {
/**
* @ignore
*/
public static deployedBytecode: string | undefined;
public static contractName = 'ITransformERC20';
private readonly _methodABIIndex: { [name: string]: number } = {};
private readonly _subscriptionManager: SubscriptionManager<ITransformERC20EventArgs, ITransformERC20Events>;
public static async deployFrom0xArtifactAsync(
artifact: ContractArtifact | SimpleContractArtifact,
supportedProvider: SupportedProvider,
txDefaults: Partial<TxData>,
logDecodeDependencies: { [contractName: string]: ContractArtifact | SimpleContractArtifact },
): Promise<ITransformERC20Contract> {
assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [
schemas.addressSchema,
schemas.numberSchema,
schemas.jsNumber,
]);
if (artifact.compilerOutput === undefined) {
throw new Error('Compiler output not found in the artifact file');
}
const provider = providerUtils.standardizeOrThrow(supportedProvider);
const bytecode = artifact.compilerOutput.evm.bytecode.object;
const abi = artifact.compilerOutput.abi;
const logDecodeDependenciesAbiOnly: { [contractName: string]: ContractAbi } = {};
if (Object.keys(logDecodeDependencies) !== undefined) {
for (const key of Object.keys(logDecodeDependencies)) {
logDecodeDependenciesAbiOnly[key] = logDecodeDependencies[key].compilerOutput.abi;
}
}
return ITransformERC20Contract.deployAsync(bytecode, abi, provider, txDefaults, logDecodeDependenciesAbiOnly);
}
public static async deployWithLibrariesFrom0xArtifactAsync(
artifact: ContractArtifact,
libraryArtifacts: { [libraryName: string]: ContractArtifact },
supportedProvider: SupportedProvider,
txDefaults: Partial<TxData>,
logDecodeDependencies: { [contractName: string]: ContractArtifact | SimpleContractArtifact },
): Promise<ITransformERC20Contract> {
assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [
schemas.addressSchema,
schemas.numberSchema,
schemas.jsNumber,
]);
if (artifact.compilerOutput === undefined) {
throw new Error('Compiler output not found in the artifact file');
}
const provider = providerUtils.standardizeOrThrow(supportedProvider);
const abi = artifact.compilerOutput.abi;
const logDecodeDependenciesAbiOnly: { [contractName: string]: ContractAbi } = {};
if (Object.keys(logDecodeDependencies) !== undefined) {
for (const key of Object.keys(logDecodeDependencies)) {
logDecodeDependenciesAbiOnly[key] = logDecodeDependencies[key].compilerOutput.abi;
}
}
const libraryAddresses = await ITransformERC20Contract._deployLibrariesAsync(
artifact,
libraryArtifacts,
new Web3Wrapper(provider),
txDefaults,
);
const bytecode = linkLibrariesInBytecode(artifact, libraryAddresses);
return ITransformERC20Contract.deployAsync(bytecode, abi, provider, txDefaults, logDecodeDependenciesAbiOnly);
}
public static async deployAsync(
bytecode: string,
abi: ContractAbi,
supportedProvider: SupportedProvider,
txDefaults: Partial<TxData>,
logDecodeDependencies: { [contractName: string]: ContractAbi },
): Promise<ITransformERC20Contract> {
assert.isHexString('bytecode', bytecode);
assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [
schemas.addressSchema,
schemas.numberSchema,
schemas.jsNumber,
]);
const provider = providerUtils.standardizeOrThrow(supportedProvider);
const constructorAbi = BaseContract._lookupConstructorAbi(abi);
[] = BaseContract._formatABIDataItemList(constructorAbi.inputs, [], BaseContract._bigNumberToString);
const iface = new ethers.utils.Interface(abi);
const deployInfo = iface.deployFunction;
const txData = deployInfo.encode(bytecode, []);
const web3Wrapper = new Web3Wrapper(provider);
const txDataWithDefaults = await BaseContract._applyDefaultsToContractTxDataAsync(
{
data: txData,
...txDefaults,
},
web3Wrapper.estimateGasAsync.bind(web3Wrapper),
);
const txHash = await web3Wrapper.sendTransactionAsync(txDataWithDefaults);
logUtils.log(`transactionHash: ${txHash}`);
const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync(txHash);
logUtils.log(`ITransformERC20 successfully deployed at ${txReceipt.contractAddress}`);
const contractInstance = new ITransformERC20Contract(
txReceipt.contractAddress as string,
provider,
txDefaults,
logDecodeDependencies,
);
contractInstance.constructorArgs = [];
return contractInstance;
}
/**
* @returns The contract ABI
*/
public static ABI(): ContractAbi {
const abi = [
{
anonymous: false,
inputs: [
{
name: 'taker',
type: 'address',
indexed: true,
},
{
name: 'inputToken',
type: 'address',
indexed: false,
},
{
name: 'outputToken',
type: 'address',
indexed: false,
},
{
name: 'inputTokenAmount',
type: 'uint256',
indexed: false,
},
{
name: 'outputTokenAmount',
type: 'uint256',
indexed: false,
},
],
name: 'TransformedERC20',
outputs: [],
type: 'event',
},
{
anonymous: false,
inputs: [
{
name: 'transformerDeployer',
type: 'address',
indexed: false,
},
],
name: 'TransformerDeployerUpdated',
outputs: [],
type: 'event',
},
{
inputs: [
{
name: 'callDataHash',
type: 'bytes32',
},
{
name: 'taker',
type: 'address',
},
{
name: 'inputToken',
type: 'address',
},
{
name: 'outputToken',
type: 'address',
},
{
name: 'inputTokenAmount',
type: 'uint256',
},
{
name: 'minOutputTokenAmount',
type: 'uint256',
},
{
name: 'transformations',
type: 'tuple[]',
components: [
{
name: 'deploymentNonce',
type: 'uint32',
},
{
name: 'data',
type: 'bytes',
},
],
},
],
name: '_transformERC20',
outputs: [
{
name: 'outputTokenAmount',
type: 'uint256',
},
],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [],
name: 'createTransformWallet',
outputs: [
{
name: 'wallet',
type: 'address',
},
],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'getTransformWallet',
outputs: [
{
name: 'wallet',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getTransformerDeployer',
outputs: [
{
name: 'deployer',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
name: 'transformerDeployer',
type: 'address',
},
],
name: 'setTransformerDeployer',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
name: 'inputToken',
type: 'address',
},
{
name: 'outputToken',
type: 'address',
},
{
name: 'inputTokenAmount',
type: 'uint256',
},
{
name: 'minOutputTokenAmount',
type: 'uint256',
},
{
name: 'transformations',
type: 'tuple[]',
components: [
{
name: 'deploymentNonce',
type: 'uint32',
},
{
name: 'data',
type: 'bytes',
},
],
},
],
name: 'transformERC20',
outputs: [
{
name: 'outputTokenAmount',
type: 'uint256',
},
],
stateMutability: 'payable',
type: 'function',
},
] as ContractAbi;
return abi;
}
protected static async _deployLibrariesAsync(
artifact: ContractArtifact,
libraryArtifacts: { [libraryName: string]: ContractArtifact },
web3Wrapper: Web3Wrapper,
txDefaults: Partial<TxData>,
libraryAddresses: { [libraryName: string]: string } = {},
): Promise<{ [libraryName: string]: string }> {
const links = artifact.compilerOutput.evm.bytecode.linkReferences;
// Go through all linked libraries, recursively deploying them if necessary.
for (const link of Object.values(links)) {
for (const libraryName of Object.keys(link)) {
if (!libraryAddresses[libraryName]) {
// Library not yet deployed.
const libraryArtifact = libraryArtifacts[libraryName];
if (!libraryArtifact) {
throw new Error(`Missing artifact for linked library "${libraryName}"`);
}
// Deploy any dependent libraries used by this library.
await ITransformERC20Contract._deployLibrariesAsync(
libraryArtifact,
libraryArtifacts,
web3Wrapper,
txDefaults,
libraryAddresses,
);
// Deploy this library.
const linkedLibraryBytecode = linkLibrariesInBytecode(libraryArtifact, libraryAddresses);
const txDataWithDefaults = await BaseContract._applyDefaultsToContractTxDataAsync(
{
data: linkedLibraryBytecode,
...txDefaults,
},
web3Wrapper.estimateGasAsync.bind(web3Wrapper),
);
const txHash = await web3Wrapper.sendTransactionAsync(txDataWithDefaults);
logUtils.log(`transactionHash: ${txHash}`);
const { contractAddress } = await web3Wrapper.awaitTransactionSuccessAsync(txHash);
logUtils.log(`${libraryArtifact.contractName} successfully deployed at ${contractAddress}`);
libraryAddresses[libraryArtifact.contractName] = contractAddress as string;
}
}
}
return libraryAddresses;
}
public getFunctionSignature(methodName: string): string {
const index = this._methodABIIndex[methodName];
const methodAbi = ITransformERC20Contract.ABI()[index] as MethodAbi; // tslint:disable-line:no-unnecessary-type-assertion
const functionSignature = methodAbiToFunctionSignature(methodAbi);
return functionSignature;
}
public getABIDecodedTransactionData<T>(methodName: string, callData: string): T {
const functionSignature = this.getFunctionSignature(methodName);
const self = (this as any) as ITransformERC20Contract;
const abiEncoder = self._lookupAbiEncoder(functionSignature);
const abiDecodedCallData = abiEncoder.strictDecode<T>(callData);
return abiDecodedCallData;
}
public getABIDecodedReturnData<T>(methodName: string, callData: string): T {
const functionSignature = this.getFunctionSignature(methodName);
const self = (this as any) as ITransformERC20Contract;
const abiEncoder = self._lookupAbiEncoder(functionSignature);
const abiDecodedCallData = abiEncoder.strictDecodeReturnValue<T>(callData);
return abiDecodedCallData;
}
public getSelector(methodName: string): string {
const functionSignature = this.getFunctionSignature(methodName);
const self = (this as any) as ITransformERC20Contract;
const abiEncoder = self._lookupAbiEncoder(functionSignature);
return abiEncoder.getSelector();
}
/**
* Internal version of `transformERC20()`. Only callable from within.
* @param callDataHash Hash of the ingress calldata.
* @param taker The taker address.
* @param inputToken The token being provided by the taker. If
* `0xeee...`, ETH is implied and should be provided with the call.`
* @param outputToken The token to be acquired by the taker. `0xeee...`
* implies ETH.
* @param inputTokenAmount The amount of `inputToken` to take from the taker.
* @param minOutputTokenAmount The minimum amount of `outputToken` the taker
* must receive for the entire transformation to succeed.
* @param transformations The transformations to execute on the token
* balance(s) in sequence.
*/
public _transformERC20(
callDataHash: string,
taker: string,
inputToken: string,
outputToken: string,
inputTokenAmount: BigNumber,
minOutputTokenAmount: BigNumber,
transformations: Array<{ deploymentNonce: number | BigNumber; data: string }>,
): ContractTxFunctionObj<BigNumber> {
const self = (this as any) as ITransformERC20Contract;
assert.isString('callDataHash', callDataHash);
assert.isString('taker', taker);
assert.isString('inputToken', inputToken);
assert.isString('outputToken', outputToken);
assert.isBigNumber('inputTokenAmount', inputTokenAmount);
assert.isBigNumber('minOutputTokenAmount', minOutputTokenAmount);
assert.isArray('transformations', transformations);
const functionSignature = '_transformERC20(bytes32,address,address,address,uint256,uint256,(uint32,bytes)[])';
return {
async sendTransactionAsync(
txData?: Partial<TxData> | undefined,
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
await this.callAsync(txDataWithDefaults);
}
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
},
awaitTransactionSuccessAsync(
txData?: Partial<TxData>,
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
...txData,
data: this.getABIEncodedTransactionData(),
});
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
},
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<BigNumber>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
callDataHash,
taker.toLowerCase(),
inputToken.toLowerCase(),
outputToken.toLowerCase(),
inputTokenAmount,
minOutputTokenAmount,
transformations,
]);
},
};
}
/**
* Deploy a new flash wallet instance and replace the current one with it.
* Useful if we somehow break the current wallet instance.
* Anyone can call this.
*/
public createTransformWallet(): ContractTxFunctionObj<string> {
const self = (this as any) as ITransformERC20Contract;
const functionSignature = 'createTransformWallet()';
return {
async sendTransactionAsync(
txData?: Partial<TxData> | undefined,
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
await this.callAsync(txDataWithDefaults);
}
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
},
awaitTransactionSuccessAsync(
txData?: Partial<TxData>,
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
...txData,
data: this.getABIEncodedTransactionData(),
});
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
},
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<string> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<string>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, []);
},
};
}
/**
* Return the current wallet instance that will serve as the execution
* context for transformations.
*/
public getTransformWallet(): ContractTxFunctionObj<string> {
const self = (this as any) as ITransformERC20Contract;
const functionSignature = 'getTransformWallet()';
return {
async sendTransactionAsync(
txData?: Partial<TxData> | undefined,
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
await this.callAsync(txDataWithDefaults);
}
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
},
awaitTransactionSuccessAsync(
txData?: Partial<TxData>,
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
...txData,
data: this.getABIEncodedTransactionData(),
});
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
},
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<string> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<string>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, []);
},
};
}
/**
* Return the allowed deployer for transformers.
*/
public getTransformerDeployer(): ContractTxFunctionObj<string> {
const self = (this as any) as ITransformERC20Contract;
const functionSignature = 'getTransformerDeployer()';
return {
async sendTransactionAsync(
txData?: Partial<TxData> | undefined,
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
await this.callAsync(txDataWithDefaults);
}
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
},
awaitTransactionSuccessAsync(
txData?: Partial<TxData>,
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
...txData,
data: this.getABIEncodedTransactionData(),
});
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
},
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<string> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<string>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, []);
},
};
}
/**
* Replace the allowed deployer for transformers.
* Only callable by the owner.
* @param transformerDeployer The address of the trusted deployer for
* transformers.
*/
public setTransformerDeployer(transformerDeployer: string): ContractTxFunctionObj<void> {
const self = (this as any) as ITransformERC20Contract;
assert.isString('transformerDeployer', transformerDeployer);
const functionSignature = 'setTransformerDeployer(address)';
return {
async sendTransactionAsync(
txData?: Partial<TxData> | undefined,
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
await this.callAsync(txDataWithDefaults);
}
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
},
awaitTransactionSuccessAsync(
txData?: Partial<TxData>,
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
...txData,
data: this.getABIEncodedTransactionData(),
});
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
},
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<void> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<void>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [transformerDeployer.toLowerCase()]);
},
};
}
/**
* Executes a series of transformations to convert an ERC20 `inputToken`
* to an ERC20 `outputToken`.
* @param inputToken The token being provided by the sender. If
* `0xeee...`, ETH is implied and should be provided with the call.`
* @param outputToken The token to be acquired by the sender. `0xeee...`
* implies ETH.
* @param inputTokenAmount The amount of `inputToken` to take from the sender.
* @param minOutputTokenAmount The minimum amount of `outputToken` the sender
* must receive for the entire transformation to succeed.
* @param transformations The transformations to execute on the token
* balance(s) in sequence.
*/
public transformERC20(
inputToken: string,
outputToken: string,
inputTokenAmount: BigNumber,
minOutputTokenAmount: BigNumber,
transformations: Array<{ deploymentNonce: number | BigNumber; data: string }>,
): ContractTxFunctionObj<BigNumber> {
const self = (this as any) as ITransformERC20Contract;
assert.isString('inputToken', inputToken);
assert.isString('outputToken', outputToken);
assert.isBigNumber('inputTokenAmount', inputTokenAmount);
assert.isBigNumber('minOutputTokenAmount', minOutputTokenAmount);
assert.isArray('transformations', transformations);
const functionSignature = 'transformERC20(address,address,uint256,uint256,(uint32,bytes)[])';
return {
async sendTransactionAsync(
txData?: Partial<TxData> | undefined,
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
await this.callAsync(txDataWithDefaults);
}
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
},
awaitTransactionSuccessAsync(
txData?: Partial<TxData>,
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
...txData,
data: this.getABIEncodedTransactionData(),
});
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
},
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<BigNumber>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
inputToken.toLowerCase(),
outputToken.toLowerCase(),
inputTokenAmount,
minOutputTokenAmount,
transformations,
]);
},
};
}
/**
* Subscribe to an event type emitted by the ITransformERC20 contract.
* @param eventName The ITransformERC20 contract event you would like to subscribe to.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
* @param callback Callback that gets called when a log is added/removed
* @param isVerbose Enable verbose subscription warnings (e.g recoverable network issues encountered)
* @return Subscription token used later to unsubscribe
*/
public subscribe<ArgsType extends ITransformERC20EventArgs>(
eventName: ITransformERC20Events,
indexFilterValues: IndexedFilterValues,
callback: EventCallback<ArgsType>,
isVerbose: boolean = false,
blockPollingIntervalMs?: number,
): string {
assert.doesBelongToStringEnum('eventName', eventName, ITransformERC20Events);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
assert.isFunction('callback', callback);
const subscriptionToken = this._subscriptionManager.subscribe<ArgsType>(
this.address,
eventName,
indexFilterValues,
ITransformERC20Contract.ABI(),
callback,
isVerbose,
blockPollingIntervalMs,
);
return subscriptionToken;
}
/**
* Cancel a subscription
* @param subscriptionToken Subscription token returned by `subscribe()`
*/
public unsubscribe(subscriptionToken: string): void {
this._subscriptionManager.unsubscribe(subscriptionToken);
}
/**
* Cancels all existing subscriptions
*/
public unsubscribeAll(): void {
this._subscriptionManager.unsubscribeAll();
}
/**
* Gets historical logs without creating a subscription
* @param eventName The ITransformERC20 contract event you would like to subscribe to.
* @param blockRange Block range to get logs from.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
* @return Array of logs that match the parameters
*/
public async getLogsAsync<ArgsType extends ITransformERC20EventArgs>(
eventName: ITransformERC20Events,
blockRange: BlockRange,
indexFilterValues: IndexedFilterValues,
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
assert.doesBelongToStringEnum('eventName', eventName, ITransformERC20Events);
assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
const logs = await this._subscriptionManager.getLogsAsync<ArgsType>(
this.address,
eventName,
blockRange,
indexFilterValues,
ITransformERC20Contract.ABI(),
);
return logs;
}
constructor(
address: string,
supportedProvider: SupportedProvider,
txDefaults?: Partial<TxData>,
logDecodeDependencies?: { [contractName: string]: ContractAbi },
deployedBytecode: string | undefined = ITransformERC20Contract.deployedBytecode,
) {
super(
'ITransformERC20',
ITransformERC20Contract.ABI(),
address,
supportedProvider,
txDefaults,
logDecodeDependencies,
deployedBytecode,
);
classUtils.bindAll(this, ['_abiEncoderByFunctionSignature', 'address', '_web3Wrapper']);
this._subscriptionManager = new SubscriptionManager<ITransformERC20EventArgs, ITransformERC20Events>(
ITransformERC20Contract.ABI(),
this._web3Wrapper,
);
ITransformERC20Contract.ABI().forEach((item, index) => {
if (item.type === 'function') {
const methodAbi = item as MethodAbi;
this._methodABIIndex[methodAbi.name] = index;
}
});
}
}
// tslint:disable:max-file-line-count
// tslint:enable:no-unbound-method no-parameter-reassignment no-consecutive-blank-lines ordered-imports align
// tslint:enable:trailing-comma whitespace no-trailing-whitespace

View File

@@ -83,6 +83,13 @@ export {
StakingProxyStakingContractAttachedToProxyEventArgs,
StakingProxyStakingContractDetachedFromProxyEventArgs,
} from './generated-wrappers/staking_proxy';
export {
ITransformERC20Contract,
ITransformERC20EventArgs,
ITransformERC20Events,
ITransformERC20TransformerDeployerUpdatedEventArgs,
ITransformERC20TransformedERC20EventArgs,
} from './generated-wrappers/i_transform_erc20';
export {
BlockRange,
SupportedProvider,

View File

@@ -13,6 +13,10 @@
{
"note": "Remove `slippagePercentage` SwapQuoter config.",
"pr": 2513
},
{
"note": "Pin `asset-swapper` dependency",
"pr": 2591
}
]
},

View File

@@ -44,7 +44,7 @@
"homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md",
"dependencies": {
"@0x/assert": "^3.0.7",
"@0x/asset-swapper": "^4.4.0",
"@0x/asset-swapper": "4.4.0",
"@0x/contract-wrappers": "^13.6.3",
"@0x/json-schemas": "^5.0.7",
"@0x/subproviders": "^6.0.8",

View File

@@ -13,6 +13,10 @@
{
"note": "Added `UniswapV2Bridge` address on Mainnet",
"pr": 2599
},
{
"note": "Return empty Exchange Proxy addresses",
"pr": 2591
}
]
},

View File

@@ -49,6 +49,8 @@ const allArtifacts = {
...erc20BridgeSamplerArtifacts,
};
const { NULL_ADDRESS } = constants;
/**
* Creates and deploys all the contracts that are required for the latest
* version of the 0x protocol.
@@ -202,8 +204,8 @@ export async function runMigrationsAsync(
txDefaults,
allArtifacts,
exchange.address,
constants.NULL_ADDRESS,
constants.NULL_ADDRESS,
NULL_ADDRESS,
NULL_ADDRESS,
);
// tslint:disable-next-line:no-unused-variable
@@ -278,7 +280,7 @@ export async function runMigrationsAsync(
txDefaults,
allArtifacts,
exchange.address,
exchangeV2Address || constants.NULL_ADDRESS,
exchangeV2Address || NULL_ADDRESS,
etherToken.address,
);
@@ -297,33 +299,42 @@ export async function runMigrationsAsync(
zrxToken: zrxToken.address,
etherToken: etherToken.address,
exchange: exchange.address,
assetProxyOwner: constants.NULL_ADDRESS,
assetProxyOwner: NULL_ADDRESS,
erc20BridgeProxy: erc20BridgeProxy.address,
zeroExGovernor: constants.NULL_ADDRESS,
zeroExGovernor: NULL_ADDRESS,
forwarder: forwarder.address,
coordinatorRegistry: coordinatorRegistry.address,
coordinator: coordinator.address,
multiAssetProxy: multiAssetProxy.address,
staticCallProxy: staticCallProxy.address,
devUtils: devUtils.address,
exchangeV2: exchangeV2Address || constants.NULL_ADDRESS,
exchangeV2: exchangeV2Address || NULL_ADDRESS,
zrxVault: zrxVault.address,
staking: stakingLogic.address,
stakingProxy: stakingProxy.address,
uniswapBridge: constants.NULL_ADDRESS,
uniswapV2Bridge: constants.NULL_ADDRESS,
eth2DaiBridge: constants.NULL_ADDRESS,
kyberBridge: constants.NULL_ADDRESS,
uniswapBridge: NULL_ADDRESS,
eth2DaiBridge: NULL_ADDRESS,
kyberBridge: NULL_ADDRESS,
erc20BridgeSampler: erc20BridgeSampler.address,
chaiBridge: constants.NULL_ADDRESS,
dydxBridge: constants.NULL_ADDRESS,
curveBridge: constants.NULL_ADDRESS,
godsUnchainedValidator: constants.NULL_ADDRESS,
broker: constants.NULL_ADDRESS,
chainlinkStopLimit: constants.NULL_ADDRESS,
maximumGasPrice: constants.NULL_ADDRESS,
dexForwarderBridge: constants.NULL_ADDRESS,
multiBridge: constants.NULL_ADDRESS,
chaiBridge: NULL_ADDRESS,
dydxBridge: NULL_ADDRESS,
curveBridge: NULL_ADDRESS,
uniswapV2Bridge: NULL_ADDRESS,
godsUnchainedValidator: NULL_ADDRESS,
broker: NULL_ADDRESS,
chainlinkStopLimit: NULL_ADDRESS,
maximumGasPrice: NULL_ADDRESS,
dexForwarderBridge: NULL_ADDRESS,
multiBridge: NULL_ADDRESS,
exchangeProxyGovernor: NULL_ADDRESS,
exchangeProxy: NULL_ADDRESS,
exchangeProxyAllowanceTarget: NULL_ADDRESS,
exchangeProxyTransformerDeployer: NULL_ADDRESS,
transformers: {
wethTransformer: NULL_ADDRESS,
payTakerTransformer: NULL_ADDRESS,
fillQuoteTransformer: NULL_ADDRESS,
},
};
return contractAddresses;
}

View File

@@ -9,6 +9,10 @@
{
"note": "Filter `receive` functions from 0.6 ABIs",
"pr": 2540
},
{
"note": "Fix `CompilerOptions` schema",
"pr": 2591
}
]
},

View File

@@ -3,7 +3,7 @@ export const compilerOptionsSchema = {
properties: {
contractsDir: { type: 'string' },
artifactsDir: { type: 'string' },
solcVersion: { type: 'string', pattern: '^\\d+.\\d+.\\d+$' },
solcVersion: { type: 'string', pattern: '^\\d+.\\d+.\\d+\\+commit\\.[a-f0-9]{8}$' },
compilerSettings: { type: 'object' },
contracts: {
oneOf: [