@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", "note": "Add `MetaTransactions` and `SignatureValidator` features",
"pr": 2610 "pr": 2610
},
{
"note": "Update `TransformERC20` and `MetaTransactions` to handle signed calldata.",
"pr": 2626
} }
], ],
"timestamp": 1594788383 "timestamp": 1594788383

View File

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

View File

@ -37,6 +37,33 @@ interface ITransformERC20 {
bytes data; 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`. /// @dev Raised upon a successful `transformERC20`.
/// @param taker The taker (caller) address. /// @param taker The taker (caller) address.
/// @param inputToken The token being provided by the taker. /// @param inputToken The token being provided by the taker.
@ -57,12 +84,23 @@ interface ITransformERC20 {
/// @param transformerDeployer The new deployer address. /// @param transformerDeployer The new deployer address.
event TransformerDeployerUpdated(address transformerDeployer); 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. /// @dev Replace the allowed deployer for transformers.
/// Only callable by the owner. /// 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) function setTransformerDeployer(address transformerDeployer)
external; 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. /// @dev Deploy a new flash wallet instance and replace the current one with it.
/// Useful if we somehow break the current wallet instance. /// Useful if we somehow break the current wallet instance.
/// Only callable by the owner. /// Only callable by the owner.
@ -95,27 +133,9 @@ interface ITransformERC20 {
returns (uint256 outputTokenAmount); returns (uint256 outputTokenAmount);
/// @dev Internal version of `transformERC20()`. Only callable from within. /// @dev Internal version of `transformERC20()`. Only callable from within.
/// @param callDataHash Hash of the ingress calldata. /// @param args A `TransformERC20Args` struct.
/// @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.
/// @return outputTokenAmount The amount of `outputToken` received by the taker. /// @return outputTokenAmount The amount of `outputToken` received by the taker.
function _transformERC20( function _transformERC20(TransformERC20Args calldata args)
bytes32 callDataHash,
address payable taker,
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 minOutputTokenAmount,
Transformation[] calldata transformations
)
external external
payable payable
returns (uint256 outputTokenAmount); returns (uint256 outputTokenAmount);
@ -134,4 +154,11 @@ interface ITransformERC20 {
external external
view view
returns (address deployer); 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 "../fixins/FixinEIP712.sol";
import "../migrations/LibMigrate.sol"; import "../migrations/LibMigrate.sol";
import "../storage/LibMetaTransactionsStorage.sol"; import "../storage/LibMetaTransactionsStorage.sol";
import "./libs/LibSignedCallData.sol";
import "./IMetaTransactions.sol"; import "./IMetaTransactions.sol";
import "./ITransformERC20.sol"; import "./ITransformERC20.sol";
import "./ISignatureValidator.sol"; import "./ISignatureValidator.sol";
@ -43,18 +44,27 @@ contract MetaTransactions is
using LibBytesV06 for bytes; using LibBytesV06 for bytes;
using LibRichErrorsV06 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 { struct ExecuteState {
// Sender of the meta-transaction.
address sender; address sender;
// Hash of the meta-transaction data.
bytes32 hash; bytes32 hash;
// The meta-transaction data.
MetaTransactionData mtx; MetaTransactionData mtx;
// The meta-transaction signature (by `mtx.signer`).
bytes signature; bytes signature;
// The selector of the function being called.
bytes4 selector; bytes4 selector;
// The ETH balance of this contract before performing the call.
uint256 selfBalance; uint256 selfBalance;
// The block number at which the meta-transaction was executed.
uint256 executedBlockNumber; uint256 executedBlockNumber;
} }
struct TransformERC20Args { /// @dev Arguments for a `TransformERC20.transformERC20()` call.
struct ExternalTransformERC20Args {
IERC20TokenV06 inputToken; IERC20TokenV06 inputToken;
IERC20TokenV06 outputToken; IERC20TokenV06 outputToken;
uint256 inputTokenAmount; uint256 inputTokenAmount;
@ -379,7 +389,7 @@ contract MetaTransactions is
// | transformations (offset) | 160 | = 32 // | transformations (offset) | 160 | = 32
// | transformations (data) | 192 | // | transformations (data) | 192 |
TransformERC20Args memory args; ExternalTransformERC20Args memory args;
{ {
bytes memory encodedStructArgs = new bytes(state.mtx.callData.length - 4 + 32); bytes memory encodedStructArgs = new bytes(state.mtx.callData.length - 4 + 32);
// Copy the args data from the original, after the new struct offset prefix. // Copy the args data from the original, after the new struct offset prefix.
@ -388,8 +398,8 @@ contract MetaTransactions is
uint256 fromMem; uint256 fromMem;
uint256 toMem; uint256 toMem;
assembly { assembly {
// Prefix the original calldata with a struct offset, // Prefix the calldata with a struct offset,
// which is just one word over. // which points to just one word over.
mstore(add(encodedStructArgs, 32), 32) mstore(add(encodedStructArgs, 32), 32)
// Copy everything after the selector. // Copy everything after the selector.
fromMem := add(fromCallData, 36) fromMem := add(fromCallData, 36)
@ -398,20 +408,27 @@ contract MetaTransactions is
} }
LibBytesV06.memCopy(toMem, fromMem, fromCallData.length - 4); LibBytesV06.memCopy(toMem, fromMem, fromCallData.length - 4);
// Decode call args for `ITransformERC20.transformERC20()` as a struct. // 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). // Call `ITransformERC20._transformERC20()` (internal variant).
return _callSelf( return _callSelf(
state.hash, state.hash,
abi.encodeWithSelector( abi.encodeWithSelector(
ITransformERC20._transformERC20.selector, ITransformERC20._transformERC20.selector,
keccak256(state.mtx.callData), ITransformERC20.TransformERC20Args({
state.mtx.signer, // taker is mtx signer taker: state.mtx.signer, // taker is mtx signer
args.inputToken, inputToken: args.inputToken,
args.outputToken, outputToken: args.outputToken,
args.inputTokenAmount, inputTokenAmount: args.inputTokenAmount,
args.minOutputTokenAmount, minOutputTokenAmount: args.minOutputTokenAmount,
args.transformations transformations: args.transformations,
callDataHash: callDataHash,
callDataSignature: callDataSignature
})
), ),
state.mtx.value state.mtx.value
); );

View File

@ -106,11 +106,18 @@ contract SignatureValidator is
override override
returns (bool isValid) returns (bool isValid)
{ {
try this.validateHashSignature(hash, signer, signature) { // HACK: `validateHashSignature()` is stateless so we can just perform
isValid = true; // a staticcall against the implementation contract. This avoids the
} catch (bytes memory) { // overhead of going through the proxy. If `validateHashSignature()` ever
isValid = false; // 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. /// @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 "../storage/LibTransformERC20Storage.sol";
import "../transformers/IERC20Transformer.sol"; import "../transformers/IERC20Transformer.sol";
import "../transformers/LibERC20Transformer.sol"; import "../transformers/LibERC20Transformer.sol";
import "./libs/LibSignedCallData.sol";
import "./ITransformERC20.sol"; import "./ITransformERC20.sol";
import "./ITokenSpender.sol"; import "./ITokenSpender.sol";
import "./IFeature.sol"; import "./IFeature.sol";
import "./ISignatureValidator.sol";
import "./ISimpleFunctionRegistry.sol"; import "./ISimpleFunctionRegistry.sol";
@ -43,6 +45,8 @@ contract TransformERC20 is
ITransformERC20, ITransformERC20,
FixinCommon FixinCommon
{ {
using LibSafeMathV06 for uint256;
using LibRichErrorsV06 for bytes;
/// @dev Stack vars for `_transformERC20Private()`. /// @dev Stack vars for `_transformERC20Private()`.
struct TransformERC20PrivateState { struct TransformERC20PrivateState {
@ -55,10 +59,7 @@ contract TransformERC20 is
/// @dev Name of this feature. /// @dev Name of this feature.
string public constant override FEATURE_NAME = "TransformERC20"; string public constant override FEATURE_NAME = "TransformERC20";
/// @dev Version of this feature. /// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 0); uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0);
using LibSafeMathV06 for uint256;
using LibRichErrorsV06 for bytes;
constructor() public FixinCommon() { constructor() public FixinCommon() {
// solhint-disable-next-line no-empty-blocks // solhint-disable-next-line no-empty-blocks
@ -76,6 +77,8 @@ contract TransformERC20 is
_registerFeatureFunction(this.createTransformWallet.selector); _registerFeatureFunction(this.createTransformWallet.selector);
_registerFeatureFunction(this.getTransformWallet.selector); _registerFeatureFunction(this.getTransformWallet.selector);
_registerFeatureFunction(this.setTransformerDeployer.selector); _registerFeatureFunction(this.setTransformerDeployer.selector);
_registerFeatureFunction(this.setQuoteSigner.selector);
_registerFeatureFunction(this.getQuoteSigner.selector);
_registerFeatureFunction(this.transformERC20.selector); _registerFeatureFunction(this.transformERC20.selector);
_registerFeatureFunction(this._transformERC20.selector); _registerFeatureFunction(this._transformERC20.selector);
this.createTransformWallet(); this.createTransformWallet();
@ -95,6 +98,18 @@ contract TransformERC20 is
emit TransformerDeployerUpdated(transformerDeployer); 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. /// @dev Return the allowed deployer for transformers.
/// @return deployer The transform deployer address. /// @return deployer The transform deployer address.
function getTransformerDeployer() function getTransformerDeployer()
@ -106,6 +121,17 @@ contract TransformERC20 is
return LibTransformERC20Storage.getStorage().transformerDeployer; 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. /// @dev Deploy a new wallet instance and replace the current one with it.
/// Useful if we somehow break the current wallet instance. /// Useful if we somehow break the current wallet instance.
/// Only callable by the owner. /// Only callable by the owner.
@ -147,42 +173,26 @@ contract TransformERC20 is
payable payable
returns (uint256 outputTokenAmount) returns (uint256 outputTokenAmount)
{ {
(bytes32 callDataHash, bytes memory callDataSignature) =
LibSignedCallData.parseCallData(msg.data);
return _transformERC20Private( return _transformERC20Private(
keccak256(msg.data), TransformERC20Args({
msg.sender, taker: msg.sender,
inputToken, inputToken: inputToken,
outputToken, outputToken: outputToken,
inputTokenAmount, inputTokenAmount: inputTokenAmount,
minOutputTokenAmount, minOutputTokenAmount: minOutputTokenAmount,
transformations transformations: transformations,
callDataHash: callDataHash,
callDataSignature: callDataSignature
})
); );
} }
/// @dev Internal version of `transformERC20()`. Only callable from within. /// @dev Internal version of `transformERC20()`. Only callable from within.
/// @param callDataHash Hash of the ingress calldata. /// @param args A `TransformERC20Args` struct.
/// @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.
/// @return outputTokenAmount The amount of `outputToken` received by the taker. /// @return outputTokenAmount The amount of `outputToken` received by the taker.
function _transformERC20( function _transformERC20(TransformERC20Args memory args)
bytes32 callDataHash,
address payable taker,
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 minOutputTokenAmount,
Transformation[] memory transformations
)
public public
virtual virtual
override override
@ -190,50 +200,21 @@ contract TransformERC20 is
onlySelf onlySelf
returns (uint256 outputTokenAmount) returns (uint256 outputTokenAmount)
{ {
return _transformERC20Private( return _transformERC20Private(args);
callDataHash,
taker,
inputToken,
outputToken,
inputTokenAmount,
minOutputTokenAmount,
transformations
);
} }
/// @dev Private version of `transformERC20()`. /// @dev Private version of `transformERC20()`.
/// @param callDataHash Hash of the ingress calldata. /// @param args A `TransformERC20Args` struct.
/// @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.
/// @return outputTokenAmount The amount of `outputToken` received by the taker. /// @return outputTokenAmount The amount of `outputToken` received by the taker.
function _transformERC20Private( function _transformERC20Private(TransformERC20Args memory args)
bytes32 callDataHash,
address payable taker,
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 minOutputTokenAmount,
Transformation[] memory transformations
)
private private
returns (uint256 outputTokenAmount) returns (uint256 outputTokenAmount)
{ {
// If the input token amount is -1, transform the taker's entire // If the input token amount is -1, transform the taker's entire
// spendable balance. // spendable balance.
if (inputTokenAmount == uint256(-1)) { if (args.inputTokenAmount == uint256(-1)) {
inputTokenAmount = ITokenSpender(address(this)) args.inputTokenAmount = ITokenSpender(address(this))
.getSpendableERC20BalanceOf(inputToken, taker); .getSpendableERC20BalanceOf(args.inputToken, args.taker);
} }
TransformERC20PrivateState memory state; TransformERC20PrivateState memory state;
@ -242,55 +223,65 @@ contract TransformERC20 is
// Remember the initial output token balance of the taker. // Remember the initial output token balance of the taker.
state.takerOutputTokenBalanceBefore = 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. // Pull input tokens from the taker to the wallet and transfer attached ETH.
_transferInputTokensAndAttachedEth( _transferInputTokensAndAttachedEth(
inputToken, args.inputToken,
taker, args.taker,
address(state.wallet), 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. // Perform transformations.
for (uint256 i = 0; i < transformations.length; ++i) { for (uint256 i = 0; i < args.transformations.length; ++i) {
_executeTransformation( _executeTransformation(
state.wallet, state.wallet,
transformations[i], args.transformations[i],
state.transformerDeployer, state.transformerDeployer,
taker, args.taker,
callDataHash // 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. // Compute how much output token has been transferred to the taker.
state.takerOutputTokenBalanceAfter = state.takerOutputTokenBalanceAfter =
LibERC20Transformer.getTokenBalanceOf(outputToken, taker); LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker);
if (state.takerOutputTokenBalanceAfter > state.takerOutputTokenBalanceBefore) { if (state.takerOutputTokenBalanceAfter > state.takerOutputTokenBalanceBefore) {
outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub( outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
state.takerOutputTokenBalanceBefore state.takerOutputTokenBalanceBefore
); );
} else if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) { } else if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
LibTransformERC20RichErrors.NegativeTransformERC20OutputError( LibTransformERC20RichErrors.NegativeTransformERC20OutputError(
address(outputToken), address(args.outputToken),
state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter
).rrevert(); ).rrevert();
} }
// Ensure enough output token has been sent to the taker. // Ensure enough output token has been sent to the taker.
if (outputTokenAmount < minOutputTokenAmount) { if (outputTokenAmount < args.minOutputTokenAmount) {
LibTransformERC20RichErrors.IncompleteTransformERC20Error( LibTransformERC20RichErrors.IncompleteTransformERC20Error(
address(outputToken), address(args.outputToken),
outputTokenAmount, outputTokenAmount,
minOutputTokenAmount args.minOutputTokenAmount
).rrevert(); ).rrevert();
} }
// Emit an event. // Emit an event.
emit TransformedERC20( emit TransformedERC20(
taker, args.taker,
address(inputToken), address(args.inputToken),
address(outputToken), address(args.outputToken),
inputTokenAmount, args.inputTokenAmount,
outputTokenAmount outputTokenAmount
); );
} }
@ -385,4 +376,29 @@ contract TransformERC20 is
).rrevert(); ).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; IFlashWallet wallet;
// The transformer deployer address. // The transformer deployer address.
address transformerDeployer; address transformerDeployer;
// The optional signer for `transformERC20()` calldata.
address quoteSigner;
} }
/// @dev Get the storage bucket for this contract. /// @dev Get the storage bucket for this contract.

View File

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

View File

@ -45,3 +45,4 @@ export {
export * from './nonce_utils'; export * from './nonce_utils';
export * from './migration'; 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 LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json'; import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
import * as LibSignatureRichErrors from '../test/generated-artifacts/LibSignatureRichErrors.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 LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json'; import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json';
import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json'; import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json';
@ -113,6 +114,7 @@ export const artifacts = {
SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact, SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact,
TokenSpender: TokenSpender as ContractArtifact, TokenSpender: TokenSpender as ContractArtifact,
TransformERC20: TransformERC20 as ContractArtifact, TransformERC20: TransformERC20 as ContractArtifact,
LibSignedCallData: LibSignedCallData as ContractArtifact,
FixinCommon: FixinCommon as ContractArtifact, FixinCommon: FixinCommon as ContractArtifact,
FixinEIP712: FixinEIP712 as ContractArtifact, FixinEIP712: FixinEIP712 as ContractArtifact,
FixinGasToken: FixinGasToken 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 { BigNumber, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { generateCallDataSignature, signCallData } from '../../src/signed_call_data';
import { MetaTransactionsContract, ZeroExContract } from '../../src/wrappers'; import { MetaTransactionsContract, ZeroExContract } from '../../src/wrappers';
import { artifacts } from '../artifacts'; import { artifacts } from '../artifacts';
import { abis } from '../utils/abis'; import { abis } from '../utils/abis';
@ -22,7 +23,7 @@ import {
TestMintableERC20TokenContract, TestMintableERC20TokenContract,
} from '../wrappers'; } from '../wrappers';
const { NULL_ADDRESS, ZERO_AMOUNT } = constants; const { NULL_ADDRESS, NULL_BYTES, ZERO_AMOUNT } = constants;
blockchainTests.resets('MetaTransactions feature', env => { blockchainTests.resets('MetaTransactions feature', env => {
let owner: string; let owner: string;
@ -161,6 +162,50 @@ blockchainTests.resets('MetaTransactions feature', env => {
value: mtx.value, value: mtx.value,
callDataHash: hexUtils.hash(mtx.callData), callDataHash: hexUtils.hash(mtx.callData),
taker: mtx.signer, 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, TestMetaTransactionsTransformERC20FeatureEvents.TransformERC20Called,
@ -237,15 +282,16 @@ blockchainTests.resets('MetaTransactions feature', env => {
}; };
const tx = feature.executeMetaTransaction(mtx, signature).callAsync(callOpts); const tx = feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
const actualCallData = transformERC20Feature const actualCallData = transformERC20Feature
._transformERC20( ._transformERC20({
hexUtils.hash(mtx.callData), taker: mtx.signer,
mtx.signer, inputToken: args.inputToken,
args.inputToken, outputToken: args.outputToken,
args.outputToken, inputTokenAmount: args.inputTokenAmount,
args.inputTokenAmount, minOutputTokenAmount: args.minOutputTokenAmount,
args.minOutputTokenAmount, transformations: args.transformations,
args.transformations, callDataHash: hexUtils.hash(mtx.callData),
) callDataSignature: NULL_BYTES,
})
.getABIEncodedTransactionData(); .getABIEncodedTransactionData();
return expect(tx).to.revertWith( return expect(tx).to.revertWith(
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError( new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(

View File

@ -10,7 +10,10 @@ import {
} from '@0x/contracts-test-utils'; } from '@0x/contracts-test-utils';
import { ETH_TOKEN_ADDRESS } from '@0x/order-utils'; import { ETH_TOKEN_ADDRESS } from '@0x/order-utils';
import { AbiEncoder, hexUtils, OwnableRevertErrors, ZeroExRevertErrors } from '@0x/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 { TransformERC20Contract, ZeroExContract } from '../../src/wrappers';
import { artifacts } from '../artifacts'; import { artifacts } from '../artifacts';
import { abis } from '../utils/abis'; import { abis } from '../utils/abis';
@ -21,10 +24,17 @@ import {
TestMintableERC20TokenContract, TestMintableERC20TokenContract,
TestMintTokenERC20TransformerContract, TestMintTokenERC20TransformerContract,
TestMintTokenERC20TransformerEvents, TestMintTokenERC20TransformerEvents,
TestMintTokenERC20TransformerMintTransformEventArgs,
TransformERC20Events, TransformERC20Events,
} from '../wrappers'; } from '../wrappers';
const { NULL_BYTES, NULL_BYTES32 } = constants;
type MintTokenTransformerEvent = DecodedLogEntry<TestMintTokenERC20TransformerMintTransformEventArgs>;
blockchainTests.resets('TransformERC20 feature', env => { blockchainTests.resets('TransformERC20 feature', env => {
const callDataSignerKey = hexUtils.random();
const callDataSigner = ethjs.bufferToHex(ethjs.privateToAddress(ethjs.toBuffer(callDataSignerKey)));
let owner: string; let owner: string;
let taker: string; let taker: string;
let transformerDeployer: string; let transformerDeployer: string;
@ -54,6 +64,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
allowanceTarget = await new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults) allowanceTarget = await new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults)
.getAllowanceTarget() .getAllowanceTarget()
.callAsync(); .callAsync();
await feature.setQuoteSigner(callDataSigner).awaitTransactionSuccessAsync();
}); });
const { MAX_UINT256, ZERO_AMOUNT } = constants; 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 inputToken: TestMintableERC20TokenContract;
let outputToken: TestMintableERC20TokenContract; let outputToken: TestMintableERC20TokenContract;
let mintTransformer: TestMintTokenERC20TransformerContract; 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 () => { it("succeeds if taker's output token balance increases by exactly minOutputTokenAmount", async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18'); const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18'); const startingInputTokenBalance = getRandomInteger(0, '100e18');
@ -202,15 +236,16 @@ blockchainTests.resets('TransformERC20 feature', env => {
inputTokenBurnAmunt: inputTokenAmount, inputTokenBurnAmunt: inputTokenAmount,
}); });
const receipt = await feature const receipt = await feature
._transformERC20( ._transformERC20({
callDataHash,
taker, taker,
inputToken.address, inputToken: inputToken.address,
outputToken.address, outputToken: outputToken.address,
inputTokenAmount, inputTokenAmount,
minOutputTokenAmount, minOutputTokenAmount,
[transformation], transformations: [transformation],
) callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue }); .awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs( verifyEventsFromLogs(
receipt.logs, receipt.logs,
@ -229,7 +264,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
receipt.logs, receipt.logs,
[ [
{ {
callDataHash, callDataHash: NULL_BYTES32,
taker, taker,
context: wallet.address, context: wallet.address,
caller: zeroEx.address, caller: zeroEx.address,
@ -257,15 +292,16 @@ blockchainTests.resets('TransformERC20 feature', env => {
}); });
const startingOutputTokenBalance = await env.web3Wrapper.getBalanceInWeiAsync(taker); const startingOutputTokenBalance = await env.web3Wrapper.getBalanceInWeiAsync(taker);
const receipt = await feature const receipt = await feature
._transformERC20( ._transformERC20({
callDataHash,
taker, taker,
inputToken.address, inputToken: inputToken.address,
ETH_TOKEN_ADDRESS, outputToken: ETH_TOKEN_ADDRESS,
inputTokenAmount, inputTokenAmount,
minOutputTokenAmount, minOutputTokenAmount,
[transformation], transformations: [transformation],
) callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue }); .awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs( verifyEventsFromLogs(
receipt.logs, receipt.logs,
@ -284,7 +320,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
receipt.logs, receipt.logs,
[ [
{ {
callDataHash, callDataHash: NULL_BYTES32,
taker, taker,
context: wallet.address, context: wallet.address,
caller: zeroEx.address, caller: zeroEx.address,
@ -315,15 +351,16 @@ blockchainTests.resets('TransformERC20 feature', env => {
inputTokenBurnAmunt: inputTokenAmount, inputTokenBurnAmunt: inputTokenAmount,
}); });
const receipt = await feature const receipt = await feature
._transformERC20( ._transformERC20({
callDataHash,
taker, taker,
inputToken.address, inputToken: inputToken.address,
outputToken.address, outputToken: outputToken.address,
inputTokenAmount, inputTokenAmount,
minOutputTokenAmount, minOutputTokenAmount,
[transformation], transformations: [transformation],
) callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue }); .awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs( verifyEventsFromLogs(
receipt.logs, receipt.logs,
@ -342,7 +379,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
receipt.logs, receipt.logs,
[ [
{ {
callDataHash, callDataHash: NULL_BYTES32,
taker, taker,
context: wallet.address, context: wallet.address,
caller: zeroEx.address, caller: zeroEx.address,
@ -365,20 +402,21 @@ blockchainTests.resets('TransformERC20 feature', env => {
const outputTokenMintAmount = minOutputTokenAmount.minus(1); const outputTokenMintAmount = minOutputTokenAmount.minus(1);
const callValue = getRandomInteger(1, '1e18'); const callValue = getRandomInteger(1, '1e18');
const tx = feature const tx = feature
._transformERC20( ._transformERC20({
hexUtils.random(), callDataHash: hexUtils.random(),
taker, taker,
inputToken.address, inputToken: inputToken.address,
outputToken.address, outputToken: outputToken.address,
inputTokenAmount, inputTokenAmount,
minOutputTokenAmount, minOutputTokenAmount,
[ callDataSignature: NULL_BYTES,
transformations: [
createMintTokenTransformation({ createMintTokenTransformation({
outputTokenMintAmount, outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount, inputTokenBurnAmunt: inputTokenAmount,
}), }),
], ],
) })
.awaitTransactionSuccessAsync({ value: callValue }); .awaitTransactionSuccessAsync({ value: callValue });
const expectedError = new ZeroExRevertErrors.TransformERC20.IncompleteTransformERC20Error( const expectedError = new ZeroExRevertErrors.TransformERC20.IncompleteTransformERC20Error(
outputToken.address, outputToken.address,
@ -398,20 +436,21 @@ blockchainTests.resets('TransformERC20 feature', env => {
const outputTokenFeeAmount = 1; const outputTokenFeeAmount = 1;
const callValue = getRandomInteger(1, '1e18'); const callValue = getRandomInteger(1, '1e18');
const tx = feature const tx = feature
._transformERC20( ._transformERC20({
hexUtils.random(), callDataHash: hexUtils.random(),
taker, taker,
inputToken.address, inputToken: inputToken.address,
outputToken.address, outputToken: outputToken.address,
inputTokenAmount, inputTokenAmount,
minOutputTokenAmount, minOutputTokenAmount,
[ callDataSignature: NULL_BYTES,
transformations: [
createMintTokenTransformation({ createMintTokenTransformation({
outputTokenFeeAmount, outputTokenFeeAmount,
inputTokenBurnAmunt: inputTokenAmount, inputTokenBurnAmunt: inputTokenAmount,
}), }),
], ],
) })
.awaitTransactionSuccessAsync({ value: callValue }); .awaitTransactionSuccessAsync({ value: callValue });
const expectedError = new ZeroExRevertErrors.TransformERC20.NegativeTransformERC20OutputError( const expectedError = new ZeroExRevertErrors.TransformERC20.NegativeTransformERC20OutputError(
outputToken.address, outputToken.address,
@ -442,21 +481,22 @@ blockchainTests.resets('TransformERC20 feature', env => {
}), }),
]; ];
const receipt = await feature const receipt = await feature
._transformERC20( ._transformERC20({
callDataHash,
taker, taker,
inputToken.address, inputToken: inputToken.address,
outputToken.address, outputToken: outputToken.address,
inputTokenAmount, inputTokenAmount,
minOutputTokenAmount, minOutputTokenAmount,
transformations, transformations,
) callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue }); .awaitTransactionSuccessAsync({ value: callValue });
verifyEventsFromLogs( verifyEventsFromLogs(
receipt.logs, receipt.logs,
[ [
{ {
callDataHash, callDataHash: NULL_BYTES32,
taker, taker,
context: wallet.address, context: wallet.address,
caller: zeroEx.address, caller: zeroEx.address,
@ -465,7 +505,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
ethBalance: callValue, ethBalance: callValue,
}, },
{ {
callDataHash, callDataHash: NULL_BYTES32,
taker, taker,
context: wallet.address, context: wallet.address,
caller: zeroEx.address, caller: zeroEx.address,
@ -489,15 +529,16 @@ blockchainTests.resets('TransformERC20 feature', env => {
const callDataHash = hexUtils.random(); const callDataHash = hexUtils.random();
const transformations = [createMintTokenTransformation({ deploymentNonce: 1337 })]; const transformations = [createMintTokenTransformation({ deploymentNonce: 1337 })];
const tx = feature const tx = feature
._transformERC20( ._transformERC20({
callDataHash,
taker, taker,
inputToken.address, inputToken: inputToken.address,
outputToken.address, outputToken: outputToken.address,
inputTokenAmount, inputTokenAmount,
minOutputTokenAmount, minOutputTokenAmount,
transformations, transformations,
) callDataHash,
callDataSignature: NULL_BYTES,
})
.awaitTransactionSuccessAsync({ value: callValue }); .awaitTransactionSuccessAsync({ value: callValue });
return expect(tx).to.revertWith( return expect(tx).to.revertWith(
new ZeroExRevertErrors.TransformERC20.TransformerFailedError( 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', 'createTransformWallet',
'getTransformWallet', 'getTransformWallet',
'setTransformerDeployer', 'setTransformerDeployer',
'getQuoteSigner',
'setQuoteSigner',
], ],
}, },
SignatureValidator: { 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_rich_errors';
export * from '../test/generated-wrappers/lib_proxy_storage'; export * from '../test/generated-wrappers/lib_proxy_storage';
export * from '../test/generated-wrappers/lib_signature_rich_errors'; 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_rich_errors';
export * from '../test/generated-wrappers/lib_simple_function_registry_storage'; export * from '../test/generated-wrappers/lib_simple_function_registry_storage';
export * from '../test/generated-wrappers/lib_spender_rich_errors'; export * from '../test/generated-wrappers/lib_spender_rich_errors';

View File

@ -59,6 +59,7 @@
"test/generated-artifacts/LibProxyRichErrors.json", "test/generated-artifacts/LibProxyRichErrors.json",
"test/generated-artifacts/LibProxyStorage.json", "test/generated-artifacts/LibProxyStorage.json",
"test/generated-artifacts/LibSignatureRichErrors.json", "test/generated-artifacts/LibSignatureRichErrors.json",
"test/generated-artifacts/LibSignedCallData.json",
"test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json", "test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json",
"test/generated-artifacts/LibSimpleFunctionRegistryStorage.json", "test/generated-artifacts/LibSimpleFunctionRegistryStorage.json",
"test/generated-artifacts/LibSpenderRichErrors.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, "timestamp": 1594788383,
"version": "5.3.1", "version": "5.3.1",

View File

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

View File

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

View File

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

View File

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