@0x/contracts-zero-ex
: Update TransformERC20
and MetaTransactions
to handle signed calldata.
This commit is contained in:
parent
aae93bb6a7
commit
5f5a158060
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -45,3 +45,4 @@ export {
|
||||
|
||||
export * from './nonce_utils';
|
||||
export * from './migration';
|
||||
export * from './signed_call_data';
|
||||
|
29
contracts/zero-ex/src/signed_call_data.ts
Normal file
29
contracts/zero-ex/src/signed_call_data.ts
Normal 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);
|
||||
}
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -81,6 +81,8 @@ blockchainTests.resets('Full migration', env => {
|
||||
'createTransformWallet',
|
||||
'getTransformWallet',
|
||||
'setTransformerDeployer',
|
||||
'getQuoteSigner',
|
||||
'setQuoteSigner',
|
||||
],
|
||||
},
|
||||
SignatureValidator: {
|
||||
|
@ -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';
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user