@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",
|
"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
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
);
|
);
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
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.
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -45,3 +45,4 @@ export {
|
|||||||
|
|
||||||
export * from './nonce_utils';
|
export * from './nonce_utils';
|
||||||
export * from './migration';
|
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 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,
|
||||||
|
@ -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(
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -81,6 +81,8 @@ blockchainTests.resets('Full migration', env => {
|
|||||||
'createTransformWallet',
|
'createTransformWallet',
|
||||||
'getTransformWallet',
|
'getTransformWallet',
|
||||||
'setTransformerDeployer',
|
'setTransformerDeployer',
|
||||||
|
'getQuoteSigner',
|
||||||
|
'setQuoteSigner',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
SignatureValidator: {
|
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_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';
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user