@0x/contracts-zero-ex: Update TransformERC20 and MetaTransactions to handle signed calldata.

This commit is contained in:
Lawrence Forman 2020-07-08 14:43:27 -04:00 committed by Lawrence Forman
parent aae93bb6a7
commit 5f5a158060
22 changed files with 979 additions and 494 deletions

View File

@ -13,6 +13,10 @@
{
"note": "Add `MetaTransactions` and `SignatureValidator` features",
"pr": 2610
},
{
"note": "Update `TransformERC20` and `MetaTransactions` to handle signed calldata.",
"pr": 2626
}
],
"timestamp": 1594788383

View File

@ -28,7 +28,7 @@ interface IMetaTransactions {
/// @dev Describes an exchange proxy meta transaction.
struct MetaTransactionData {
// Signer of meta-transaction. On whose behalf to execute the MTX.
address signer;
address payable signer;
// Required sender, or NULL for anyone.
address sender;
// Minimum gas price.

View File

@ -37,6 +37,33 @@ interface ITransformERC20 {
bytes data;
}
/// @dev Arguments for `_transformERC20()`.
struct TransformERC20Args {
// The taker address.
address payable taker;
// The token being provided by the taker.
// If `0xeee...`, ETH is implied and should be provided with the call.`
IERC20TokenV06 inputToken;
// The token to be acquired by the taker.
// `0xeee...` implies ETH.
IERC20TokenV06 outputToken;
// The amount of `inputToken` to take from the taker.
// If set to `uint256(-1)`, the entire spendable balance of the taker
// will be solt.
uint256 inputTokenAmount;
// The minimum amount of `outputToken` the taker
// must receive for the entire transformation to succeed. If set to zero,
// the minimum output token transfer will not be asserted.
uint256 minOutputTokenAmount;
// The transformations to execute on the token balance(s)
// in sequence.
Transformation[] transformations;
// The hash of the calldata for the `transformERC20()` call.
bytes32 callDataHash;
// The signature for `callDataHash` signed by `getQuoteSigner()`.
bytes callDataSignature;
}
/// @dev Raised upon a successful `transformERC20`.
/// @param taker The taker (caller) address.
/// @param inputToken The token being provided by the taker.
@ -57,12 +84,23 @@ interface ITransformERC20 {
/// @param transformerDeployer The new deployer address.
event TransformerDeployerUpdated(address transformerDeployer);
/// @dev Raised when `setQuoteSigner()` is called.
/// @param quoteSigner The new quote signer.
event QuoteSignerUpdated(address quoteSigner);
/// @dev Replace the allowed deployer for transformers.
/// Only callable by the owner.
/// @param transformerDeployer The address of the trusted deployer for transformers.
/// @param transformerDeployer The address of the new trusted deployer
/// for transformers.
function setTransformerDeployer(address transformerDeployer)
external;
/// @dev Replace the optional signer for `transformERC20()` calldata.
/// Only callable by the owner.
/// @param quoteSigner The address of the new calldata signer.
function setQuoteSigner(address quoteSigner)
external;
/// @dev Deploy a new flash wallet instance and replace the current one with it.
/// Useful if we somehow break the current wallet instance.
/// Only callable by the owner.
@ -95,27 +133,9 @@ interface ITransformERC20 {
returns (uint256 outputTokenAmount);
/// @dev 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.
/// @param args A `TransformERC20Args` struct.
/// @return outputTokenAmount The amount of `outputToken` received by the taker.
function _transformERC20(
bytes32 callDataHash,
address payable taker,
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 minOutputTokenAmount,
Transformation[] calldata transformations
)
function _transformERC20(TransformERC20Args calldata args)
external
payable
returns (uint256 outputTokenAmount);
@ -134,4 +154,11 @@ interface ITransformERC20 {
external
view
returns (address deployer);
/// @dev Return the optional signer for `transformERC20()` calldata.
/// @return signer The transform deployer address.
function getQuoteSigner()
external
view
returns (address signer);
}

View File

@ -26,6 +26,7 @@ import "../fixins/FixinCommon.sol";
import "../fixins/FixinEIP712.sol";
import "../migrations/LibMigrate.sol";
import "../storage/LibMetaTransactionsStorage.sol";
import "./libs/LibSignedCallData.sol";
import "./IMetaTransactions.sol";
import "./ITransformERC20.sol";
import "./ISignatureValidator.sol";
@ -43,18 +44,27 @@ contract MetaTransactions is
using LibBytesV06 for bytes;
using LibRichErrorsV06 for bytes;
/// @dev Intermediate state vars to avoid stack overflows.
/// @dev Intermediate state vars used by `_executeMetaTransactionPrivate()`
/// to avoid stack overflows.
struct ExecuteState {
// Sender of the meta-transaction.
address sender;
// Hash of the meta-transaction data.
bytes32 hash;
// The meta-transaction data.
MetaTransactionData mtx;
// The meta-transaction signature (by `mtx.signer`).
bytes signature;
// The selector of the function being called.
bytes4 selector;
// The ETH balance of this contract before performing the call.
uint256 selfBalance;
// The block number at which the meta-transaction was executed.
uint256 executedBlockNumber;
}
struct TransformERC20Args {
/// @dev Arguments for a `TransformERC20.transformERC20()` call.
struct ExternalTransformERC20Args {
IERC20TokenV06 inputToken;
IERC20TokenV06 outputToken;
uint256 inputTokenAmount;
@ -379,7 +389,7 @@ contract MetaTransactions is
// | transformations (offset) | 160 | = 32
// | transformations (data) | 192 |
TransformERC20Args memory args;
ExternalTransformERC20Args memory args;
{
bytes memory encodedStructArgs = new bytes(state.mtx.callData.length - 4 + 32);
// Copy the args data from the original, after the new struct offset prefix.
@ -388,8 +398,8 @@ contract MetaTransactions is
uint256 fromMem;
uint256 toMem;
assembly {
// Prefix the original calldata with a struct offset,
// which is just one word over.
// Prefix the calldata with a struct offset,
// which points to just one word over.
mstore(add(encodedStructArgs, 32), 32)
// Copy everything after the selector.
fromMem := add(fromCallData, 36)
@ -398,20 +408,27 @@ contract MetaTransactions is
}
LibBytesV06.memCopy(toMem, fromMem, fromCallData.length - 4);
// Decode call args for `ITransformERC20.transformERC20()` as a struct.
args = abi.decode(encodedStructArgs, (TransformERC20Args));
args = abi.decode(encodedStructArgs, (ExternalTransformERC20Args));
}
// Parse the signature and hash out of the calldata so `_transformERC20()`
// can authenticate it.
(bytes32 callDataHash, bytes memory callDataSignature) =
LibSignedCallData.parseCallData(state.mtx.callData);
// Call `ITransformERC20._transformERC20()` (internal variant).
return _callSelf(
state.hash,
abi.encodeWithSelector(
ITransformERC20._transformERC20.selector,
keccak256(state.mtx.callData),
state.mtx.signer, // taker is mtx signer
args.inputToken,
args.outputToken,
args.inputTokenAmount,
args.minOutputTokenAmount,
args.transformations
ITransformERC20.TransformERC20Args({
taker: state.mtx.signer, // taker is mtx signer
inputToken: args.inputToken,
outputToken: args.outputToken,
inputTokenAmount: args.inputTokenAmount,
minOutputTokenAmount: args.minOutputTokenAmount,
transformations: args.transformations,
callDataHash: callDataHash,
callDataSignature: callDataSignature
})
),
state.mtx.value
);

View File

@ -106,11 +106,18 @@ contract SignatureValidator is
override
returns (bool isValid)
{
try this.validateHashSignature(hash, signer, signature) {
isValid = true;
} catch (bytes memory) {
isValid = false;
}
// HACK: `validateHashSignature()` is stateless so we can just perform
// a staticcall against the implementation contract. This avoids the
// overhead of going through the proxy. If `validateHashSignature()` ever
// becomes stateful this would need to change.
(isValid, ) = _implementation.staticcall(
abi.encodeWithSelector(
this.validateHashSignature.selector,
hash,
signer,
signature
)
);
}
/// @dev Validates a hash-only signature type. Low-level, hidden variant.

View File

@ -31,9 +31,11 @@ import "../external/FlashWallet.sol";
import "../storage/LibTransformERC20Storage.sol";
import "../transformers/IERC20Transformer.sol";
import "../transformers/LibERC20Transformer.sol";
import "./libs/LibSignedCallData.sol";
import "./ITransformERC20.sol";
import "./ITokenSpender.sol";
import "./IFeature.sol";
import "./ISignatureValidator.sol";
import "./ISimpleFunctionRegistry.sol";
@ -43,6 +45,8 @@ contract TransformERC20 is
ITransformERC20,
FixinCommon
{
using LibSafeMathV06 for uint256;
using LibRichErrorsV06 for bytes;
/// @dev Stack vars for `_transformERC20Private()`.
struct TransformERC20PrivateState {
@ -55,10 +59,7 @@ contract TransformERC20 is
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "TransformERC20";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 0);
using LibSafeMathV06 for uint256;
using LibRichErrorsV06 for bytes;
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0);
constructor() public FixinCommon() {
// solhint-disable-next-line no-empty-blocks
@ -76,6 +77,8 @@ contract TransformERC20 is
_registerFeatureFunction(this.createTransformWallet.selector);
_registerFeatureFunction(this.getTransformWallet.selector);
_registerFeatureFunction(this.setTransformerDeployer.selector);
_registerFeatureFunction(this.setQuoteSigner.selector);
_registerFeatureFunction(this.getQuoteSigner.selector);
_registerFeatureFunction(this.transformERC20.selector);
_registerFeatureFunction(this._transformERC20.selector);
this.createTransformWallet();
@ -95,6 +98,18 @@ contract TransformERC20 is
emit TransformerDeployerUpdated(transformerDeployer);
}
/// @dev Replace the optional signer for `transformERC20()` calldata.
/// Only callable by the owner.
/// @param quoteSigner The address of the new calldata signer.
function setQuoteSigner(address quoteSigner)
external
override
onlyOwner
{
LibTransformERC20Storage.getStorage().quoteSigner = quoteSigner;
emit QuoteSignerUpdated(quoteSigner);
}
/// @dev Return the allowed deployer for transformers.
/// @return deployer The transform deployer address.
function getTransformerDeployer()
@ -106,6 +121,17 @@ contract TransformERC20 is
return LibTransformERC20Storage.getStorage().transformerDeployer;
}
/// @dev Return the optional signer for `transformERC20()` calldata.
/// @return signer The signer address.
function getQuoteSigner()
public
override
view
returns (address signer)
{
return LibTransformERC20Storage.getStorage().quoteSigner;
}
/// @dev Deploy a new wallet instance and replace the current one with it.
/// Useful if we somehow break the current wallet instance.
/// Only callable by the owner.
@ -147,42 +173,26 @@ contract TransformERC20 is
payable
returns (uint256 outputTokenAmount)
{
(bytes32 callDataHash, bytes memory callDataSignature) =
LibSignedCallData.parseCallData(msg.data);
return _transformERC20Private(
keccak256(msg.data),
msg.sender,
inputToken,
outputToken,
inputTokenAmount,
minOutputTokenAmount,
transformations
TransformERC20Args({
taker: msg.sender,
inputToken: inputToken,
outputToken: outputToken,
inputTokenAmount: inputTokenAmount,
minOutputTokenAmount: minOutputTokenAmount,
transformations: transformations,
callDataHash: callDataHash,
callDataSignature: callDataSignature
})
);
}
/// @dev 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.
/// If set to `uint256(-1)`, the entire spendable balance of the taker
/// will be solt.
/// @param minOutputTokenAmount The minimum amount of `outputToken` the taker
/// must receive for the entire transformation to succeed. If set to zero,
/// the minimum output token transfer will not be asserted.
/// @param transformations The transformations to execute on the token balance(s)
/// in sequence.
/// @param args A `TransformERC20Args` struct.
/// @return outputTokenAmount The amount of `outputToken` received by the taker.
function _transformERC20(
bytes32 callDataHash,
address payable taker,
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 minOutputTokenAmount,
Transformation[] memory transformations
)
function _transformERC20(TransformERC20Args memory args)
public
virtual
override
@ -190,50 +200,21 @@ contract TransformERC20 is
onlySelf
returns (uint256 outputTokenAmount)
{
return _transformERC20Private(
callDataHash,
taker,
inputToken,
outputToken,
inputTokenAmount,
minOutputTokenAmount,
transformations
);
return _transformERC20Private(args);
}
/// @dev Private version of `transformERC20()`.
/// @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.
/// If set to `uint256(-1)`, the entire spendable balance of the taker
/// will be solt.
/// @param minOutputTokenAmount The minimum amount of `outputToken` the taker
/// must receive for the entire transformation to succeed. If set to zero,
/// the minimum output token transfer will not be asserted.
/// @param transformations The transformations to execute on the token balance(s)
/// in sequence.
/// @param args A `TransformERC20Args` struct.
/// @return outputTokenAmount The amount of `outputToken` received by the taker.
function _transformERC20Private(
bytes32 callDataHash,
address payable taker,
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 minOutputTokenAmount,
Transformation[] memory transformations
)
function _transformERC20Private(TransformERC20Args memory args)
private
returns (uint256 outputTokenAmount)
{
// If the input token amount is -1, transform the taker's entire
// spendable balance.
if (inputTokenAmount == uint256(-1)) {
inputTokenAmount = ITokenSpender(address(this))
.getSpendableERC20BalanceOf(inputToken, taker);
if (args.inputTokenAmount == uint256(-1)) {
args.inputTokenAmount = ITokenSpender(address(this))
.getSpendableERC20BalanceOf(args.inputToken, args.taker);
}
TransformERC20PrivateState memory state;
@ -242,55 +223,65 @@ contract TransformERC20 is
// Remember the initial output token balance of the taker.
state.takerOutputTokenBalanceBefore =
LibERC20Transformer.getTokenBalanceOf(outputToken, taker);
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker);
// Pull input tokens from the taker to the wallet and transfer attached ETH.
_transferInputTokensAndAttachedEth(
inputToken,
taker,
args.inputToken,
args.taker,
address(state.wallet),
inputTokenAmount
args.inputTokenAmount
);
{
// Validate that the calldata was signed by the quote signer.
// `validCallDataHash` will be 0x0 if not.
bytes32 validCallDataHash = _getValidCallDataHash(
args.callDataHash,
args.callDataSignature
);
// Perform transformations.
for (uint256 i = 0; i < transformations.length; ++i) {
for (uint256 i = 0; i < args.transformations.length; ++i) {
_executeTransformation(
state.wallet,
transformations[i],
args.transformations[i],
state.transformerDeployer,
taker,
callDataHash
args.taker,
// Transformers will receive a null calldata hash if
// the calldata was not properly signed.
validCallDataHash
);
}
}
// Compute how much output token has been transferred to the taker.
state.takerOutputTokenBalanceAfter =
LibERC20Transformer.getTokenBalanceOf(outputToken, taker);
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker);
if (state.takerOutputTokenBalanceAfter > state.takerOutputTokenBalanceBefore) {
outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
state.takerOutputTokenBalanceBefore
);
} else if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
LibTransformERC20RichErrors.NegativeTransformERC20OutputError(
address(outputToken),
address(args.outputToken),
state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter
).rrevert();
}
// Ensure enough output token has been sent to the taker.
if (outputTokenAmount < minOutputTokenAmount) {
if (outputTokenAmount < args.minOutputTokenAmount) {
LibTransformERC20RichErrors.IncompleteTransformERC20Error(
address(outputToken),
address(args.outputToken),
outputTokenAmount,
minOutputTokenAmount
args.minOutputTokenAmount
).rrevert();
}
// Emit an event.
emit TransformedERC20(
taker,
address(inputToken),
address(outputToken),
inputTokenAmount,
args.taker,
address(args.inputToken),
address(args.outputToken),
args.inputTokenAmount,
outputTokenAmount
);
}
@ -385,4 +376,29 @@ contract TransformERC20 is
).rrevert();
}
}
/// @dev Check if a call data hash is signed by the quote signer.
/// @param callDataHash The hash of the callData.
/// @param signature The signature provided by `getQuoteSigner()`.
/// @return validCallDataHash `callDataHash` if so and `0x0` otherwise.
function _getValidCallDataHash(
bytes32 callDataHash,
bytes memory signature
)
private
view
returns (bytes32 validCallDataHash)
{
if (signature.length == 0) {
return bytes32(0);
}
if (ISignatureValidator(address(this)).isValidHashSignature(
callDataHash,
getQuoteSigner(),
signature
)) {
return callDataHash;
}
}
}

View File

@ -0,0 +1,67 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
/// @dev Library for working with signed calldata.
library LibSignedCallData {
using LibBytesV06 for bytes;
// bytes4(keccak256('SignedCallDataSignature(bytes)'))
bytes4 constant private SIGNATURE_SELECTOR = 0xf86d1d92;
/// @dev Try to parse potentially signed calldata into its hash and signature
/// components. Signed calldata has signature data appended to it.
/// @param callData the raw call data.
/// @return callDataHash The hash of the signed callData, or 0x0 if no signature
/// is present.
/// @return signature The signature bytes, if present.
function parseCallData(bytes memory callData)
internal
pure
returns (bytes32 callDataHash, bytes memory signature)
{
// Signed calldata has a 70 byte signature appended as:
// ```
// abi.encodePacked(
// callData,
// bytes4(keccak256('SignedCallDataSignature(bytes)')),
// signature // 66 bytes
// );
// ```
if (
// Signed callData has to be at least 70 bytes long.
callData.length < 70 ||
// The bytes4 at offset -70 should equal `SIGNATURE_SELECTOR`.
SIGNATURE_SELECTOR != callData.readBytes4(callData.length - 70)
) {
return (keccak256(callData), signature);
}
// Consider everything before the signature selector as the original
// calldata and everything after as the signature.
assembly {
callDataHash := keccak256(add(callData, 32), sub(mload(callData), 70))
}
signature = callData.slice(callData.length - 66, callData.length);
}
}

View File

@ -32,6 +32,8 @@ library LibTransformERC20Storage {
IFlashWallet wallet;
// The transformer deployer address.
address transformerDeployer;
// The optional signer for `transformERC20()` calldata.
address quoteSigner;
}
/// @dev Get the storage bucket for this contract.

View File

@ -28,24 +28,17 @@ contract TestMetaTransactionsTransformERC20Feature is
event TransformERC20Called(
address sender,
uint256 value,
bytes32 callDataHash,
address taker,
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 minOutputTokenAmount,
Transformation[] transformations
Transformation[] transformations,
bytes32 callDataHash,
bytes callDataSignature
);
function _transformERC20(
bytes32 callDataHash,
address payable taker,
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 minOutputTokenAmount,
Transformation[] memory transformations
)
function _transformERC20(TransformERC20Args memory args)
public
override
payable
@ -58,13 +51,14 @@ contract TestMetaTransactionsTransformERC20Feature is
emit TransformERC20Called(
msg.sender,
msg.value,
callDataHash,
taker,
inputToken,
outputToken,
inputTokenAmount,
minOutputTokenAmount,
transformations
args.taker,
args.inputToken,
args.outputToken,
args.inputTokenAmount,
args.minOutputTokenAmount,
args.transformations,
args.callDataHash,
args.callDataSignature
);
return 1337;
}

View File

@ -45,3 +45,4 @@ export {
export * from './nonce_utils';
export * from './migration';
export * from './signed_call_data';

View File

@ -0,0 +1,29 @@
import { SignatureType } from '@0x/types';
import { hexUtils } from '@0x/utils';
import * as ethjs from 'ethereumjs-util';
/**
* Generate calldata with a signature appended.
*/
export function signCallData(callData: string, privateKey: string): string {
const prefix = ethjs.sha3('SignedCallDataSignature(bytes)').slice(0, 4);
return hexUtils.concat(callData, prefix, generateCallDataSignature(callData, privateKey));
}
/**
* Generate a signature for calldata.
*/
export function generateCallDataSignature(callData: string, privateKey: string): string {
return generateCallDataHashSignature(hexUtils.hash(callData), privateKey);
}
/**
* Generate a signature for calldata hash.
*/
export function generateCallDataHashSignature(callDataHash: string, privateKey: string): string {
const { r, s, v } = ethjs.ecsign(
ethjs.hashPersonalMessage(ethjs.toBuffer(callDataHash)),
ethjs.toBuffer(privateKey),
);
return hexUtils.concat(v, r, s, SignatureType.EthSign);
}

View File

@ -41,6 +41,7 @@ import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorag
import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
import * as LibSignatureRichErrors from '../test/generated-artifacts/LibSignatureRichErrors.json';
import * as LibSignedCallData from '../test/generated-artifacts/LibSignedCallData.json';
import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json';
import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json';
@ -113,6 +114,7 @@ export const artifacts = {
SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact,
TokenSpender: TokenSpender as ContractArtifact,
TransformERC20: TransformERC20 as ContractArtifact,
LibSignedCallData: LibSignedCallData as ContractArtifact,
FixinCommon: FixinCommon as ContractArtifact,
FixinEIP712: FixinEIP712 as ContractArtifact,
FixinGasToken: FixinGasToken as ContractArtifact,

View File

@ -11,6 +11,7 @@ import { ExchangeProxyMetaTransaction } from '@0x/types';
import { BigNumber, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
import * as _ from 'lodash';
import { generateCallDataSignature, signCallData } from '../../src/signed_call_data';
import { MetaTransactionsContract, ZeroExContract } from '../../src/wrappers';
import { artifacts } from '../artifacts';
import { abis } from '../utils/abis';
@ -22,7 +23,7 @@ import {
TestMintableERC20TokenContract,
} from '../wrappers';
const { NULL_ADDRESS, ZERO_AMOUNT } = constants;
const { NULL_ADDRESS, NULL_BYTES, ZERO_AMOUNT } = constants;
blockchainTests.resets('MetaTransactions feature', env => {
let owner: string;
@ -161,6 +162,50 @@ blockchainTests.resets('MetaTransactions feature', env => {
value: mtx.value,
callDataHash: hexUtils.hash(mtx.callData),
taker: mtx.signer,
callDataSignature: NULL_BYTES,
},
],
TestMetaTransactionsTransformERC20FeatureEvents.TransformERC20Called,
);
});
it('can call `TransformERC20.transformERC20()` with signed calldata', async () => {
const args = getRandomTransformERC20Args();
const callData = transformERC20Feature
.transformERC20(
args.inputToken,
args.outputToken,
args.inputTokenAmount,
args.minOutputTokenAmount,
args.transformations,
)
.getABIEncodedTransactionData();
const callDataSignerKey = hexUtils.random();
const callDataSignature = generateCallDataSignature(callData, callDataSignerKey);
const signedCallData = signCallData(callData, callDataSignerKey);
const mtx = getRandomMetaTransaction({ callData: signedCallData });
const signature = await signMetaTransactionAsync(mtx);
const callOpts = {
gasPrice: mtx.minGasPrice,
value: mtx.value,
};
const rawResult = await feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
expect(rawResult).to.eq(RAW_SUCCESS_RESULT);
const receipt = await feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
verifyEventsFromLogs(
receipt.logs,
[
{
inputToken: args.inputToken,
outputToken: args.outputToken,
inputTokenAmount: args.inputTokenAmount,
minOutputTokenAmount: args.minOutputTokenAmount,
transformations: args.transformations,
sender: zeroEx.address,
value: mtx.value,
callDataHash: hexUtils.hash(callData),
taker: mtx.signer,
callDataSignature,
},
],
TestMetaTransactionsTransformERC20FeatureEvents.TransformERC20Called,
@ -237,15 +282,16 @@ blockchainTests.resets('MetaTransactions feature', env => {
};
const tx = feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
const actualCallData = transformERC20Feature
._transformERC20(
hexUtils.hash(mtx.callData),
mtx.signer,
args.inputToken,
args.outputToken,
args.inputTokenAmount,
args.minOutputTokenAmount,
args.transformations,
)
._transformERC20({
taker: mtx.signer,
inputToken: args.inputToken,
outputToken: args.outputToken,
inputTokenAmount: args.inputTokenAmount,
minOutputTokenAmount: args.minOutputTokenAmount,
transformations: args.transformations,
callDataHash: hexUtils.hash(mtx.callData),
callDataSignature: NULL_BYTES,
})
.getABIEncodedTransactionData();
return expect(tx).to.revertWith(
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(

View File

@ -10,7 +10,10 @@ import {
} from '@0x/contracts-test-utils';
import { ETH_TOKEN_ADDRESS } from '@0x/order-utils';
import { AbiEncoder, hexUtils, OwnableRevertErrors, ZeroExRevertErrors } from '@0x/utils';
import { DecodedLogEntry } from 'ethereum-types';
import * as ethjs from 'ethereumjs-util';
import { generateCallDataHashSignature, signCallData } from '../../src/signed_call_data';
import { TransformERC20Contract, ZeroExContract } from '../../src/wrappers';
import { artifacts } from '../artifacts';
import { abis } from '../utils/abis';
@ -21,10 +24,17 @@ import {
TestMintableERC20TokenContract,
TestMintTokenERC20TransformerContract,
TestMintTokenERC20TransformerEvents,
TestMintTokenERC20TransformerMintTransformEventArgs,
TransformERC20Events,
} from '../wrappers';
const { NULL_BYTES, NULL_BYTES32 } = constants;
type MintTokenTransformerEvent = DecodedLogEntry<TestMintTokenERC20TransformerMintTransformEventArgs>;
blockchainTests.resets('TransformERC20 feature', env => {
const callDataSignerKey = hexUtils.random();
const callDataSigner = ethjs.bufferToHex(ethjs.privateToAddress(ethjs.toBuffer(callDataSignerKey)));
let owner: string;
let taker: string;
let transformerDeployer: string;
@ -54,6 +64,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
allowanceTarget = await new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults)
.getAllowanceTarget()
.callAsync();
await feature.setQuoteSigner(callDataSigner).awaitTransactionSuccessAsync();
});
const { MAX_UINT256, ZERO_AMOUNT } = constants;
@ -101,7 +112,29 @@ blockchainTests.resets('TransformERC20 feature', env => {
});
});
describe('_transformERC20()', () => {
describe('quote signer', () => {
it('`getQuoteSigner()` returns the quote signer', async () => {
const actualSigner = await feature.getQuoteSigner().callAsync();
expect(actualSigner).to.eq(callDataSigner);
});
it('owner can set the quote signer with `setQuoteSigner()`', async () => {
const newSigner = randomAddress();
const receipt = await feature.setQuoteSigner(newSigner).awaitTransactionSuccessAsync({ from: owner });
verifyEventsFromLogs(receipt.logs, [{ quoteSigner: newSigner }], TransformERC20Events.QuoteSignerUpdated);
const actualSigner = await feature.getQuoteSigner().callAsync();
expect(actualSigner).to.eq(newSigner);
});
it('non-owner cannot set the transformer deployer with `setTransformerDeployer()`', async () => {
const newSigner = randomAddress();
const notOwner = randomAddress();
const tx = feature.setQuoteSigner(newSigner).callAsync({ from: notOwner });
return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner, owner));
});
});
describe('_transformERC20()/transformERC20()', () => {
let inputToken: TestMintableERC20TokenContract;
let outputToken: TestMintableERC20TokenContract;
let mintTransformer: TestMintTokenERC20TransformerContract;
@ -187,6 +220,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
};
}
describe('_transformERC20()', () => {
it("succeeds if taker's output token balance increases by exactly minOutputTokenAmount", async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
@ -202,15 +236,16 @@ blockchainTests.resets('TransformERC20 feature', env => {
inputTokenBurnAmunt: inputTokenAmount,
});
const receipt = await feature
._transformERC20(
callDataHash,
._transformERC20({
taker,
inputToken.address,
outputToken.address,
inputToken: inputToken.address,
outputToken: outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
)
transformations: [transformation],
callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs(
receipt.logs,
@ -229,7 +264,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
receipt.logs,
[
{
callDataHash,
callDataHash: NULL_BYTES32,
taker,
context: wallet.address,
caller: zeroEx.address,
@ -257,15 +292,16 @@ blockchainTests.resets('TransformERC20 feature', env => {
});
const startingOutputTokenBalance = await env.web3Wrapper.getBalanceInWeiAsync(taker);
const receipt = await feature
._transformERC20(
callDataHash,
._transformERC20({
taker,
inputToken.address,
ETH_TOKEN_ADDRESS,
inputToken: inputToken.address,
outputToken: ETH_TOKEN_ADDRESS,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
)
transformations: [transformation],
callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs(
receipt.logs,
@ -284,7 +320,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
receipt.logs,
[
{
callDataHash,
callDataHash: NULL_BYTES32,
taker,
context: wallet.address,
caller: zeroEx.address,
@ -315,15 +351,16 @@ blockchainTests.resets('TransformERC20 feature', env => {
inputTokenBurnAmunt: inputTokenAmount,
});
const receipt = await feature
._transformERC20(
callDataHash,
._transformERC20({
taker,
inputToken.address,
outputToken.address,
inputToken: inputToken.address,
outputToken: outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
)
transformations: [transformation],
callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs(
receipt.logs,
@ -342,7 +379,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
receipt.logs,
[
{
callDataHash,
callDataHash: NULL_BYTES32,
taker,
context: wallet.address,
caller: zeroEx.address,
@ -365,20 +402,21 @@ blockchainTests.resets('TransformERC20 feature', env => {
const outputTokenMintAmount = minOutputTokenAmount.minus(1);
const callValue = getRandomInteger(1, '1e18');
const tx = feature
._transformERC20(
hexUtils.random(),
._transformERC20({
callDataHash: hexUtils.random(),
taker,
inputToken.address,
outputToken.address,
inputToken: inputToken.address,
outputToken: outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[
callDataSignature: NULL_BYTES,
transformations: [
createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
}),
],
)
})
.awaitTransactionSuccessAsync({ value: callValue });
const expectedError = new ZeroExRevertErrors.TransformERC20.IncompleteTransformERC20Error(
outputToken.address,
@ -398,20 +436,21 @@ blockchainTests.resets('TransformERC20 feature', env => {
const outputTokenFeeAmount = 1;
const callValue = getRandomInteger(1, '1e18');
const tx = feature
._transformERC20(
hexUtils.random(),
._transformERC20({
callDataHash: hexUtils.random(),
taker,
inputToken.address,
outputToken.address,
inputToken: inputToken.address,
outputToken: outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[
callDataSignature: NULL_BYTES,
transformations: [
createMintTokenTransformation({
outputTokenFeeAmount,
inputTokenBurnAmunt: inputTokenAmount,
}),
],
)
})
.awaitTransactionSuccessAsync({ value: callValue });
const expectedError = new ZeroExRevertErrors.TransformERC20.NegativeTransformERC20OutputError(
outputToken.address,
@ -442,21 +481,22 @@ blockchainTests.resets('TransformERC20 feature', env => {
}),
];
const receipt = await feature
._transformERC20(
callDataHash,
._transformERC20({
taker,
inputToken.address,
outputToken.address,
inputToken: inputToken.address,
outputToken: outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations,
)
callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
callDataHash,
callDataHash: NULL_BYTES32,
taker,
context: wallet.address,
caller: zeroEx.address,
@ -465,7 +505,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
ethBalance: callValue,
},
{
callDataHash,
callDataHash: NULL_BYTES32,
taker,
context: wallet.address,
caller: zeroEx.address,
@ -489,15 +529,16 @@ blockchainTests.resets('TransformERC20 feature', env => {
const callDataHash = hexUtils.random();
const transformations = [createMintTokenTransformation({ deploymentNonce: 1337 })];
const tx = feature
._transformERC20(
callDataHash,
._transformERC20({
taker,
inputToken.address,
outputToken.address,
inputToken: inputToken.address,
outputToken: outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations,
)
callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.TransformERC20.TransformerFailedError(
@ -507,5 +548,224 @@ blockchainTests.resets('TransformERC20 feature', env => {
),
);
});
it('passes the calldata hash to transformer with proper signature', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const receipt = await feature
._transformERC20({
taker,
inputToken: inputToken.address,
outputToken: outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations: [transformation],
callDataHash,
callDataSignature: generateCallDataHashSignature(callDataHash, callDataSignerKey),
})
.awaitTransactionSuccessAsync({ value: callValue });
const { callDataHash: actualCallDataHash } = (receipt.logs[0] as MintTokenTransformerEvent).args;
expect(actualCallDataHash).to.eq(callDataHash);
});
it('passes empty calldata hash to transformer with improper signature', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const receipt = await feature
._transformERC20({
taker,
inputToken: inputToken.address,
outputToken: outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations: [transformation],
callDataHash,
callDataSignature: generateCallDataHashSignature(callDataHash, hexUtils.random()),
})
.awaitTransactionSuccessAsync({ value: callValue });
const { callDataHash: actualCallDataHash } = (receipt.logs[0] as MintTokenTransformerEvent).args;
expect(actualCallDataHash).to.eq(NULL_BYTES32);
});
it('passes empty calldata hash to transformer with no signature', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const callDataHash = hexUtils.random();
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const receipt = await feature
._transformERC20({
taker,
inputToken: inputToken.address,
outputToken: outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
transformations: [transformation],
callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue });
const { callDataHash: actualCallDataHash } = (receipt.logs[0] as MintTokenTransformerEvent).args;
expect(actualCallDataHash).to.eq(NULL_BYTES32);
});
});
describe('transformERC20()', () => {
it('passes the calldata hash to transformer with properly signed calldata', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const bakedCall = feature.transformERC20(
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
);
const callData = bakedCall.getABIEncodedTransactionData();
const signedCallData = signCallData(callData, callDataSignerKey);
const receipt = await bakedCall.awaitTransactionSuccessAsync({
from: taker,
value: callValue,
data: signedCallData,
});
const { callDataHash: actualCallDataHash } = (receipt.logs[0] as MintTokenTransformerEvent).args;
expect(actualCallDataHash).to.eq(hexUtils.hash(callData));
});
it('passes empty calldata hash to transformer with improperly signed calldata', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const bakedCall = feature.transformERC20(
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
);
const callData = bakedCall.getABIEncodedTransactionData();
const signedCallData = signCallData(callData, hexUtils.random());
const receipt = await bakedCall.awaitTransactionSuccessAsync({
from: taker,
value: callValue,
data: signedCallData,
});
const { callDataHash: actualCallDataHash } = (receipt.logs[0] as MintTokenTransformerEvent).args;
expect(actualCallDataHash).to.eq(NULL_BYTES32);
});
it('passes empty calldata hash to transformer with unsigned calldata', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const bakedCall = feature.transformERC20(
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
);
const callData = bakedCall.getABIEncodedTransactionData();
const receipt = await bakedCall.awaitTransactionSuccessAsync({
from: taker,
value: callValue,
data: callData,
});
const { callDataHash: actualCallDataHash } = (receipt.logs[0] as MintTokenTransformerEvent).args;
expect(actualCallDataHash).to.eq(NULL_BYTES32);
});
it('passes empty calldata hash to transformer with calldata with malformed signature', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const bakedCall = feature.transformERC20(
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
);
const callData = bakedCall.getABIEncodedTransactionData();
const signedCallData = hexUtils.concat(
hexUtils.slice(signCallData(callData, hexUtils.random()), 0, -1),
127,
);
const receipt = await bakedCall.awaitTransactionSuccessAsync({
from: taker,
value: callValue,
data: signedCallData,
});
const { callDataHash: actualCallDataHash } = (receipt.logs[0] as MintTokenTransformerEvent).args;
expect(actualCallDataHash).to.eq(NULL_BYTES32);
});
});
});
});

View File

@ -81,6 +81,8 @@ blockchainTests.resets('Full migration', env => {
'createTransformWallet',
'getTransformWallet',
'setTransformerDeployer',
'getQuoteSigner',
'setQuoteSigner',
],
},
SignatureValidator: {

View File

@ -39,6 +39,7 @@ export * from '../test/generated-wrappers/lib_ownable_storage';
export * from '../test/generated-wrappers/lib_proxy_rich_errors';
export * from '../test/generated-wrappers/lib_proxy_storage';
export * from '../test/generated-wrappers/lib_signature_rich_errors';
export * from '../test/generated-wrappers/lib_signed_call_data';
export * from '../test/generated-wrappers/lib_simple_function_registry_rich_errors';
export * from '../test/generated-wrappers/lib_simple_function_registry_storage';
export * from '../test/generated-wrappers/lib_spender_rich_errors';

View File

@ -59,6 +59,7 @@
"test/generated-artifacts/LibProxyRichErrors.json",
"test/generated-artifacts/LibProxyStorage.json",
"test/generated-artifacts/LibSignatureRichErrors.json",
"test/generated-artifacts/LibSignedCallData.json",
"test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json",
"test/generated-artifacts/LibSimpleFunctionRegistryStorage.json",
"test/generated-artifacts/LibSpenderRichErrors.json",

View File

@ -1,4 +1,13 @@
[
{
"version": "5.4.0",
"changes": [
{
"note": "Allow overriding of `data` with contract calls and transactions",
"pr": 2626
}
]
},
{
"timestamp": 1594788383,
"version": "5.3.1",

View File

@ -8,10 +8,10 @@ async callAsync(
if (self._deployedBytecodeIfExists) {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync({ ...callData, data: this.getABIEncodedTransactionData() }, defaultBlock);
rawCallResult = await self._performCallAsync({ data: this.getABIEncodedTransactionData(), ...callData }, defaultBlock);
}
{{else}}
const rawCallResult = await self._performCallAsync({ ...callData, data: this.getABIEncodedTransactionData() }, defaultBlock);
const rawCallResult = await self._performCallAsync({ data: this.getABIEncodedTransactionData(), ...callData }, defaultBlock);
{{/ifEquals}}
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);

View File

@ -3,7 +3,7 @@ async sendTransactionAsync(
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...txData },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
@ -21,7 +21,7 @@ async estimateGasAsync(
txData?: Partial<TxData> | undefined,
): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() }
{ data: this.getABIEncodedTransactionData(), ...txData }
);
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
},

View File

@ -997,7 +997,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1023,7 +1023,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1069,7 +1069,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1114,7 +1114,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1137,7 +1137,7 @@ export class AbiGenDummyContract extends BaseContract {
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...txData },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
@ -1153,15 +1153,15 @@ export class AbiGenDummyContract extends BaseContract {
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
...txData,
data: this.getABIEncodedTransactionData(),
...txData,
});
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() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
@ -1193,7 +1193,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1226,7 +1226,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1258,7 +1258,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1285,7 +1285,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1315,7 +1315,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1353,7 +1353,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1387,7 +1387,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1421,7 +1421,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1457,7 +1457,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1485,7 +1485,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1508,7 +1508,7 @@ export class AbiGenDummyContract extends BaseContract {
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...txData },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
@ -1524,15 +1524,15 @@ export class AbiGenDummyContract extends BaseContract {
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
...txData,
data: this.getABIEncodedTransactionData(),
...txData,
});
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() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
@ -1554,7 +1554,7 @@ export class AbiGenDummyContract extends BaseContract {
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...txData },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
@ -1570,15 +1570,15 @@ export class AbiGenDummyContract extends BaseContract {
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
...txData,
data: this.getABIEncodedTransactionData(),
...txData,
});
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() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
@ -1603,7 +1603,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1629,7 +1629,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1654,7 +1654,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1679,7 +1679,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1704,7 +1704,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1733,7 +1733,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1762,7 +1762,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1787,7 +1787,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1813,7 +1813,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1838,7 +1838,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1863,7 +1863,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1894,7 +1894,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1931,7 +1931,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1972,7 +1972,7 @@ export class AbiGenDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -1996,7 +1996,7 @@ export class AbiGenDummyContract extends BaseContract {
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...txData },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
@ -2012,15 +2012,15 @@ export class AbiGenDummyContract extends BaseContract {
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
...txData,
data: this.getABIEncodedTransactionData(),
...txData,
});
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() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);

View File

@ -280,7 +280,7 @@ export class TestLibDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}
@ -306,7 +306,7 @@ export class TestLibDummyContract extends BaseContract {
rawCallResult = await self._evmExecAsync(this.getABIEncodedTransactionData());
} else {
rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
}