Merge pull request #2610 from 0xProject/feat/zero-ex/meta-transactions
EP: MetaTransactions
This commit is contained in:
commit
18bc701e8b
@ -9,6 +9,10 @@
|
||||
{
|
||||
"note": "Export `AffiliateFeeTransformerContract`",
|
||||
"pr": 2622
|
||||
},
|
||||
{
|
||||
"note": "Add `MetaTransactions` and `SignatureValidator` features",
|
||||
"pr": 2610
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -34,11 +34,12 @@ contract ZeroEx {
|
||||
|
||||
/// @dev Construct this contract and register the `Bootstrap` feature.
|
||||
/// After constructing this contract, `bootstrap()` should be called
|
||||
/// to seed the initial feature set.
|
||||
constructor() public {
|
||||
/// by `bootstrap()` to seed the initial feature set.
|
||||
/// @param bootstrapper Who can call `bootstrap()`.
|
||||
constructor(address bootstrapper) public {
|
||||
// Temporarily create and register the bootstrap feature.
|
||||
// It will deregister itself after `bootstrap()` has been called.
|
||||
Bootstrap bootstrap = new Bootstrap(msg.sender);
|
||||
Bootstrap bootstrap = new Bootstrap(bootstrapper);
|
||||
LibProxyStorage.getStorage().impls[bootstrap.bootstrap.selector] =
|
||||
address(bootstrap);
|
||||
}
|
||||
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
|
||||
library LibMetaTransactionsRichErrors {
|
||||
|
||||
// solhint-disable func-name-mixedcase
|
||||
|
||||
function InvalidMetaTransactionsArrayLengthsError(
|
||||
uint256 mtxCount,
|
||||
uint256 signatureCount
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("InvalidMetaTransactionsArrayLengthsError(uint256,uint256)")),
|
||||
mtxCount,
|
||||
signatureCount
|
||||
);
|
||||
}
|
||||
|
||||
function MetaTransactionUnsupportedFunctionError(
|
||||
bytes32 mtxHash,
|
||||
bytes4 selector
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("MetaTransactionUnsupportedFunctionError(bytes32,bytes4)")),
|
||||
mtxHash,
|
||||
selector
|
||||
);
|
||||
}
|
||||
|
||||
function MetaTransactionWrongSenderError(
|
||||
bytes32 mtxHash,
|
||||
address sender,
|
||||
address expectedSender
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("MetaTransactionWrongSenderError(bytes32,address,address)")),
|
||||
mtxHash,
|
||||
sender,
|
||||
expectedSender
|
||||
);
|
||||
}
|
||||
|
||||
function MetaTransactionExpiredError(
|
||||
bytes32 mtxHash,
|
||||
uint256 time,
|
||||
uint256 expirationTime
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("MetaTransactionExpiredError(bytes32,uint256,uint256)")),
|
||||
mtxHash,
|
||||
time,
|
||||
expirationTime
|
||||
);
|
||||
}
|
||||
|
||||
function MetaTransactionGasPriceError(
|
||||
bytes32 mtxHash,
|
||||
uint256 gasPrice,
|
||||
uint256 minGasPrice,
|
||||
uint256 maxGasPrice
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("MetaTransactionGasPriceError(bytes32,uint256,uint256,uint256)")),
|
||||
mtxHash,
|
||||
gasPrice,
|
||||
minGasPrice,
|
||||
maxGasPrice
|
||||
);
|
||||
}
|
||||
|
||||
function MetaTransactionInsufficientEthError(
|
||||
bytes32 mtxHash,
|
||||
uint256 ethBalance,
|
||||
uint256 ethRequired
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("MetaTransactionInsufficientEthError(bytes32,uint256,uint256)")),
|
||||
mtxHash,
|
||||
ethBalance,
|
||||
ethRequired
|
||||
);
|
||||
}
|
||||
|
||||
function MetaTransactionInvalidSignatureError(
|
||||
bytes32 mtxHash,
|
||||
bytes memory signature,
|
||||
bytes memory errData
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("MetaTransactionInvalidSignatureError(bytes32,bytes,bytes)")),
|
||||
mtxHash,
|
||||
signature,
|
||||
errData
|
||||
);
|
||||
}
|
||||
|
||||
function MetaTransactionAlreadyExecutedError(
|
||||
bytes32 mtxHash,
|
||||
uint256 executedBlockNumber
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("MetaTransactionAlreadyExecutedError(bytes32,uint256)")),
|
||||
mtxHash,
|
||||
executedBlockNumber
|
||||
);
|
||||
}
|
||||
|
||||
function MetaTransactionCallFailedError(
|
||||
bytes32 mtxHash,
|
||||
bytes memory callData,
|
||||
bytes memory returnData
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("MetaTransactionCallFailedError(bytes32,bytes,bytes)")),
|
||||
mtxHash,
|
||||
callData,
|
||||
returnData
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
|
||||
library LibSignatureRichErrors {
|
||||
|
||||
enum SignatureValidationErrorCodes {
|
||||
ALWAYS_INVALID,
|
||||
INVALID_LENGTH,
|
||||
UNSUPPORTED,
|
||||
ILLEGAL,
|
||||
WRONG_SIGNER
|
||||
}
|
||||
|
||||
// solhint-disable func-name-mixedcase
|
||||
|
||||
function SignatureValidationError(
|
||||
SignatureValidationErrorCodes code,
|
||||
bytes32 hash,
|
||||
address signerAddress,
|
||||
bytes memory signature
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("SignatureValidationError(uint8,bytes32,address,bytes)")),
|
||||
code,
|
||||
hash,
|
||||
signerAddress,
|
||||
signature
|
||||
);
|
||||
}
|
||||
}
|
127
contracts/zero-ex/contracts/src/features/IMetaTransactions.sol
Normal file
127
contracts/zero-ex/contracts/src/features/IMetaTransactions.sol
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
|
||||
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-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
|
||||
|
||||
/// @dev Meta-transactions feature.
|
||||
interface IMetaTransactions {
|
||||
|
||||
/// @dev Describes an exchange proxy meta transaction.
|
||||
struct MetaTransactionData {
|
||||
// Signer of meta-transaction. On whose behalf to execute the MTX.
|
||||
address signer;
|
||||
// Required sender, or NULL for anyone.
|
||||
address sender;
|
||||
// Minimum gas price.
|
||||
uint256 minGasPrice;
|
||||
// Maximum gas price.
|
||||
uint256 maxGasPrice;
|
||||
// MTX is invalid after this time.
|
||||
uint256 expirationTimeSeconds;
|
||||
// Nonce to make this MTX unique.
|
||||
uint256 salt;
|
||||
// Encoded call data to a function on the exchange proxy.
|
||||
bytes callData;
|
||||
// Amount of ETH to attach to the call.
|
||||
uint256 value;
|
||||
// ERC20 fee `signer` pays `sender`.
|
||||
IERC20TokenV06 feeToken;
|
||||
// ERC20 fee amount.
|
||||
uint256 feeAmount;
|
||||
}
|
||||
|
||||
/// @dev Emitted whenever a meta-transaction is executed via
|
||||
/// `executeMetaTransaction()` or `executeMetaTransactions()`.
|
||||
/// @param hash The meta-transaction hash.
|
||||
/// @param selector The selector of the function being executed.
|
||||
/// @param signer Who to execute the meta-transaction on behalf of.
|
||||
/// @param sender Who executed the meta-transaction.
|
||||
event MetaTransactionExecuted(
|
||||
bytes32 hash,
|
||||
bytes4 indexed selector,
|
||||
address signer,
|
||||
address sender
|
||||
);
|
||||
|
||||
/// @dev Execute a single meta-transaction.
|
||||
/// @param mtx The meta-transaction.
|
||||
/// @param signature The signature by `mtx.signer`.
|
||||
/// @return returnResult The ABI-encoded result of the underlying call.
|
||||
function executeMetaTransaction(
|
||||
MetaTransactionData calldata mtx,
|
||||
bytes calldata signature
|
||||
)
|
||||
external
|
||||
payable
|
||||
returns (bytes memory returnResult);
|
||||
|
||||
/// @dev Execute multiple meta-transactions.
|
||||
/// @param mtxs The meta-transactions.
|
||||
/// @param signatures The signature by each respective `mtx.signer`.
|
||||
/// @return returnResults The ABI-encoded results of the underlying calls.
|
||||
function batchExecuteMetaTransactions(
|
||||
MetaTransactionData[] calldata mtxs,
|
||||
bytes[] calldata signatures
|
||||
)
|
||||
external
|
||||
payable
|
||||
returns (bytes[] memory returnResults);
|
||||
|
||||
/// @dev Execute a meta-transaction via `sender`. Privileged variant.
|
||||
/// Only callable from within.
|
||||
/// @param sender Who is executing the meta-transaction..
|
||||
/// @param mtx The meta-transaction.
|
||||
/// @param signature The signature by `mtx.signer`.
|
||||
/// @return returnResult The ABI-encoded result of the underlying call.
|
||||
function _executeMetaTransaction(
|
||||
address sender,
|
||||
MetaTransactionData calldata mtx,
|
||||
bytes calldata signature
|
||||
)
|
||||
external
|
||||
payable
|
||||
returns (bytes memory returnResult);
|
||||
|
||||
/// @dev Get the block at which a meta-transaction has been executed.
|
||||
/// @param mtx The meta-transaction.
|
||||
/// @return blockNumber The block height when the meta-transactioin was executed.
|
||||
function getMetaTransactionExecutedBlock(MetaTransactionData calldata mtx)
|
||||
external
|
||||
view
|
||||
returns (uint256 blockNumber);
|
||||
|
||||
/// @dev Get the block at which a meta-transaction hash has been executed.
|
||||
/// @param mtxHash The meta-transaction hash.
|
||||
/// @return blockNumber The block height when the meta-transactioin was executed.
|
||||
function getMetaTransactionHashExecutedBlock(bytes32 mtxHash)
|
||||
external
|
||||
view
|
||||
returns (uint256 blockNumber);
|
||||
|
||||
/// @dev Get the EIP712 hash of a meta-transaction.
|
||||
/// @param mtx The meta-transaction.
|
||||
/// @return mtxHash The EIP712 hash of `mtx`.
|
||||
function getMetaTransactionHash(MetaTransactionData calldata mtx)
|
||||
external
|
||||
view
|
||||
returns (bytes32 mtxHash);
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
|
||||
/// @dev Feature for validating signatures.
|
||||
interface ISignatureValidator {
|
||||
|
||||
/// @dev Allowed signature types.
|
||||
enum SignatureType {
|
||||
Illegal, // 0x00, default value
|
||||
Invalid, // 0x01
|
||||
EIP712, // 0x02
|
||||
EthSign, // 0x03
|
||||
NSignatureTypes // 0x04, number of signature types. Always leave at end.
|
||||
}
|
||||
|
||||
/// @dev Validate that `hash` was signed by `signer` given `signature`.
|
||||
/// Reverts otherwise.
|
||||
/// @param hash The hash that was signed.
|
||||
/// @param signer The signer of the hash.
|
||||
/// @param signature The signature. The last byte of this signature should
|
||||
/// be a member of the `SignatureType` enum.
|
||||
function validateHashSignature(
|
||||
bytes32 hash,
|
||||
address signer,
|
||||
bytes calldata signature
|
||||
)
|
||||
external
|
||||
view;
|
||||
|
||||
/// @dev Check that `hash` was signed by `signer` given `signature`.
|
||||
/// @param hash The hash that was signed.
|
||||
/// @param signer The signer of the hash.
|
||||
/// @param signature The signature. The last byte of this signature should
|
||||
/// be a member of the `SignatureType` enum.
|
||||
/// @return isValid `true` on success.
|
||||
function isValidHashSignature(
|
||||
bytes32 hash,
|
||||
address signer,
|
||||
bytes calldata signature
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (bool isValid);
|
||||
}
|
436
contracts/zero-ex/contracts/src/features/MetaTransactions.sol
Normal file
436
contracts/zero-ex/contracts/src/features/MetaTransactions.sol
Normal file
@ -0,0 +1,436 @@
|
||||
/*
|
||||
|
||||
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/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||
import "../errors/LibMetaTransactionsRichErrors.sol";
|
||||
import "../fixins/FixinCommon.sol";
|
||||
import "../fixins/FixinEIP712.sol";
|
||||
import "../migrations/LibMigrate.sol";
|
||||
import "../storage/LibMetaTransactionsStorage.sol";
|
||||
import "./IMetaTransactions.sol";
|
||||
import "./ITransformERC20.sol";
|
||||
import "./ISignatureValidator.sol";
|
||||
import "./ITokenSpender.sol";
|
||||
import "./IFeature.sol";
|
||||
|
||||
|
||||
/// @dev MetaTransactions feature.
|
||||
contract MetaTransactions is
|
||||
IFeature,
|
||||
IMetaTransactions,
|
||||
FixinCommon,
|
||||
FixinEIP712
|
||||
{
|
||||
using LibBytesV06 for bytes;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
/// @dev Intermediate state vars to avoid stack overflows.
|
||||
struct ExecuteState {
|
||||
address sender;
|
||||
bytes32 hash;
|
||||
MetaTransactionData mtx;
|
||||
bytes signature;
|
||||
bytes4 selector;
|
||||
uint256 selfBalance;
|
||||
uint256 executedBlockNumber;
|
||||
}
|
||||
|
||||
struct TransformERC20Args {
|
||||
IERC20TokenV06 inputToken;
|
||||
IERC20TokenV06 outputToken;
|
||||
uint256 inputTokenAmount;
|
||||
uint256 minOutputTokenAmount;
|
||||
ITransformERC20.Transformation[] transformations;
|
||||
}
|
||||
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "MetaTransactions";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||
/// @dev EIP712 typehash of the `MetaTransactionData` struct.
|
||||
bytes32 public immutable MTX_EIP712_TYPEHASH = keccak256(
|
||||
"MetaTransactionData("
|
||||
"address signer,"
|
||||
"address sender,"
|
||||
"uint256 minGasPrice,"
|
||||
"uint256 maxGasPrice,"
|
||||
"uint256 expirationTimeSeconds,"
|
||||
"uint256 salt,"
|
||||
"bytes callData,"
|
||||
"uint256 value,"
|
||||
"address feeToken,"
|
||||
"uint256 feeAmount"
|
||||
")"
|
||||
);
|
||||
|
||||
constructor(address zeroExAddress)
|
||||
public
|
||||
FixinCommon()
|
||||
FixinEIP712(zeroExAddress)
|
||||
{
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initialize and register this feature.
|
||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||
/// @return success `LibMigrate.SUCCESS` on success.
|
||||
function migrate()
|
||||
external
|
||||
returns (bytes4 success)
|
||||
{
|
||||
_registerFeatureFunction(this.executeMetaTransaction.selector);
|
||||
_registerFeatureFunction(this.batchExecuteMetaTransactions.selector);
|
||||
_registerFeatureFunction(this._executeMetaTransaction.selector);
|
||||
_registerFeatureFunction(this.getMetaTransactionExecutedBlock.selector);
|
||||
_registerFeatureFunction(this.getMetaTransactionHashExecutedBlock.selector);
|
||||
_registerFeatureFunction(this.getMetaTransactionHash.selector);
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
}
|
||||
|
||||
/// @dev Execute a single meta-transaction.
|
||||
/// @param mtx The meta-transaction.
|
||||
/// @param signature The signature by `mtx.signer`.
|
||||
/// @return returnResult The ABI-encoded result of the underlying call.
|
||||
function executeMetaTransaction(
|
||||
MetaTransactionData memory mtx,
|
||||
bytes memory signature
|
||||
)
|
||||
public
|
||||
payable
|
||||
override
|
||||
returns (bytes memory returnResult)
|
||||
{
|
||||
return _executeMetaTransactionPrivate(
|
||||
msg.sender,
|
||||
mtx,
|
||||
signature
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Execute multiple meta-transactions.
|
||||
/// @param mtxs The meta-transactions.
|
||||
/// @param signatures The signature by each respective `mtx.signer`.
|
||||
/// @return returnResults The ABI-encoded results of the underlying calls.
|
||||
function batchExecuteMetaTransactions(
|
||||
MetaTransactionData[] memory mtxs,
|
||||
bytes[] memory signatures
|
||||
)
|
||||
public
|
||||
payable
|
||||
override
|
||||
returns (bytes[] memory returnResults)
|
||||
{
|
||||
if (mtxs.length != signatures.length) {
|
||||
LibMetaTransactionsRichErrors.InvalidMetaTransactionsArrayLengthsError(
|
||||
mtxs.length,
|
||||
signatures.length
|
||||
).rrevert();
|
||||
}
|
||||
returnResults = new bytes[](mtxs.length);
|
||||
for (uint256 i = 0; i < mtxs.length; ++i) {
|
||||
returnResults[i] = _executeMetaTransactionPrivate(
|
||||
msg.sender,
|
||||
mtxs[i],
|
||||
signatures[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Execute a meta-transaction via `sender`. Privileged variant.
|
||||
/// Only callable from within.
|
||||
/// @param sender Who is executing the meta-transaction..
|
||||
/// @param mtx The meta-transaction.
|
||||
/// @param signature The signature by `mtx.signer`.
|
||||
/// @return returnResult The ABI-encoded result of the underlying call.
|
||||
function _executeMetaTransaction(
|
||||
address sender,
|
||||
MetaTransactionData memory mtx,
|
||||
bytes memory signature
|
||||
)
|
||||
public
|
||||
payable
|
||||
override
|
||||
onlySelf
|
||||
returns (bytes memory returnResult)
|
||||
{
|
||||
return _executeMetaTransactionPrivate(sender, mtx, signature);
|
||||
}
|
||||
|
||||
/// @dev Get the block at which a meta-transaction has been executed.
|
||||
/// @param mtx The meta-transaction.
|
||||
/// @return blockNumber The block height when the meta-transactioin was executed.
|
||||
function getMetaTransactionExecutedBlock(MetaTransactionData memory mtx)
|
||||
public
|
||||
override
|
||||
view
|
||||
returns (uint256 blockNumber)
|
||||
{
|
||||
return getMetaTransactionHashExecutedBlock(getMetaTransactionHash(mtx));
|
||||
}
|
||||
|
||||
/// @dev Get the block at which a meta-transaction hash has been executed.
|
||||
/// @param mtxHash The meta-transaction hash.
|
||||
/// @return blockNumber The block height when the meta-transactioin was executed.
|
||||
function getMetaTransactionHashExecutedBlock(bytes32 mtxHash)
|
||||
public
|
||||
override
|
||||
view
|
||||
returns (uint256 blockNumber)
|
||||
{
|
||||
return LibMetaTransactionsStorage.getStorage().mtxHashToExecutedBlockNumber[mtxHash];
|
||||
}
|
||||
|
||||
/// @dev Get the EIP712 hash of a meta-transaction.
|
||||
/// @param mtx The meta-transaction.
|
||||
/// @return mtxHash The EIP712 hash of `mtx`.
|
||||
function getMetaTransactionHash(MetaTransactionData memory mtx)
|
||||
public
|
||||
override
|
||||
view
|
||||
returns (bytes32 mtxHash)
|
||||
{
|
||||
return _getEIP712Hash(keccak256(abi.encode(
|
||||
MTX_EIP712_TYPEHASH,
|
||||
mtx.signer,
|
||||
mtx.sender,
|
||||
mtx.minGasPrice,
|
||||
mtx.maxGasPrice,
|
||||
mtx.expirationTimeSeconds,
|
||||
mtx.salt,
|
||||
keccak256(mtx.callData),
|
||||
mtx.value,
|
||||
mtx.feeToken,
|
||||
mtx.feeAmount
|
||||
)));
|
||||
}
|
||||
|
||||
/// @dev Execute a meta-transaction by `sender`. Low-level, hidden variant.
|
||||
/// @param sender Who is executing the meta-transaction..
|
||||
/// @param mtx The meta-transaction.
|
||||
/// @param signature The signature by `mtx.signer`.
|
||||
/// @return returnResult The ABI-encoded result of the underlying call.
|
||||
function _executeMetaTransactionPrivate(
|
||||
address sender,
|
||||
MetaTransactionData memory mtx,
|
||||
bytes memory signature
|
||||
)
|
||||
private
|
||||
returns (bytes memory returnResult)
|
||||
{
|
||||
ExecuteState memory state;
|
||||
state.sender = sender;
|
||||
state.hash = getMetaTransactionHash(mtx);
|
||||
state.mtx = mtx;
|
||||
state.signature = signature;
|
||||
|
||||
_validateMetaTransaction(state);
|
||||
|
||||
// Mark the transaction executed.
|
||||
assert(block.number > 0);
|
||||
LibMetaTransactionsStorage.getStorage()
|
||||
.mtxHashToExecutedBlockNumber[state.hash] = block.number;
|
||||
|
||||
// Execute the call based on the selector.
|
||||
state.selector = mtx.callData.readBytes4(0);
|
||||
if (state.selector == ITransformERC20.transformERC20.selector) {
|
||||
returnResult = _executeTransformERC20Call(state);
|
||||
} else {
|
||||
LibMetaTransactionsRichErrors
|
||||
.MetaTransactionUnsupportedFunctionError(state.hash, state.selector)
|
||||
.rrevert();
|
||||
}
|
||||
// Pay the fee to the sender.
|
||||
if (mtx.feeAmount > 0) {
|
||||
ITokenSpender(address(this))._spendERC20Tokens(
|
||||
mtx.feeToken,
|
||||
mtx.signer, // From the signer.
|
||||
sender, // To the sender.
|
||||
mtx.feeAmount
|
||||
);
|
||||
}
|
||||
emit MetaTransactionExecuted(
|
||||
state.hash,
|
||||
state.selector,
|
||||
mtx.signer,
|
||||
mtx.sender
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Validate that a meta-transaction is executable.
|
||||
function _validateMetaTransaction(ExecuteState memory state)
|
||||
private
|
||||
view
|
||||
{
|
||||
// Must be from the required sender, if set.
|
||||
if (state.mtx.sender != address(0) && state.mtx.sender != state.sender) {
|
||||
LibMetaTransactionsRichErrors
|
||||
.MetaTransactionWrongSenderError(
|
||||
state.hash,
|
||||
state.sender,
|
||||
state.mtx.sender
|
||||
).rrevert();
|
||||
}
|
||||
// Must not be expired.
|
||||
if (state.mtx.expirationTimeSeconds <= block.timestamp) {
|
||||
LibMetaTransactionsRichErrors
|
||||
.MetaTransactionExpiredError(
|
||||
state.hash,
|
||||
block.timestamp,
|
||||
state.mtx.expirationTimeSeconds
|
||||
).rrevert();
|
||||
}
|
||||
// Must have a valid gas price.
|
||||
if (state.mtx.minGasPrice > tx.gasprice || state.mtx.maxGasPrice < tx.gasprice) {
|
||||
LibMetaTransactionsRichErrors
|
||||
.MetaTransactionGasPriceError(
|
||||
state.hash,
|
||||
tx.gasprice,
|
||||
state.mtx.minGasPrice,
|
||||
state.mtx.maxGasPrice
|
||||
).rrevert();
|
||||
}
|
||||
// Must have enough ETH.
|
||||
state.selfBalance = address(this).balance;
|
||||
if (state.mtx.value > state.selfBalance) {
|
||||
LibMetaTransactionsRichErrors
|
||||
.MetaTransactionInsufficientEthError(
|
||||
state.hash,
|
||||
state.selfBalance,
|
||||
state.mtx.value
|
||||
).rrevert();
|
||||
}
|
||||
// Must be signed by signer.
|
||||
try
|
||||
ISignatureValidator(address(this))
|
||||
.validateHashSignature(state.hash, state.mtx.signer, state.signature)
|
||||
{}
|
||||
catch (bytes memory err) {
|
||||
LibMetaTransactionsRichErrors
|
||||
.MetaTransactionInvalidSignatureError(
|
||||
state.hash,
|
||||
state.signature,
|
||||
err
|
||||
).rrevert();
|
||||
}
|
||||
// Transaction must not have been already executed.
|
||||
state.executedBlockNumber = LibMetaTransactionsStorage
|
||||
.getStorage().mtxHashToExecutedBlockNumber[state.hash];
|
||||
if (state.executedBlockNumber != 0) {
|
||||
LibMetaTransactionsRichErrors
|
||||
.MetaTransactionAlreadyExecutedError(
|
||||
state.hash,
|
||||
state.executedBlockNumber
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Execute a `ITransformERC20.transformERC20()` meta-transaction call
|
||||
/// by decoding the call args and translating the call to the internal
|
||||
/// `ITransformERC20._transformERC20()` variant, where we can override
|
||||
/// the taker address.
|
||||
function _executeTransformERC20Call(ExecuteState memory state)
|
||||
private
|
||||
returns (bytes memory returnResult)
|
||||
{
|
||||
// HACK(dorothy-zbornak): `abi.decode()` with the individual args
|
||||
// will cause a stack overflow. But we can prefix the call data with an
|
||||
// offset to transform it into the encoding for the equivalent single struct arg,
|
||||
// since decoding a single struct arg consumes far less stack space than
|
||||
// decoding multiple struct args.
|
||||
|
||||
// Where the encoding for multiple args (with the seleector ommitted)
|
||||
// would typically look like:
|
||||
// | argument | offset |
|
||||
// |--------------------------|---------|
|
||||
// | inputToken | 0 |
|
||||
// | outputToken | 32 |
|
||||
// | inputTokenAmount | 64 |
|
||||
// | minOutputTokenAmount | 96 |
|
||||
// | transformations (offset) | 128 | = 32
|
||||
// | transformations (data) | 160 |
|
||||
|
||||
// We will ABI-decode a single struct arg copy with the layout:
|
||||
// | argument | offset |
|
||||
// |--------------------------|---------|
|
||||
// | (arg 1 offset) | 0 | = 32
|
||||
// | inputToken | 32 |
|
||||
// | outputToken | 64 |
|
||||
// | inputTokenAmount | 96 |
|
||||
// | minOutputTokenAmount | 128 |
|
||||
// | transformations (offset) | 160 | = 32
|
||||
// | transformations (data) | 192 |
|
||||
|
||||
TransformERC20Args memory args;
|
||||
{
|
||||
bytes memory encodedStructArgs = new bytes(state.mtx.callData.length - 4 + 32);
|
||||
// Copy the args data from the original, after the new struct offset prefix.
|
||||
bytes memory fromCallData = state.mtx.callData;
|
||||
assert(fromCallData.length >= 4);
|
||||
uint256 fromMem;
|
||||
uint256 toMem;
|
||||
assembly {
|
||||
// Prefix the original calldata with a struct offset,
|
||||
// which is just one word over.
|
||||
mstore(add(encodedStructArgs, 32), 32)
|
||||
// Copy everything after the selector.
|
||||
fromMem := add(fromCallData, 36)
|
||||
// Start copying after the struct offset.
|
||||
toMem := add(encodedStructArgs, 64)
|
||||
}
|
||||
LibBytesV06.memCopy(toMem, fromMem, fromCallData.length - 4);
|
||||
// Decode call args for `ITransformERC20.transformERC20()` as a struct.
|
||||
args = abi.decode(encodedStructArgs, (TransformERC20Args));
|
||||
}
|
||||
// Call `ITransformERC20._transformERC20()` (internal variant).
|
||||
return _callSelf(
|
||||
state.hash,
|
||||
abi.encodeWithSelector(
|
||||
ITransformERC20._transformERC20.selector,
|
||||
keccak256(state.mtx.callData),
|
||||
state.mtx.signer, // taker is mtx signer
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations
|
||||
),
|
||||
state.mtx.value
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Make an arbitrary internal, meta-transaction call.
|
||||
/// Warning: Do not let unadulerated `callData` into this function.
|
||||
function _callSelf(bytes32 hash, bytes memory callData, uint256 value)
|
||||
private
|
||||
returns (bytes memory returnResult)
|
||||
{
|
||||
bool success;
|
||||
(success, returnResult) = address(this).call{value: value}(callData);
|
||||
if (!success) {
|
||||
LibMetaTransactionsRichErrors.MetaTransactionCallFailedError(
|
||||
hash,
|
||||
callData,
|
||||
returnResult
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
}
|
@ -37,19 +37,15 @@ contract Ownable is
|
||||
FixinCommon
|
||||
{
|
||||
|
||||
// solhint-disable
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "Ownable";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||
/// @dev The deployed address of this contract.
|
||||
address immutable private _implementation;
|
||||
// solhint-enable
|
||||
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
constructor() public {
|
||||
_implementation = address(this);
|
||||
constructor() public FixinCommon() {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initializes this feature. The intial owner will be set to this (ZeroEx)
|
||||
|
260
contracts/zero-ex/contracts/src/features/SignatureValidator.sol
Normal file
260
contracts/zero-ex/contracts/src/features/SignatureValidator.sol
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
|
||||
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/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||
import "../errors/LibSignatureRichErrors.sol";
|
||||
import "../fixins/FixinCommon.sol";
|
||||
import "../migrations/LibMigrate.sol";
|
||||
import "./ISignatureValidator.sol";
|
||||
import "./IFeature.sol";
|
||||
|
||||
|
||||
/// @dev Feature for validating signatures.
|
||||
contract SignatureValidator is
|
||||
IFeature,
|
||||
ISignatureValidator,
|
||||
FixinCommon
|
||||
{
|
||||
using LibBytesV06 for bytes;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "SignatureValidator";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||
|
||||
constructor() public FixinCommon() {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initialize and register this feature.
|
||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||
/// @return success `LibMigrate.SUCCESS` on success.
|
||||
function migrate()
|
||||
external
|
||||
returns (bytes4 success)
|
||||
{
|
||||
_registerFeatureFunction(this.validateHashSignature.selector);
|
||||
_registerFeatureFunction(this.isValidHashSignature.selector);
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
}
|
||||
|
||||
/// @dev Validate that `hash` was signed by `signer` given `signature`.
|
||||
/// Reverts otherwise.
|
||||
/// @param hash The hash that was signed.
|
||||
/// @param signer The signer of the hash.
|
||||
/// @param signature The signature. The last byte of this signature should
|
||||
/// be a member of the `SignatureType` enum.
|
||||
function validateHashSignature(
|
||||
bytes32 hash,
|
||||
address signer,
|
||||
bytes memory signature
|
||||
)
|
||||
public
|
||||
override
|
||||
view
|
||||
{
|
||||
SignatureType signatureType = _readValidSignatureType(
|
||||
hash,
|
||||
signer,
|
||||
signature
|
||||
);
|
||||
|
||||
// TODO: When we support non-hash signature types, assert that
|
||||
// `signatureType` is only `EIP712` or `EthSign` here.
|
||||
|
||||
_validateHashSignatureTypes(
|
||||
signatureType,
|
||||
hash,
|
||||
signer,
|
||||
signature
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Check that `hash` was signed by `signer` given `signature`.
|
||||
/// @param hash The hash that was signed.
|
||||
/// @param signer The signer of the hash.
|
||||
/// @param signature The signature. The last byte of this signature should
|
||||
/// be a member of the `SignatureType` enum.
|
||||
/// @return isValid `true` on success.
|
||||
function isValidHashSignature(
|
||||
bytes32 hash,
|
||||
address signer,
|
||||
bytes calldata signature
|
||||
)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (bool isValid)
|
||||
{
|
||||
try this.validateHashSignature(hash, signer, signature) {
|
||||
isValid = true;
|
||||
} catch (bytes memory) {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Validates a hash-only signature type. Low-level, hidden variant.
|
||||
/// @param signatureType The type of signature to check.
|
||||
/// @param hash The hash that was signed.
|
||||
/// @param signer The signer of the hash.
|
||||
/// @param signature The signature. The last byte of this signature should
|
||||
/// be a member of the `SignatureType` enum.
|
||||
function _validateHashSignatureTypes(
|
||||
SignatureType signatureType,
|
||||
bytes32 hash,
|
||||
address signer,
|
||||
bytes memory signature
|
||||
)
|
||||
private
|
||||
pure
|
||||
{
|
||||
address recovered = address(0);
|
||||
if (signatureType == SignatureType.Invalid) {
|
||||
// Always invalid signature.
|
||||
// Like Illegal, this is always implicitly available and therefore
|
||||
// offered explicitly. It can be implicitly created by providing
|
||||
// a correctly formatted but incorrect signature.
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.ALWAYS_INVALID,
|
||||
hash,
|
||||
signer,
|
||||
signature
|
||||
).rrevert();
|
||||
} else if (signatureType == SignatureType.EIP712) {
|
||||
// Signature using EIP712
|
||||
if (signature.length != 66) {
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.INVALID_LENGTH,
|
||||
hash,
|
||||
signer,
|
||||
signature
|
||||
).rrevert();
|
||||
}
|
||||
uint8 v = uint8(signature[0]);
|
||||
bytes32 r = signature.readBytes32(1);
|
||||
bytes32 s = signature.readBytes32(33);
|
||||
recovered = ecrecover(
|
||||
hash,
|
||||
v,
|
||||
r,
|
||||
s
|
||||
);
|
||||
} else if (signatureType == SignatureType.EthSign) {
|
||||
// Signed using `eth_sign`
|
||||
if (signature.length != 66) {
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.INVALID_LENGTH,
|
||||
hash,
|
||||
signer,
|
||||
signature
|
||||
).rrevert();
|
||||
}
|
||||
uint8 v = uint8(signature[0]);
|
||||
bytes32 r = signature.readBytes32(1);
|
||||
bytes32 s = signature.readBytes32(33);
|
||||
recovered = ecrecover(
|
||||
keccak256(abi.encodePacked(
|
||||
"\x19Ethereum Signed Message:\n32",
|
||||
hash
|
||||
)),
|
||||
v,
|
||||
r,
|
||||
s
|
||||
);
|
||||
} else {
|
||||
// This should never happen.
|
||||
revert('SignatureValidator/ILLEGAL_CODE_PATH');
|
||||
}
|
||||
if (recovered == address(0) || signer != recovered) {
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.WRONG_SIGNER,
|
||||
hash,
|
||||
signer,
|
||||
signature
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Reads the `SignatureType` from the end of a signature and validates it.
|
||||
function _readValidSignatureType(
|
||||
bytes32 hash,
|
||||
address signer,
|
||||
bytes memory signature
|
||||
)
|
||||
private
|
||||
pure
|
||||
returns (SignatureType signatureType)
|
||||
{
|
||||
// Read the signatureType from the signature
|
||||
signatureType = _readSignatureType(
|
||||
hash,
|
||||
signer,
|
||||
signature
|
||||
);
|
||||
|
||||
// Ensure signature is supported
|
||||
if (uint8(signatureType) >= uint8(SignatureType.NSignatureTypes)) {
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.UNSUPPORTED,
|
||||
hash,
|
||||
signer,
|
||||
signature
|
||||
).rrevert();
|
||||
}
|
||||
|
||||
// Always illegal signature.
|
||||
// This is always an implicit option since a signer can create a
|
||||
// signature array with invalid type or length. We may as well make
|
||||
// it an explicit option. This aids testing and analysis. It is
|
||||
// also the initialization value for the enum type.
|
||||
if (signatureType == SignatureType.Illegal) {
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.ILLEGAL,
|
||||
hash,
|
||||
signer,
|
||||
signature
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Reads the `SignatureType` from the end of a signature.
|
||||
function _readSignatureType(
|
||||
bytes32 hash,
|
||||
address signer,
|
||||
bytes memory signature
|
||||
)
|
||||
private
|
||||
pure
|
||||
returns (SignatureType sigType)
|
||||
{
|
||||
if (signature.length == 0) {
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.INVALID_LENGTH,
|
||||
hash,
|
||||
signer,
|
||||
signature
|
||||
).rrevert();
|
||||
}
|
||||
return SignatureType(uint8(signature[signature.length - 1]));
|
||||
}
|
||||
}
|
@ -35,19 +35,15 @@ contract SimpleFunctionRegistry is
|
||||
ISimpleFunctionRegistry,
|
||||
FixinCommon
|
||||
{
|
||||
// solhint-disable
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "SimpleFunctionRegistry";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||
/// @dev The deployed address of this contract.
|
||||
address private immutable _implementation;
|
||||
// solhint-enable
|
||||
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
constructor() public {
|
||||
_implementation = address(this);
|
||||
constructor() public FixinCommon() {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initializes this feature, registering its own functions.
|
||||
|
@ -44,14 +44,12 @@ contract TokenSpender is
|
||||
string public constant override FEATURE_NAME = "TokenSpender";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||
/// @dev The implementation address of this feature.
|
||||
address private immutable _implementation;
|
||||
// solhint-enable
|
||||
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
constructor() public {
|
||||
_implementation = address(this);
|
||||
constructor() public FixinCommon() {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initialize and register this feature. Should be delegatecalled
|
||||
@ -59,14 +57,14 @@ contract TokenSpender is
|
||||
/// @param allowanceTarget An `allowanceTarget` instance, configured to have
|
||||
/// the ZeroeEx contract as an authority.
|
||||
/// @return success `MIGRATE_SUCCESS` on success.
|
||||
function migrate(IAllowanceTarget allowanceTarget) external returns (bytes4 success) {
|
||||
function migrate(IAllowanceTarget allowanceTarget)
|
||||
external
|
||||
returns (bytes4 success)
|
||||
{
|
||||
LibTokenSpenderStorage.getStorage().allowanceTarget = allowanceTarget;
|
||||
ISimpleFunctionRegistry(address(this))
|
||||
.extend(this.getAllowanceTarget.selector, _implementation);
|
||||
ISimpleFunctionRegistry(address(this))
|
||||
.extend(this._spendERC20Tokens.selector, _implementation);
|
||||
ISimpleFunctionRegistry(address(this))
|
||||
.extend(this.getSpendableERC20BalanceOf.selector, _implementation);
|
||||
_registerFeatureFunction(this.getAllowanceTarget.selector);
|
||||
_registerFeatureFunction(this._spendERC20Tokens.selector);
|
||||
_registerFeatureFunction(this.getSpendableERC20BalanceOf.selector);
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -52,39 +52,32 @@ contract TransformERC20 is
|
||||
uint256 takerOutputTokenBalanceAfter;
|
||||
}
|
||||
|
||||
// solhint-disable
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "TransformERC20";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 0);
|
||||
/// @dev The implementation address of this feature.
|
||||
address private immutable _implementation;
|
||||
// solhint-enable
|
||||
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
constructor() public {
|
||||
_implementation = address(this);
|
||||
constructor() public FixinCommon() {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initialize and register this feature.
|
||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||
/// @param transformerDeployer The trusted deployer for transformers.
|
||||
/// @return success `LibMigrate.SUCCESS` on success.
|
||||
function migrate(address transformerDeployer) external returns (bytes4 success) {
|
||||
ISimpleFunctionRegistry(address(this))
|
||||
.extend(this.getTransformerDeployer.selector, _implementation);
|
||||
ISimpleFunctionRegistry(address(this))
|
||||
.extend(this.createTransformWallet.selector, _implementation);
|
||||
ISimpleFunctionRegistry(address(this))
|
||||
.extend(this.getTransformWallet.selector, _implementation);
|
||||
ISimpleFunctionRegistry(address(this))
|
||||
.extend(this.setTransformerDeployer.selector, _implementation);
|
||||
ISimpleFunctionRegistry(address(this))
|
||||
.extend(this.transformERC20.selector, _implementation);
|
||||
ISimpleFunctionRegistry(address(this))
|
||||
.extend(this._transformERC20.selector, _implementation);
|
||||
function migrate(address transformerDeployer)
|
||||
external
|
||||
returns (bytes4 success)
|
||||
{
|
||||
_registerFeatureFunction(this.getTransformerDeployer.selector);
|
||||
_registerFeatureFunction(this.createTransformWallet.selector);
|
||||
_registerFeatureFunction(this.getTransformWallet.selector);
|
||||
_registerFeatureFunction(this.setTransformerDeployer.selector);
|
||||
_registerFeatureFunction(this.transformERC20.selector);
|
||||
_registerFeatureFunction(this._transformERC20.selector);
|
||||
this.createTransformWallet();
|
||||
LibTransformERC20Storage.getStorage().transformerDeployer = transformerDeployer;
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
@ -191,6 +184,7 @@ contract TransformERC20 is
|
||||
Transformation[] memory transformations
|
||||
)
|
||||
public
|
||||
virtual
|
||||
override
|
||||
payable
|
||||
onlySelf
|
||||
|
@ -23,13 +23,17 @@ import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "../errors/LibCommonRichErrors.sol";
|
||||
import "../errors/LibOwnableRichErrors.sol";
|
||||
import "../features/IOwnable.sol";
|
||||
import "../features/ISimpleFunctionRegistry.sol";
|
||||
|
||||
|
||||
/// @dev Common feature utilities.
|
||||
contract FixinCommon {
|
||||
abstract contract FixinCommon {
|
||||
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
/// @dev The implementation address of this feature.
|
||||
address internal immutable _implementation;
|
||||
|
||||
/// @dev The caller must be this contract.
|
||||
modifier onlySelf() virtual {
|
||||
if (msg.sender != address(this)) {
|
||||
@ -52,6 +56,21 @@ contract FixinCommon {
|
||||
_;
|
||||
}
|
||||
|
||||
constructor() internal {
|
||||
// Remember this feature's original address.
|
||||
_implementation = address(this);
|
||||
}
|
||||
|
||||
/// @dev Registers a function implemented by this feature at `_implementation`.
|
||||
/// Can and should only be called within a `migrate()`.
|
||||
/// @param selector The selector of the function whose implementation
|
||||
/// is at `_implementation`.
|
||||
function _registerFeatureFunction(bytes4 selector)
|
||||
internal
|
||||
{
|
||||
ISimpleFunctionRegistry(address(this)).extend(selector, _implementation);
|
||||
}
|
||||
|
||||
/// @dev Encode a feature version as a `uint256`.
|
||||
/// @param major The major version number of the feature.
|
||||
/// @param minor The minor version number of the feature.
|
||||
|
69
contracts/zero-ex/contracts/src/fixins/FixinEIP712.sol
Normal file
69
contracts/zero-ex/contracts/src/fixins/FixinEIP712.sol
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
|
||||
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/errors/LibRichErrorsV06.sol";
|
||||
import "../errors/LibCommonRichErrors.sol";
|
||||
import "../errors/LibOwnableRichErrors.sol";
|
||||
import "../features/IOwnable.sol";
|
||||
|
||||
|
||||
/// @dev EIP712 helpers for features.
|
||||
abstract contract FixinEIP712 {
|
||||
|
||||
/// @dev The domain hash separator for the entire exchange proxy.
|
||||
bytes32 public immutable EIP712_DOMAIN_SEPARATOR;
|
||||
|
||||
constructor(address zeroExAddress) internal {
|
||||
// Compute `EIP712_DOMAIN_SEPARATOR`
|
||||
{
|
||||
uint256 chainId;
|
||||
assembly { chainId := chainid() }
|
||||
EIP712_DOMAIN_SEPARATOR = keccak256(
|
||||
abi.encode(
|
||||
keccak256(
|
||||
"EIP712Domain("
|
||||
"string name,"
|
||||
"string version,"
|
||||
"uint256 chainId,"
|
||||
"address verifyingContract"
|
||||
")"
|
||||
),
|
||||
keccak256("ZeroEx"),
|
||||
keccak256("1.0.0"),
|
||||
chainId,
|
||||
zeroExAddress
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function _getEIP712Hash(bytes32 structHash)
|
||||
internal
|
||||
view
|
||||
returns (bytes32 eip712Hash)
|
||||
{
|
||||
return keccak256(abi.encodePacked(
|
||||
hex"1901",
|
||||
EIP712_DOMAIN_SEPARATOR,
|
||||
structHash
|
||||
));
|
||||
}
|
||||
}
|
@ -23,6 +23,8 @@ import "../ZeroEx.sol";
|
||||
import "../features/IOwnable.sol";
|
||||
import "../features/TokenSpender.sol";
|
||||
import "../features/TransformERC20.sol";
|
||||
import "../features/SignatureValidator.sol";
|
||||
import "../features/MetaTransactions.sol";
|
||||
import "../external/AllowanceTarget.sol";
|
||||
import "./InitialMigration.sol";
|
||||
|
||||
@ -38,6 +40,8 @@ contract FullMigration {
|
||||
Ownable ownable;
|
||||
TokenSpender tokenSpender;
|
||||
TransformERC20 transformERC20;
|
||||
SignatureValidator signatureValidator;
|
||||
MetaTransactions metaTransactions;
|
||||
}
|
||||
|
||||
/// @dev Parameters needed to initialize features.
|
||||
@ -45,42 +49,56 @@ contract FullMigration {
|
||||
address transformerDeployer;
|
||||
}
|
||||
|
||||
/// @dev The allowed caller of `deploy()`.
|
||||
address public immutable deployer;
|
||||
/// @dev The allowed caller of `initializeZeroEx()`.
|
||||
address public immutable initializeCaller;
|
||||
/// @dev The initial migration contract.
|
||||
InitialMigration private _initialMigration;
|
||||
|
||||
/// @dev Instantiate this contract and set the allowed caller of `deploy()`
|
||||
/// to `deployer`.
|
||||
/// @param deployer_ The allowed caller of `deploy()`.
|
||||
constructor(address payable deployer_)
|
||||
/// @dev Instantiate this contract and set the allowed caller of `initializeZeroEx()`
|
||||
/// to `initializeCaller`.
|
||||
/// @param initializeCaller_ The allowed caller of `initializeZeroEx()`.
|
||||
constructor(address payable initializeCaller_)
|
||||
public
|
||||
{
|
||||
deployer = deployer_;
|
||||
initializeCaller = initializeCaller_;
|
||||
// Create an initial migration contract with this contract set to the
|
||||
// allowed deployer.
|
||||
// allowed `initializeCaller`.
|
||||
_initialMigration = new InitialMigration(address(this));
|
||||
}
|
||||
|
||||
/// @dev Deploy the `ZeroEx` contract with the full feature set,
|
||||
/// @dev Retrieve the bootstrapper address to use when constructing `ZeroEx`.
|
||||
/// @return bootstrapper The bootstrapper address.
|
||||
function getBootstrapper()
|
||||
external
|
||||
view
|
||||
returns (address bootstrapper)
|
||||
{
|
||||
return address(_initialMigration);
|
||||
}
|
||||
|
||||
/// @dev Initialize the `ZeroEx` contract with the full feature set,
|
||||
/// transfer ownership to `owner`, then self-destruct.
|
||||
/// @param owner The owner of the contract.
|
||||
/// @param zeroEx The instance of the ZeroEx contract. ZeroEx should
|
||||
/// been constructed with this contract as the bootstrapper.
|
||||
/// @param features Features to add to the proxy.
|
||||
/// @return zeroEx The deployed and configured `ZeroEx` contract.
|
||||
/// @return _zeroEx The configured ZeroEx contract. Same as the `zeroEx` parameter.
|
||||
/// @param migrateOpts Parameters needed to initialize features.
|
||||
function deploy(
|
||||
function initializeZeroEx(
|
||||
address payable owner,
|
||||
ZeroEx zeroEx,
|
||||
Features memory features,
|
||||
MigrateOpts memory migrateOpts
|
||||
)
|
||||
public
|
||||
returns (ZeroEx zeroEx)
|
||||
returns (ZeroEx _zeroEx)
|
||||
{
|
||||
require(msg.sender == deployer, "FullMigration/INVALID_SENDER");
|
||||
require(msg.sender == initializeCaller, "FullMigration/INVALID_SENDER");
|
||||
|
||||
// Perform the initial migration with the owner set to this contract.
|
||||
zeroEx = _initialMigration.deploy(
|
||||
_initialMigration.initializeZeroEx(
|
||||
address(uint160(address(this))),
|
||||
zeroEx,
|
||||
InitialMigration.BootstrapFeatures({
|
||||
registry: features.registry,
|
||||
ownable: features.ownable
|
||||
@ -95,9 +113,11 @@ contract FullMigration {
|
||||
|
||||
// Self-destruct.
|
||||
this.die(owner);
|
||||
|
||||
return zeroEx;
|
||||
}
|
||||
|
||||
/// @dev Destroy this contract. Only callable from ourselves (from `deploy()`).
|
||||
/// @dev Destroy this contract. Only callable from ourselves (from `initializeZeroEx()`).
|
||||
/// @param ethRecipient Receiver of any ETH in this contract.
|
||||
function die(address payable ethRecipient)
|
||||
external
|
||||
@ -153,5 +173,27 @@ contract FullMigration {
|
||||
address(this)
|
||||
);
|
||||
}
|
||||
// SignatureValidator
|
||||
{
|
||||
// Register the feature.
|
||||
ownable.migrate(
|
||||
address(features.signatureValidator),
|
||||
abi.encodeWithSelector(
|
||||
SignatureValidator.migrate.selector
|
||||
),
|
||||
address(this)
|
||||
);
|
||||
}
|
||||
// MetaTransactions
|
||||
{
|
||||
// Register the feature.
|
||||
ownable.migrate(
|
||||
address(features.metaTransactions),
|
||||
abi.encodeWithSelector(
|
||||
MetaTransactions.migrate.selector
|
||||
),
|
||||
address(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,36 +35,39 @@ contract InitialMigration {
|
||||
Ownable ownable;
|
||||
}
|
||||
|
||||
/// @dev The allowed caller of `deploy()`. In production, this would be
|
||||
/// @dev The allowed caller of `initializeZeroEx()`. In production, this would be
|
||||
/// the governor.
|
||||
address public immutable deployer;
|
||||
address public immutable initializeCaller;
|
||||
/// @dev The real address of this contract.
|
||||
address private immutable _implementation;
|
||||
|
||||
/// @dev Instantiate this contract and set the allowed caller of `deploy()`
|
||||
/// to `deployer_`.
|
||||
/// @param deployer_ The allowed caller of `deploy()`.
|
||||
constructor(address deployer_) public {
|
||||
deployer = deployer_;
|
||||
/// @dev Instantiate this contract and set the allowed caller of `initializeZeroEx()`
|
||||
/// to `initializeCaller_`.
|
||||
/// @param initializeCaller_ The allowed caller of `initializeZeroEx()`.
|
||||
constructor(address initializeCaller_) public {
|
||||
initializeCaller = initializeCaller_;
|
||||
_implementation = address(this);
|
||||
}
|
||||
|
||||
/// @dev Deploy the `ZeroEx` contract with the minimum feature set,
|
||||
/// @dev Initialize the `ZeroEx` contract with the minimum feature set,
|
||||
/// transfers ownership to `owner`, then self-destructs.
|
||||
/// Only callable by `deployer` set in the contstructor.
|
||||
/// Only callable by `initializeCaller` set in the contstructor.
|
||||
/// @param owner The owner of the contract.
|
||||
/// @param zeroEx The instance of the ZeroEx contract. ZeroEx should
|
||||
/// been constructed with this contract as the bootstrapper.
|
||||
/// @param features Features to bootstrap into the proxy.
|
||||
/// @return zeroEx The deployed and configured `ZeroEx` contract.
|
||||
function deploy(address payable owner, BootstrapFeatures memory features)
|
||||
/// @return _zeroEx The configured ZeroEx contract. Same as the `zeroEx` parameter.
|
||||
function initializeZeroEx(
|
||||
address payable owner,
|
||||
ZeroEx zeroEx,
|
||||
BootstrapFeatures memory features
|
||||
)
|
||||
public
|
||||
virtual
|
||||
returns (ZeroEx zeroEx)
|
||||
returns (ZeroEx _zeroEx)
|
||||
{
|
||||
// Must be called by the allowed deployer.
|
||||
require(msg.sender == deployer, "InitialMigration/INVALID_SENDER");
|
||||
|
||||
// Deploy the ZeroEx contract, setting ourselves as the bootstrapper.
|
||||
zeroEx = new ZeroEx();
|
||||
// Must be called by the allowed initializeCaller.
|
||||
require(msg.sender == initializeCaller, "InitialMigration/INVALID_SENDER");
|
||||
|
||||
// Bootstrap the initial feature set.
|
||||
IBootstrap(address(zeroEx)).bootstrap(
|
||||
@ -75,6 +78,8 @@ contract InitialMigration {
|
||||
// Self-destruct. This contract should not hold any funds but we send
|
||||
// them to the owner just in case.
|
||||
this.die(owner);
|
||||
|
||||
return zeroEx;
|
||||
}
|
||||
|
||||
/// @dev Sets up the initial state of the `ZeroEx` contract.
|
||||
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
|
||||
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 "./LibStorage.sol";
|
||||
|
||||
|
||||
/// @dev Storage helpers for the `MetaTransactions` feature.
|
||||
library LibMetaTransactionsStorage {
|
||||
|
||||
/// @dev Storage bucket for this feature.
|
||||
struct Storage {
|
||||
// The block number when a hash was executed.
|
||||
mapping (bytes32 => uint256) mtxHashToExecutedBlockNumber;
|
||||
}
|
||||
|
||||
/// @dev Get the storage bucket for this contract.
|
||||
function getStorage() internal pure returns (Storage storage stor) {
|
||||
uint256 storageSlot = LibStorage.getStorageSlot(
|
||||
LibStorage.StorageId.MetaTransactions
|
||||
);
|
||||
// Dip into assembly to change the slot pointed to by the local
|
||||
// variable `stor`.
|
||||
// See https://solidity.readthedocs.io/en/v0.6.8/assembly.html?highlight=slot#access-to-external-variables-functions-and-libraries
|
||||
assembly { stor_slot := storageSlot }
|
||||
}
|
||||
}
|
@ -34,7 +34,8 @@ library LibStorage {
|
||||
SimpleFunctionRegistry,
|
||||
Ownable,
|
||||
TokenSpender,
|
||||
TransformERC20
|
||||
TransformERC20,
|
||||
MetaTransactions
|
||||
}
|
||||
|
||||
/// @dev Get the storage slot given a storage ID. We assign unique, well-spaced
|
||||
|
@ -23,7 +23,7 @@ import "./LibStorage.sol";
|
||||
import "../external/IFlashWallet.sol";
|
||||
|
||||
|
||||
/// @dev Storage helpers for the `TokenSpender` feature.
|
||||
/// @dev Storage helpers for the `TransformERC20` feature.
|
||||
library LibTransformERC20Storage {
|
||||
|
||||
/// @dev Storage bucket for this feature.
|
||||
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
|
||||
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 "../src/features/TransformERC20.sol";
|
||||
|
||||
|
||||
contract TestMetaTransactionsTransformERC20Feature is
|
||||
TransformERC20
|
||||
{
|
||||
event TransformERC20Called(
|
||||
address sender,
|
||||
uint256 value,
|
||||
bytes32 callDataHash,
|
||||
address taker,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
uint256 inputTokenAmount,
|
||||
uint256 minOutputTokenAmount,
|
||||
Transformation[] transformations
|
||||
);
|
||||
|
||||
function _transformERC20(
|
||||
bytes32 callDataHash,
|
||||
address payable taker,
|
||||
IERC20TokenV06 inputToken,
|
||||
IERC20TokenV06 outputToken,
|
||||
uint256 inputTokenAmount,
|
||||
uint256 minOutputTokenAmount,
|
||||
Transformation[] memory transformations
|
||||
)
|
||||
public
|
||||
override
|
||||
payable
|
||||
returns (uint256 outputTokenAmount)
|
||||
{
|
||||
if (msg.value == 666) {
|
||||
revert('FAIL');
|
||||
}
|
||||
|
||||
emit TransformERC20Called(
|
||||
msg.sender,
|
||||
msg.value,
|
||||
callDataHash,
|
||||
taker,
|
||||
inputToken,
|
||||
outputToken,
|
||||
inputTokenAmount,
|
||||
minOutputTokenAmount,
|
||||
transformations
|
||||
);
|
||||
return 1337;
|
||||
}
|
||||
}
|
@ -39,9 +39,9 @@
|
||||
"publish:private": "yarn build && gitpkg publish"
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer,Ownable,SimpleFunctionRegistry,TransformERC20,TokenSpender,AffiliateFeeTransformer",
|
||||
"publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer,Ownable,SimpleFunctionRegistry,TransformERC20,TokenSpender,AffiliateFeeTransformer,SignatureValidator,MetaTransactions",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FixinGasToken|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|PayTakerTransformer|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinGasToken|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactions|IOwnable|ISignatureValidator|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|MetaTransactions|Ownable|PayTakerTransformer|SignatureValidator|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -16,8 +16,10 @@ import * as IOwnable from '../generated-artifacts/IOwnable.json';
|
||||
import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunctionRegistry.json';
|
||||
import * as ITokenSpender from '../generated-artifacts/ITokenSpender.json';
|
||||
import * as ITransformERC20 from '../generated-artifacts/ITransformERC20.json';
|
||||
import * as MetaTransactions from '../generated-artifacts/MetaTransactions.json';
|
||||
import * as Ownable from '../generated-artifacts/Ownable.json';
|
||||
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
|
||||
import * as SignatureValidator from '../generated-artifacts/SignatureValidator.json';
|
||||
import * as SimpleFunctionRegistry from '../generated-artifacts/SimpleFunctionRegistry.json';
|
||||
import * as TokenSpender from '../generated-artifacts/TokenSpender.json';
|
||||
import * as TransformERC20 from '../generated-artifacts/TransformERC20.json';
|
||||
@ -42,4 +44,6 @@ export const artifacts = {
|
||||
TransformERC20: TransformERC20 as ContractArtifact,
|
||||
TokenSpender: TokenSpender as ContractArtifact,
|
||||
AffiliateFeeTransformer: AffiliateFeeTransformer as ContractArtifact,
|
||||
SignatureValidator: SignatureValidator as ContractArtifact,
|
||||
MetaTransactions: MetaTransactions as ContractArtifact,
|
||||
};
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { BaseContract } from '@0x/base-contract';
|
||||
import { SupportedProvider } from '@0x/subproviders';
|
||||
import { TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
@ -7,7 +6,9 @@ import { artifacts } from './artifacts';
|
||||
import {
|
||||
FullMigrationContract,
|
||||
InitialMigrationContract,
|
||||
MetaTransactionsContract,
|
||||
OwnableContract,
|
||||
SignatureValidatorContract,
|
||||
SimpleFunctionRegistryContract,
|
||||
TokenSpenderContract,
|
||||
TransformERC20Contract,
|
||||
@ -16,11 +17,17 @@ import {
|
||||
|
||||
// tslint:disable: completed-docs
|
||||
|
||||
/**
|
||||
* Addresses of minimum features for a deployment of the Exchange Proxy.
|
||||
*/
|
||||
export interface BootstrapFeatures {
|
||||
registry: SimpleFunctionRegistryContract;
|
||||
ownable: OwnableContract;
|
||||
registry: string;
|
||||
ownable: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy the minimum features of the Exchange Proxy.
|
||||
*/
|
||||
export async function deployBootstrapFeaturesAsync(
|
||||
provider: SupportedProvider,
|
||||
txDefaults: Partial<TxData>,
|
||||
@ -34,20 +41,23 @@ export async function deployBootstrapFeaturesAsync(
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
)),
|
||||
)).address,
|
||||
ownable:
|
||||
features.ownable ||
|
||||
(await OwnableContract.deployFrom0xArtifactAsync(artifacts.Ownable, provider, txDefaults, artifacts)),
|
||||
(await OwnableContract.deployFrom0xArtifactAsync(artifacts.Ownable, provider, txDefaults, artifacts))
|
||||
.address,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate an instance of the Exchange proxy with minimum viable features.
|
||||
*/
|
||||
export async function initialMigrateAsync(
|
||||
owner: string,
|
||||
provider: SupportedProvider,
|
||||
txDefaults: Partial<TxData>,
|
||||
features: Partial<BootstrapFeatures> = {},
|
||||
): Promise<ZeroExContract> {
|
||||
const _features = await deployBootstrapFeaturesAsync(provider, txDefaults, features);
|
||||
const migrator = await InitialMigrationContract.deployFrom0xArtifactAsync(
|
||||
artifacts.InitialMigration,
|
||||
provider,
|
||||
@ -55,24 +65,42 @@ export async function initialMigrateAsync(
|
||||
artifacts,
|
||||
txDefaults.from as string,
|
||||
);
|
||||
const deployCall = migrator.deploy(owner, toFeatureAdddresses(_features));
|
||||
const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {});
|
||||
await deployCall.awaitTransactionSuccessAsync();
|
||||
const zeroEx = await ZeroExContract.deployFrom0xArtifactAsync(
|
||||
artifacts.ZeroEx,
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
migrator.address,
|
||||
);
|
||||
const _features = await deployBootstrapFeaturesAsync(provider, txDefaults, features);
|
||||
await migrator.initializeZeroEx(owner, zeroEx.address, _features).awaitTransactionSuccessAsync();
|
||||
return zeroEx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Addresses of features for a full deployment of the Exchange Proxy.
|
||||
*/
|
||||
export interface FullFeatures extends BootstrapFeatures {
|
||||
tokenSpender: TokenSpenderContract;
|
||||
transformERC20: TransformERC20Contract;
|
||||
tokenSpender: string;
|
||||
transformERC20: string;
|
||||
signatureValidator: string;
|
||||
metaTransactions: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra configuration options for a full migration of the Exchange Proxy.
|
||||
*/
|
||||
export interface FullMigrationOpts {
|
||||
transformerDeployer: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy all the features for a full Exchange Proxy.
|
||||
*/
|
||||
export async function deployFullFeaturesAsync(
|
||||
provider: SupportedProvider,
|
||||
txDefaults: Partial<TxData>,
|
||||
zeroExAddress: string,
|
||||
features: Partial<FullFeatures> = {},
|
||||
): Promise<FullFeatures> {
|
||||
return {
|
||||
@ -84,7 +112,7 @@ export async function deployFullFeaturesAsync(
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
)),
|
||||
)).address,
|
||||
transformERC20:
|
||||
features.transformERC20 ||
|
||||
(await TransformERC20Contract.deployFrom0xArtifactAsync(
|
||||
@ -92,10 +120,30 @@ export async function deployFullFeaturesAsync(
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
)),
|
||||
)).address,
|
||||
signatureValidator:
|
||||
features.signatureValidator ||
|
||||
(await SignatureValidatorContract.deployFrom0xArtifactAsync(
|
||||
artifacts.SignatureValidator,
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
)).address,
|
||||
metaTransactions:
|
||||
features.metaTransactions ||
|
||||
(await MetaTransactionsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.MetaTransactions,
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
zeroExAddress,
|
||||
)).address,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy a fully featured instance of the Exchange Proxy.
|
||||
*/
|
||||
export async function fullMigrateAsync(
|
||||
owner: string,
|
||||
provider: SupportedProvider,
|
||||
@ -103,7 +151,6 @@ export async function fullMigrateAsync(
|
||||
features: Partial<FullFeatures> = {},
|
||||
opts: Partial<FullMigrationOpts> = {},
|
||||
): Promise<ZeroExContract> {
|
||||
const _features = await deployFullFeaturesAsync(provider, txDefaults, features);
|
||||
const migrator = await FullMigrationContract.deployFrom0xArtifactAsync(
|
||||
artifacts.FullMigration,
|
||||
provider,
|
||||
@ -111,20 +158,18 @@ export async function fullMigrateAsync(
|
||||
artifacts,
|
||||
txDefaults.from as string,
|
||||
);
|
||||
const zeroEx = await ZeroExContract.deployFrom0xArtifactAsync(
|
||||
artifacts.ZeroEx,
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
await migrator.getBootstrapper().callAsync(),
|
||||
);
|
||||
const _features = await deployFullFeaturesAsync(provider, txDefaults, zeroEx.address, features);
|
||||
const _opts = {
|
||||
transformerDeployer: txDefaults.from as string,
|
||||
...opts,
|
||||
};
|
||||
const deployCall = migrator.deploy(owner, toFeatureAdddresses(_features), _opts);
|
||||
const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {});
|
||||
await deployCall.awaitTransactionSuccessAsync();
|
||||
await migrator.initializeZeroEx(owner, zeroEx.address, _features, _opts).awaitTransactionSuccessAsync();
|
||||
return zeroEx;
|
||||
}
|
||||
|
||||
// tslint:disable:space-before-function-parent one-line
|
||||
export function toFeatureAdddresses<T extends BootstrapFeatures | FullFeatures | (BootstrapFeatures & FullFeatures)>(
|
||||
features: T,
|
||||
): { [name in keyof T]: string } {
|
||||
// TS can't figure this out.
|
||||
return _.mapValues(features, (c: BaseContract) => c.address) as any;
|
||||
}
|
||||
|
@ -14,8 +14,10 @@ export * from '../generated-wrappers/i_simple_function_registry';
|
||||
export * from '../generated-wrappers/i_token_spender';
|
||||
export * from '../generated-wrappers/i_transform_erc20';
|
||||
export * from '../generated-wrappers/initial_migration';
|
||||
export * from '../generated-wrappers/meta_transactions';
|
||||
export * from '../generated-wrappers/ownable';
|
||||
export * from '../generated-wrappers/pay_taker_transformer';
|
||||
export * from '../generated-wrappers/signature_validator';
|
||||
export * from '../generated-wrappers/simple_function_registry';
|
||||
export * from '../generated-wrappers/token_spender';
|
||||
export * from '../generated-wrappers/transform_erc20';
|
||||
|
@ -10,6 +10,7 @@ import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.js
|
||||
import * as Bootstrap from '../test/generated-artifacts/Bootstrap.json';
|
||||
import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json';
|
||||
import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json';
|
||||
import * as FixinEIP712 from '../test/generated-artifacts/FixinEIP712.json';
|
||||
import * as FixinGasToken from '../test/generated-artifacts/FixinGasToken.json';
|
||||
import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json';
|
||||
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
|
||||
@ -21,8 +22,10 @@ import * as IExchange from '../test/generated-artifacts/IExchange.json';
|
||||
import * as IFeature from '../test/generated-artifacts/IFeature.json';
|
||||
import * as IFlashWallet from '../test/generated-artifacts/IFlashWallet.json';
|
||||
import * as IGasToken from '../test/generated-artifacts/IGasToken.json';
|
||||
import * as IMetaTransactions from '../test/generated-artifacts/IMetaTransactions.json';
|
||||
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
|
||||
import * as IOwnable from '../test/generated-artifacts/IOwnable.json';
|
||||
import * as ISignatureValidator from '../test/generated-artifacts/ISignatureValidator.json';
|
||||
import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json';
|
||||
import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json';
|
||||
import * as ITokenSpender from '../test/generated-artifacts/ITokenSpender.json';
|
||||
@ -30,11 +33,14 @@ import * as ITransformERC20 from '../test/generated-artifacts/ITransformERC20.js
|
||||
import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json';
|
||||
import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json';
|
||||
import * as LibERC20Transformer from '../test/generated-artifacts/LibERC20Transformer.json';
|
||||
import * as LibMetaTransactionsRichErrors from '../test/generated-artifacts/LibMetaTransactionsRichErrors.json';
|
||||
import * as LibMetaTransactionsStorage from '../test/generated-artifacts/LibMetaTransactionsStorage.json';
|
||||
import * as LibMigrate from '../test/generated-artifacts/LibMigrate.json';
|
||||
import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRichErrors.json';
|
||||
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json';
|
||||
import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
|
||||
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
|
||||
import * as LibSignatureRichErrors from '../test/generated-artifacts/LibSignatureRichErrors.json';
|
||||
import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
|
||||
import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json';
|
||||
import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json';
|
||||
@ -43,8 +49,10 @@ import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpe
|
||||
import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json';
|
||||
import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json';
|
||||
import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json';
|
||||
import * as MetaTransactions from '../test/generated-artifacts/MetaTransactions.json';
|
||||
import * as Ownable from '../test/generated-artifacts/Ownable.json';
|
||||
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
|
||||
import * as SignatureValidator from '../test/generated-artifacts/SignatureValidator.json';
|
||||
import * as SimpleFunctionRegistry from '../test/generated-artifacts/SimpleFunctionRegistry.json';
|
||||
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
|
||||
import * as TestDelegateCaller from '../test/generated-artifacts/TestDelegateCaller.json';
|
||||
@ -53,6 +61,7 @@ import * as TestFillQuoteTransformerExchange from '../test/generated-artifacts/T
|
||||
import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.json';
|
||||
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
|
||||
import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json';
|
||||
import * as TestMetaTransactionsTransformERC20Feature from '../test/generated-artifacts/TestMetaTransactionsTransformERC20Feature.json';
|
||||
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
|
||||
import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json';
|
||||
import * as TestMintTokenERC20Transformer from '../test/generated-artifacts/TestMintTokenERC20Transformer.json';
|
||||
@ -76,8 +85,10 @@ import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
|
||||
export const artifacts = {
|
||||
ZeroEx: ZeroEx as ContractArtifact,
|
||||
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
|
||||
LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact,
|
||||
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
|
||||
LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
|
||||
LibSignatureRichErrors: LibSignatureRichErrors as ContractArtifact,
|
||||
LibSimpleFunctionRegistryRichErrors: LibSimpleFunctionRegistryRichErrors as ContractArtifact,
|
||||
LibSpenderRichErrors: LibSpenderRichErrors as ContractArtifact,
|
||||
LibTransformERC20RichErrors: LibTransformERC20RichErrors as ContractArtifact,
|
||||
@ -90,20 +101,26 @@ export const artifacts = {
|
||||
Bootstrap: Bootstrap as ContractArtifact,
|
||||
IBootstrap: IBootstrap as ContractArtifact,
|
||||
IFeature: IFeature as ContractArtifact,
|
||||
IMetaTransactions: IMetaTransactions as ContractArtifact,
|
||||
IOwnable: IOwnable as ContractArtifact,
|
||||
ISignatureValidator: ISignatureValidator as ContractArtifact,
|
||||
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
|
||||
ITokenSpender: ITokenSpender as ContractArtifact,
|
||||
ITransformERC20: ITransformERC20 as ContractArtifact,
|
||||
MetaTransactions: MetaTransactions as ContractArtifact,
|
||||
Ownable: Ownable as ContractArtifact,
|
||||
SignatureValidator: SignatureValidator as ContractArtifact,
|
||||
SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact,
|
||||
TokenSpender: TokenSpender as ContractArtifact,
|
||||
TransformERC20: TransformERC20 as ContractArtifact,
|
||||
FixinCommon: FixinCommon as ContractArtifact,
|
||||
FixinEIP712: FixinEIP712 as ContractArtifact,
|
||||
FixinGasToken: FixinGasToken as ContractArtifact,
|
||||
FullMigration: FullMigration as ContractArtifact,
|
||||
InitialMigration: InitialMigration as ContractArtifact,
|
||||
LibBootstrap: LibBootstrap as ContractArtifact,
|
||||
LibMigrate: LibMigrate as ContractArtifact,
|
||||
LibMetaTransactionsStorage: LibMetaTransactionsStorage as ContractArtifact,
|
||||
LibOwnableStorage: LibOwnableStorage as ContractArtifact,
|
||||
LibProxyStorage: LibProxyStorage as ContractArtifact,
|
||||
LibSimpleFunctionRegistryStorage: LibSimpleFunctionRegistryStorage as ContractArtifact,
|
||||
@ -128,6 +145,7 @@ export const artifacts = {
|
||||
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
|
||||
TestFullMigration: TestFullMigration as ContractArtifact,
|
||||
TestInitialMigration: TestInitialMigration as ContractArtifact,
|
||||
TestMetaTransactionsTransformERC20Feature: TestMetaTransactionsTransformERC20Feature as ContractArtifact,
|
||||
TestMigrator: TestMigrator as ContractArtifact,
|
||||
TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact,
|
||||
TestMintableERC20Token: TestMintableERC20Token as ContractArtifact,
|
||||
|
548
contracts/zero-ex/test/features/meta_transactions_test.ts
Normal file
548
contracts/zero-ex/test/features/meta_transactions_test.ts
Normal file
@ -0,0 +1,548 @@
|
||||
import {
|
||||
blockchainTests,
|
||||
constants,
|
||||
expect,
|
||||
getRandomInteger,
|
||||
randomAddress,
|
||||
verifyEventsFromLogs,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { getExchangeProxyMetaTransactionHash, signatureUtils } from '@0x/order-utils';
|
||||
import { ExchangeProxyMetaTransaction } from '@0x/types';
|
||||
import { BigNumber, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { MetaTransactionsContract, ZeroExContract } from '../../src/wrappers';
|
||||
import { artifacts } from '../artifacts';
|
||||
import { abis } from '../utils/abis';
|
||||
import { fullMigrateAsync } from '../utils/migration';
|
||||
import {
|
||||
ITokenSpenderContract,
|
||||
TestMetaTransactionsTransformERC20FeatureContract,
|
||||
TestMetaTransactionsTransformERC20FeatureEvents,
|
||||
TestMintableERC20TokenContract,
|
||||
} from '../wrappers';
|
||||
|
||||
const { NULL_ADDRESS, ZERO_AMOUNT } = constants;
|
||||
|
||||
blockchainTests.resets('MetaTransactions feature', env => {
|
||||
let owner: string;
|
||||
let sender: string;
|
||||
let signers: string[];
|
||||
let zeroEx: ZeroExContract;
|
||||
let feature: MetaTransactionsContract;
|
||||
let feeToken: TestMintableERC20TokenContract;
|
||||
let transformERC20Feature: TestMetaTransactionsTransformERC20FeatureContract;
|
||||
let allowanceTarget: string;
|
||||
|
||||
const MAX_FEE_AMOUNT = new BigNumber('1e18');
|
||||
|
||||
before(async () => {
|
||||
[owner, sender, ...signers] = await env.getAccountAddressesAsync();
|
||||
transformERC20Feature = await TestMetaTransactionsTransformERC20FeatureContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMetaTransactionsTransformERC20Feature,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
{},
|
||||
);
|
||||
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {
|
||||
transformERC20: transformERC20Feature.address,
|
||||
});
|
||||
feature = new MetaTransactionsContract(zeroEx.address, env.provider, { ...env.txDefaults, from: sender }, abis);
|
||||
feeToken = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMintableERC20Token,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
{},
|
||||
);
|
||||
allowanceTarget = await new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults)
|
||||
.getAllowanceTarget()
|
||||
.callAsync();
|
||||
// Fund signers with fee tokens.
|
||||
await Promise.all(
|
||||
signers.map(async signer => {
|
||||
await feeToken.mint(signer, MAX_FEE_AMOUNT).awaitTransactionSuccessAsync();
|
||||
await feeToken.approve(allowanceTarget, MAX_FEE_AMOUNT).awaitTransactionSuccessAsync({ from: signer });
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
function getRandomMetaTransaction(
|
||||
fields: Partial<ExchangeProxyMetaTransaction> = {},
|
||||
): ExchangeProxyMetaTransaction {
|
||||
return {
|
||||
signer: _.sampleSize(signers)[0],
|
||||
sender,
|
||||
minGasPrice: getRandomInteger('2', '1e9'),
|
||||
maxGasPrice: getRandomInteger('1e9', '100e9'),
|
||||
expirationTimeSeconds: new BigNumber(Math.floor(_.now() / 1000) + 360),
|
||||
salt: new BigNumber(hexUtils.random()),
|
||||
callData: hexUtils.random(4),
|
||||
value: getRandomInteger(1, '1e18'),
|
||||
feeToken: feeToken.address,
|
||||
feeAmount: getRandomInteger(1, MAX_FEE_AMOUNT),
|
||||
domain: {
|
||||
chainId: 1, // Ganache's `chainid` opcode is hardcoded as 1
|
||||
verifyingContract: zeroEx.address,
|
||||
},
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
|
||||
async function signMetaTransactionAsync(mtx: ExchangeProxyMetaTransaction, signer?: string): Promise<string> {
|
||||
return signatureUtils.ecSignHashAsync(
|
||||
env.provider,
|
||||
getExchangeProxyMetaTransactionHash(mtx),
|
||||
signer || mtx.signer,
|
||||
);
|
||||
}
|
||||
|
||||
describe('getMetaTransactionHash()', () => {
|
||||
it('generates the correct hash', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const expected = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const actual = await feature.getMetaTransactionHash(mtx).callAsync();
|
||||
expect(actual).to.eq(expected);
|
||||
});
|
||||
});
|
||||
|
||||
interface TransformERC20Args {
|
||||
inputToken: string;
|
||||
outputToken: string;
|
||||
inputTokenAmount: BigNumber;
|
||||
minOutputTokenAmount: BigNumber;
|
||||
transformations: Array<{ deploymentNonce: BigNumber; data: string }>;
|
||||
}
|
||||
|
||||
function getRandomTransformERC20Args(fields: Partial<TransformERC20Args> = {}): TransformERC20Args {
|
||||
return {
|
||||
inputToken: randomAddress(),
|
||||
outputToken: randomAddress(),
|
||||
inputTokenAmount: getRandomInteger(1, '1e18'),
|
||||
minOutputTokenAmount: getRandomInteger(1, '1e18'),
|
||||
transformations: [{ deploymentNonce: new BigNumber(123), data: hexUtils.random() }],
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
|
||||
const RAW_SUCCESS_RESULT = hexUtils.leftPad(1337);
|
||||
|
||||
describe('executeMetaTransaction()', () => {
|
||||
it('can call `TransformERC20.transformERC20()`', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
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(mtx.callData),
|
||||
taker: mtx.signer,
|
||||
},
|
||||
],
|
||||
TestMetaTransactionsTransformERC20FeatureEvents.TransformERC20Called,
|
||||
);
|
||||
});
|
||||
|
||||
it('can call with any sender if `sender == 0`', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
sender: NULL_ADDRESS,
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
from: randomAddress(),
|
||||
};
|
||||
const rawResult = await feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
|
||||
expect(rawResult).to.eq(RAW_SUCCESS_RESULT);
|
||||
});
|
||||
|
||||
it('works without fee', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
feeAmount: ZERO_AMOUNT,
|
||||
feeToken: randomAddress(),
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
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);
|
||||
});
|
||||
|
||||
it('fails if the translated call fails', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
value: new BigNumber(666),
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).callAsync(callOpts);
|
||||
const actualCallData = transformERC20Feature
|
||||
._transformERC20(
|
||||
hexUtils.hash(mtx.callData),
|
||||
mtx.signer,
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(
|
||||
mtxHash,
|
||||
actualCallData,
|
||||
new StringRevertError('FAIL').encode(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails with unsupported function', async () => {
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature.createTransformWallet().getABIEncodedTransactionData(),
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionUnsupportedFunctionError(
|
||||
mtxHash,
|
||||
hexUtils.slice(mtx.callData, 0, 4),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot execute the same mtx twice', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const receipt = await feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionAlreadyExecutedError(
|
||||
mtxHash,
|
||||
receipt.blockNumber,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if not enough ETH provided', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value.minus(1),
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionInsufficientEthError(
|
||||
mtxHash,
|
||||
callOpts.value,
|
||||
mtx.value,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if gas price too low', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice.minus(1),
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionGasPriceError(
|
||||
mtxHash,
|
||||
callOpts.gasPrice,
|
||||
mtx.minGasPrice,
|
||||
mtx.maxGasPrice,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if gas price too high', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice.plus(1),
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionGasPriceError(
|
||||
mtxHash,
|
||||
callOpts.gasPrice,
|
||||
mtx.minGasPrice,
|
||||
mtx.maxGasPrice,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if expired', async () => {
|
||||
const mtx = getRandomMetaTransaction({
|
||||
expirationTimeSeconds: new BigNumber(Math.floor(_.now() / 1000 - 60)),
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionExpiredError(
|
||||
mtxHash,
|
||||
undefined,
|
||||
mtx.expirationTimeSeconds,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if wrong sender', async () => {
|
||||
const requiredSender = randomAddress();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
sender: requiredSender,
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionWrongSenderError(
|
||||
mtxHash,
|
||||
sender,
|
||||
requiredSender,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if signature is wrong', async () => {
|
||||
const mtx = getRandomMetaTransaction({ signer: signers[0] });
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx, signers[1]);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionInvalidSignatureError(
|
||||
mtxHash,
|
||||
signature,
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.WrongSigner,
|
||||
mtxHash,
|
||||
signers[0],
|
||||
signature,
|
||||
).encode(),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchExecuteMetaTransactions()', () => {
|
||||
it('can execute multiple transactions', async () => {
|
||||
const mtxs = _.times(2, i => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
return getRandomMetaTransaction({
|
||||
signer: signers[i],
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
});
|
||||
const signatures = await Promise.all(mtxs.map(async mtx => signMetaTransactionAsync(mtx)));
|
||||
const callOpts = {
|
||||
gasPrice: BigNumber.max(...mtxs.map(mtx => mtx.minGasPrice)),
|
||||
value: BigNumber.sum(...mtxs.map(mtx => mtx.value)),
|
||||
};
|
||||
const rawResults = await feature.batchExecuteMetaTransactions(mtxs, signatures).callAsync(callOpts);
|
||||
expect(rawResults).to.eql(mtxs.map(() => RAW_SUCCESS_RESULT));
|
||||
});
|
||||
|
||||
it('cannot execute the same transaction twice', async () => {
|
||||
const mtx = (() => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
return getRandomMetaTransaction({
|
||||
signer: _.sampleSize(signers, 1)[0],
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
})();
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const mtxs = _.times(2, () => mtx);
|
||||
const signatures = await Promise.all(mtxs.map(async m => signMetaTransactionAsync(m)));
|
||||
const callOpts = {
|
||||
gasPrice: BigNumber.max(...mtxs.map(m => m.minGasPrice)),
|
||||
value: BigNumber.sum(...mtxs.map(m => m.value)),
|
||||
};
|
||||
const block = await env.web3Wrapper.getBlockNumberAsync();
|
||||
const tx = feature.batchExecuteMetaTransactions(mtxs, signatures).callAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionAlreadyExecutedError(mtxHash, block),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMetaTransactionExecutedBlock()', () => {
|
||||
it('returns zero for an unexecuted mtx', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const block = await feature.getMetaTransactionExecutedBlock(mtx).callAsync();
|
||||
expect(block).to.bignumber.eq(0);
|
||||
});
|
||||
|
||||
it('returns the block it was executed in', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const receipt = await feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
const block = await feature.getMetaTransactionExecutedBlock(mtx).callAsync();
|
||||
expect(block).to.bignumber.eq(receipt.blockNumber);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMetaTransactionHashExecutedBlock()', () => {
|
||||
it('returns zero for an unexecuted mtx', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const block = await feature.getMetaTransactionHashExecutedBlock(mtxHash).callAsync();
|
||||
expect(block).to.bignumber.eq(0);
|
||||
});
|
||||
|
||||
it('returns the block it was executed in', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const receipt = await feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const block = await feature.getMetaTransactionHashExecutedBlock(mtxHash).callAsync();
|
||||
expect(block).to.bignumber.eq(receipt.blockNumber);
|
||||
});
|
||||
});
|
||||
});
|
231
contracts/zero-ex/test/features/signature_validator_test.ts
Normal file
231
contracts/zero-ex/test/features/signature_validator_test.ts
Normal file
@ -0,0 +1,231 @@
|
||||
import { blockchainTests, constants, expect, randomAddress, signingUtils } from '@0x/contracts-test-utils';
|
||||
import { signatureUtils } from '@0x/order-utils';
|
||||
import { SignatureType } from '@0x/types';
|
||||
import { hexUtils, ZeroExRevertErrors } from '@0x/utils';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { SignatureValidatorContract, ZeroExContract } from '../../src/wrappers';
|
||||
import { abis } from '../utils/abis';
|
||||
import { fullMigrateAsync } from '../utils/migration';
|
||||
|
||||
const { NULL_BYTES } = constants;
|
||||
|
||||
blockchainTests.resets('SignatureValidator feature', env => {
|
||||
let owner: string;
|
||||
let signers: string[];
|
||||
let zeroEx: ZeroExContract;
|
||||
let feature: SignatureValidatorContract;
|
||||
|
||||
before(async () => {
|
||||
[owner, ...signers] = await env.getAccountAddressesAsync();
|
||||
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults);
|
||||
feature = new SignatureValidatorContract(zeroEx.address, env.provider, env.txDefaults, abis);
|
||||
});
|
||||
|
||||
describe('validateHashSignature()', () => {
|
||||
it('can validate an eth_sign signature', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = await signatureUtils.ecSignHashAsync(env.provider, hash, signer);
|
||||
await feature.validateHashSignature(hash, signer, signature).callAsync();
|
||||
});
|
||||
|
||||
it('rejects a wrong eth_sign signature', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = await signatureUtils.ecSignHashAsync(env.provider, hash, signer);
|
||||
const notSigner = randomAddress();
|
||||
const tx = feature.validateHashSignature(hash, notSigner, signature).callAsync();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.WrongSigner,
|
||||
hash,
|
||||
notSigner,
|
||||
signature,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects an eth_sign if ecrecover() fails', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = hexUtils.concat(hexUtils.random(65), SignatureType.EthSign);
|
||||
const tx = feature.validateHashSignature(hash, signer, signature).callAsync();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.WrongSigner,
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects a too short eth_sign signature', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = hexUtils.slice(await signatureUtils.ecSignHashAsync(env.provider, hash, signer), 1);
|
||||
const tx = feature.validateHashSignature(hash, signer, signature).callAsync();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.InvalidLength,
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('can validate an eip712 signature', async () => {
|
||||
const privateKey = hexUtils.random();
|
||||
const signer = hexUtils.toHex(ethjs.privateToAddress(ethjs.toBuffer(privateKey)));
|
||||
const hash = hexUtils.random();
|
||||
const signature = hexUtils.toHex(
|
||||
signingUtils.signMessage(ethjs.toBuffer(hash), ethjs.toBuffer(privateKey), SignatureType.EIP712),
|
||||
);
|
||||
await feature.validateHashSignature(hash, signer, signature).callAsync();
|
||||
});
|
||||
|
||||
it('rejects a wrong eip712 signature', async () => {
|
||||
const privateKey = hexUtils.random();
|
||||
const hash = hexUtils.random();
|
||||
const signature = hexUtils.toHex(
|
||||
signingUtils.signMessage(ethjs.toBuffer(hash), ethjs.toBuffer(privateKey), SignatureType.EIP712),
|
||||
);
|
||||
const notSigner = randomAddress();
|
||||
const tx = feature.validateHashSignature(hash, notSigner, signature).callAsync();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.WrongSigner,
|
||||
hash,
|
||||
notSigner,
|
||||
signature,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects an eip712 if ecrecover() fails', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = hexUtils.concat(hexUtils.random(65), SignatureType.EIP712);
|
||||
const tx = feature.validateHashSignature(hash, signer, signature).callAsync();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.WrongSigner,
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects a too short eip712 signature', async () => {
|
||||
const privateKey = hexUtils.random();
|
||||
const signer = hexUtils.toHex(ethjs.privateToAddress(ethjs.toBuffer(privateKey)));
|
||||
const hash = hexUtils.random();
|
||||
const signature = hexUtils.slice(
|
||||
hexUtils.toHex(
|
||||
signingUtils.signMessage(ethjs.toBuffer(hash), ethjs.toBuffer(privateKey), SignatureType.EIP712),
|
||||
),
|
||||
1,
|
||||
);
|
||||
const tx = feature.validateHashSignature(hash, signer, signature).callAsync();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.InvalidLength,
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects an INVALID signature type', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = hexUtils.concat(
|
||||
hexUtils.slice(await signatureUtils.ecSignHashAsync(env.provider, hash, signer), 0, -1),
|
||||
SignatureType.Invalid,
|
||||
);
|
||||
const tx = feature.validateHashSignature(hash, signer, signature).callAsync();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.AlwaysInvalid,
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects an ILLEGAL signature type', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = hexUtils.concat(
|
||||
hexUtils.slice(await signatureUtils.ecSignHashAsync(env.provider, hash, signer), 0, -1),
|
||||
SignatureType.Illegal,
|
||||
);
|
||||
const tx = feature.validateHashSignature(hash, signer, signature).callAsync();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.Illegal,
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects an unsupported signature type', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = hexUtils.concat(
|
||||
hexUtils.slice(await signatureUtils.ecSignHashAsync(env.provider, hash, signer), 0, -1),
|
||||
SignatureType.Wallet,
|
||||
);
|
||||
const tx = feature.validateHashSignature(hash, signer, signature).callAsync();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.Unsupported,
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects an empty signature type', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = NULL_BYTES;
|
||||
const tx = feature.validateHashSignature(hash, signer, signature).callAsync();
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.SignatureValidator.SignatureValidationError(
|
||||
ZeroExRevertErrors.SignatureValidator.SignatureValidationErrorCodes.InvalidLength,
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidHashSignature()', () => {
|
||||
it('returns true on valid signature', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = await signatureUtils.ecSignHashAsync(env.provider, hash, signer);
|
||||
const r = await feature.isValidHashSignature(hash, signer, signature).callAsync();
|
||||
expect(r).to.eq(true);
|
||||
});
|
||||
|
||||
it('returns false on invalid signature', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const signer = _.sampleSize(signers, 1)[0];
|
||||
const signature = await signatureUtils.ecSignHashAsync(env.provider, hash, signer);
|
||||
const r = await feature.isValidHashSignature(hash, randomAddress(), signature).callAsync();
|
||||
expect(r).to.eq(false);
|
||||
});
|
||||
});
|
||||
});
|
@ -22,12 +22,12 @@ blockchainTests.resets('TokenSpender feature', env => {
|
||||
before(async () => {
|
||||
const [owner] = await env.getAccountAddressesAsync();
|
||||
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {
|
||||
tokenSpender: await TokenSpenderContract.deployFrom0xArtifactAsync(
|
||||
tokenSpender: (await TokenSpenderContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestTokenSpender,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
),
|
||||
)).address,
|
||||
});
|
||||
feature = new TokenSpenderContract(zeroEx.address, env.provider, env.txDefaults, abis);
|
||||
token = await TestTokenSpenderERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
|
@ -40,12 +40,12 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
{
|
||||
transformERC20: await TransformERC20Contract.deployFrom0xArtifactAsync(
|
||||
transformERC20: (await TransformERC20Contract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestTransformERC20,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
),
|
||||
)).address,
|
||||
},
|
||||
{ transformerDeployer },
|
||||
);
|
||||
|
@ -6,10 +6,12 @@ import * as _ from 'lodash';
|
||||
|
||||
import { artifacts } from './artifacts';
|
||||
import { abis } from './utils/abis';
|
||||
import { deployFullFeaturesAsync, FullFeatures, toFeatureAdddresses } from './utils/migration';
|
||||
import { deployFullFeaturesAsync, FullFeatures } from './utils/migration';
|
||||
import {
|
||||
AllowanceTargetContract,
|
||||
IMetaTransactionsContract,
|
||||
IOwnableContract,
|
||||
ISignatureValidatorContract,
|
||||
ITokenSpenderContract,
|
||||
ITransformERC20Contract,
|
||||
TestFullMigrationContract,
|
||||
@ -27,7 +29,6 @@ blockchainTests.resets('Full migration', env => {
|
||||
|
||||
before(async () => {
|
||||
[owner] = await env.getAccountAddressesAsync();
|
||||
features = await deployFullFeaturesAsync(env.provider, env.txDefaults);
|
||||
migrator = await TestFullMigrationContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestFullMigration,
|
||||
env.provider,
|
||||
@ -35,9 +36,17 @@ blockchainTests.resets('Full migration', env => {
|
||||
artifacts,
|
||||
env.txDefaults.from as string,
|
||||
);
|
||||
const deployCall = migrator.deploy(owner, toFeatureAdddresses(features), { transformerDeployer });
|
||||
zeroEx = new ZeroExContract(await deployCall.callAsync(), env.provider, env.txDefaults);
|
||||
await deployCall.awaitTransactionSuccessAsync();
|
||||
zeroEx = await ZeroExContract.deployFrom0xArtifactAsync(
|
||||
artifacts.ZeroEx,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
await migrator.getBootstrapper().callAsync(),
|
||||
);
|
||||
features = await deployFullFeaturesAsync(env.provider, env.txDefaults, zeroEx.address);
|
||||
await migrator
|
||||
.initializeZeroEx(owner, zeroEx.address, features, { transformerDeployer })
|
||||
.awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('ZeroEx has the correct owner', async () => {
|
||||
@ -51,10 +60,10 @@ blockchainTests.resets('Full migration', env => {
|
||||
expect(dieRecipient).to.eq(owner);
|
||||
});
|
||||
|
||||
it('Non-deployer cannot call deploy()', async () => {
|
||||
it('Non-deployer cannot call initializeZeroEx()', async () => {
|
||||
const notDeployer = randomAddress();
|
||||
const tx = migrator
|
||||
.deploy(owner, toFeatureAdddresses(features), { transformerDeployer })
|
||||
.initializeZeroEx(owner, zeroEx.address, features, { transformerDeployer })
|
||||
.callAsync({ from: notDeployer });
|
||||
return expect(tx).to.revertWith('FullMigration/INVALID_SENDER');
|
||||
});
|
||||
@ -74,6 +83,21 @@ blockchainTests.resets('Full migration', env => {
|
||||
'setTransformerDeployer',
|
||||
],
|
||||
},
|
||||
SignatureValidator: {
|
||||
contractType: ISignatureValidatorContract,
|
||||
fns: ['isValidHashSignature', 'validateHashSignature'],
|
||||
},
|
||||
MetaTransactions: {
|
||||
contractType: IMetaTransactionsContract,
|
||||
fns: [
|
||||
'executeMetaTransaction',
|
||||
'batchExecuteMetaTransactions',
|
||||
'_executeMetaTransaction',
|
||||
'getMetaTransactionExecutedBlock',
|
||||
'getMetaTransactionHashExecutedBlock',
|
||||
'getMetaTransactionHash',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
function createFakeInputs(inputs: DataItem[] | DataItem): any | any[] {
|
||||
|
@ -2,7 +2,7 @@ import { blockchainTests, expect, randomAddress } from '@0x/contracts-test-utils
|
||||
import { hexUtils, ZeroExRevertErrors } from '@0x/utils';
|
||||
|
||||
import { artifacts } from './artifacts';
|
||||
import { BootstrapFeatures, deployBootstrapFeaturesAsync, toFeatureAdddresses } from './utils/migration';
|
||||
import { BootstrapFeatures, deployBootstrapFeaturesAsync } from './utils/migration';
|
||||
import {
|
||||
IBootstrapContract,
|
||||
InitialMigrationContract,
|
||||
@ -35,9 +35,14 @@ blockchainTests.resets('Initial migration', env => {
|
||||
env.txDefaults,
|
||||
{},
|
||||
);
|
||||
const deployCall = migrator.deploy(owner, toFeatureAdddresses(features));
|
||||
zeroEx = new ZeroExContract(await deployCall.callAsync(), env.provider, env.txDefaults);
|
||||
await deployCall.awaitTransactionSuccessAsync();
|
||||
zeroEx = await ZeroExContract.deployFrom0xArtifactAsync(
|
||||
artifacts.ZeroEx,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
migrator.address,
|
||||
);
|
||||
await migrator.initializeZeroEx(owner, zeroEx.address, features).awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('Self-destructs after deployment', async () => {
|
||||
@ -45,9 +50,9 @@ blockchainTests.resets('Initial migration', env => {
|
||||
expect(dieRecipient).to.eq(owner);
|
||||
});
|
||||
|
||||
it('Non-deployer cannot call deploy()', async () => {
|
||||
it('Non-deployer cannot call initializeZeroEx()', async () => {
|
||||
const notDeployer = randomAddress();
|
||||
const tx = migrator.deploy(owner, toFeatureAdddresses(features)).callAsync({ from: notDeployer });
|
||||
const tx = migrator.initializeZeroEx(owner, zeroEx.address, features).callAsync({ from: notDeployer });
|
||||
return expect(tx).to.revertWith('InitialMigration/INVALID_SENDER');
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,6 @@ export {
|
||||
deployFullFeaturesAsync,
|
||||
initialMigrateAsync,
|
||||
fullMigrateAsync,
|
||||
toFeatureAdddresses,
|
||||
FullMigrationOpts,
|
||||
FullFeatures,
|
||||
} from '../../src/migration';
|
||||
|
@ -8,6 +8,7 @@ export * from '../test/generated-wrappers/allowance_target';
|
||||
export * from '../test/generated-wrappers/bootstrap';
|
||||
export * from '../test/generated-wrappers/fill_quote_transformer';
|
||||
export * from '../test/generated-wrappers/fixin_common';
|
||||
export * from '../test/generated-wrappers/fixin_e_i_p712';
|
||||
export * from '../test/generated-wrappers/fixin_gas_token';
|
||||
export * from '../test/generated-wrappers/flash_wallet';
|
||||
export * from '../test/generated-wrappers/full_migration';
|
||||
@ -19,7 +20,9 @@ export * from '../test/generated-wrappers/i_exchange';
|
||||
export * from '../test/generated-wrappers/i_feature';
|
||||
export * from '../test/generated-wrappers/i_flash_wallet';
|
||||
export * from '../test/generated-wrappers/i_gas_token';
|
||||
export * from '../test/generated-wrappers/i_meta_transactions';
|
||||
export * from '../test/generated-wrappers/i_ownable';
|
||||
export * from '../test/generated-wrappers/i_signature_validator';
|
||||
export * from '../test/generated-wrappers/i_simple_function_registry';
|
||||
export * from '../test/generated-wrappers/i_test_simple_function_registry_feature';
|
||||
export * from '../test/generated-wrappers/i_token_spender';
|
||||
@ -28,11 +31,14 @@ export * from '../test/generated-wrappers/initial_migration';
|
||||
export * from '../test/generated-wrappers/lib_bootstrap';
|
||||
export * from '../test/generated-wrappers/lib_common_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_erc20_transformer';
|
||||
export * from '../test/generated-wrappers/lib_meta_transactions_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_meta_transactions_storage';
|
||||
export * from '../test/generated-wrappers/lib_migrate';
|
||||
export * from '../test/generated-wrappers/lib_ownable_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_ownable_storage';
|
||||
export * from '../test/generated-wrappers/lib_proxy_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_proxy_storage';
|
||||
export * from '../test/generated-wrappers/lib_signature_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_simple_function_registry_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_simple_function_registry_storage';
|
||||
export * from '../test/generated-wrappers/lib_spender_rich_errors';
|
||||
@ -41,8 +47,10 @@ export * from '../test/generated-wrappers/lib_token_spender_storage';
|
||||
export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_transform_erc20_storage';
|
||||
export * from '../test/generated-wrappers/lib_wallet_rich_errors';
|
||||
export * from '../test/generated-wrappers/meta_transactions';
|
||||
export * from '../test/generated-wrappers/ownable';
|
||||
export * from '../test/generated-wrappers/pay_taker_transformer';
|
||||
export * from '../test/generated-wrappers/signature_validator';
|
||||
export * from '../test/generated-wrappers/simple_function_registry';
|
||||
export * from '../test/generated-wrappers/test_call_target';
|
||||
export * from '../test/generated-wrappers/test_delegate_caller';
|
||||
@ -51,6 +59,7 @@ export * from '../test/generated-wrappers/test_fill_quote_transformer_exchange';
|
||||
export * from '../test/generated-wrappers/test_fill_quote_transformer_host';
|
||||
export * from '../test/generated-wrappers/test_full_migration';
|
||||
export * from '../test/generated-wrappers/test_initial_migration';
|
||||
export * from '../test/generated-wrappers/test_meta_transactions_transform_erc20_feature';
|
||||
export * from '../test/generated-wrappers/test_migrator';
|
||||
export * from '../test/generated-wrappers/test_mint_token_erc20_transformer';
|
||||
export * from '../test/generated-wrappers/test_mintable_erc20_token';
|
||||
|
@ -14,8 +14,10 @@
|
||||
"generated-artifacts/ITokenSpender.json",
|
||||
"generated-artifacts/ITransformERC20.json",
|
||||
"generated-artifacts/InitialMigration.json",
|
||||
"generated-artifacts/MetaTransactions.json",
|
||||
"generated-artifacts/Ownable.json",
|
||||
"generated-artifacts/PayTakerTransformer.json",
|
||||
"generated-artifacts/SignatureValidator.json",
|
||||
"generated-artifacts/SimpleFunctionRegistry.json",
|
||||
"generated-artifacts/TokenSpender.json",
|
||||
"generated-artifacts/TransformERC20.json",
|
||||
@ -26,6 +28,7 @@
|
||||
"test/generated-artifacts/Bootstrap.json",
|
||||
"test/generated-artifacts/FillQuoteTransformer.json",
|
||||
"test/generated-artifacts/FixinCommon.json",
|
||||
"test/generated-artifacts/FixinEIP712.json",
|
||||
"test/generated-artifacts/FixinGasToken.json",
|
||||
"test/generated-artifacts/FlashWallet.json",
|
||||
"test/generated-artifacts/FullMigration.json",
|
||||
@ -37,7 +40,9 @@
|
||||
"test/generated-artifacts/IFeature.json",
|
||||
"test/generated-artifacts/IFlashWallet.json",
|
||||
"test/generated-artifacts/IGasToken.json",
|
||||
"test/generated-artifacts/IMetaTransactions.json",
|
||||
"test/generated-artifacts/IOwnable.json",
|
||||
"test/generated-artifacts/ISignatureValidator.json",
|
||||
"test/generated-artifacts/ISimpleFunctionRegistry.json",
|
||||
"test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json",
|
||||
"test/generated-artifacts/ITokenSpender.json",
|
||||
@ -46,11 +51,14 @@
|
||||
"test/generated-artifacts/LibBootstrap.json",
|
||||
"test/generated-artifacts/LibCommonRichErrors.json",
|
||||
"test/generated-artifacts/LibERC20Transformer.json",
|
||||
"test/generated-artifacts/LibMetaTransactionsRichErrors.json",
|
||||
"test/generated-artifacts/LibMetaTransactionsStorage.json",
|
||||
"test/generated-artifacts/LibMigrate.json",
|
||||
"test/generated-artifacts/LibOwnableRichErrors.json",
|
||||
"test/generated-artifacts/LibOwnableStorage.json",
|
||||
"test/generated-artifacts/LibProxyRichErrors.json",
|
||||
"test/generated-artifacts/LibProxyStorage.json",
|
||||
"test/generated-artifacts/LibSignatureRichErrors.json",
|
||||
"test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json",
|
||||
"test/generated-artifacts/LibSimpleFunctionRegistryStorage.json",
|
||||
"test/generated-artifacts/LibSpenderRichErrors.json",
|
||||
@ -59,8 +67,10 @@
|
||||
"test/generated-artifacts/LibTransformERC20RichErrors.json",
|
||||
"test/generated-artifacts/LibTransformERC20Storage.json",
|
||||
"test/generated-artifacts/LibWalletRichErrors.json",
|
||||
"test/generated-artifacts/MetaTransactions.json",
|
||||
"test/generated-artifacts/Ownable.json",
|
||||
"test/generated-artifacts/PayTakerTransformer.json",
|
||||
"test/generated-artifacts/SignatureValidator.json",
|
||||
"test/generated-artifacts/SimpleFunctionRegistry.json",
|
||||
"test/generated-artifacts/TestCallTarget.json",
|
||||
"test/generated-artifacts/TestDelegateCaller.json",
|
||||
@ -69,6 +79,7 @@
|
||||
"test/generated-artifacts/TestFillQuoteTransformerHost.json",
|
||||
"test/generated-artifacts/TestFullMigration.json",
|
||||
"test/generated-artifacts/TestInitialMigration.json",
|
||||
"test/generated-artifacts/TestMetaTransactionsTransformERC20Feature.json",
|
||||
"test/generated-artifacts/TestMigrator.json",
|
||||
"test/generated-artifacts/TestMintTokenERC20Transformer.json",
|
||||
"test/generated-artifacts/TestMintableERC20Token.json",
|
||||
|
@ -5,6 +5,10 @@
|
||||
{
|
||||
"note": "Export `GethCallOverrides` type",
|
||||
"pr": 2620
|
||||
},
|
||||
{
|
||||
"note": "Export `ExchangeProxyMetaTransaction` and `SignedExchangeProxyMetaTransaction`",
|
||||
"pr": 2610
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -82,6 +82,8 @@ export {
|
||||
EventCallback,
|
||||
IndexedFilterValues,
|
||||
DecodedLogEvent,
|
||||
ExchangeProxyMetaTransaction,
|
||||
SignedExchangeProxyMetaTransaction,
|
||||
} from '@0x/types';
|
||||
|
||||
export {
|
||||
|
@ -21,6 +21,10 @@
|
||||
{
|
||||
"note": "Redeploy DFB on kovan",
|
||||
"pr": 2628
|
||||
},
|
||||
{
|
||||
"note": "Update ganache snapshot Exchange Proxy addresses for MetaTransactions",
|
||||
"pr": 2610
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -218,15 +218,15 @@
|
||||
"dexForwarderBridge": "0x0000000000000000000000000000000000000000",
|
||||
"multiBridge": "0x0000000000000000000000000000000000000000",
|
||||
"exchangeProxyGovernor": "0x0000000000000000000000000000000000000000",
|
||||
"exchangeProxy": "0x4b8ce0fa221284de4aaa09be3e7bf6193444b786",
|
||||
"exchangeProxyAllowanceTarget": "0xd6724bf180441a89d08ea3aeded2c995180b9a04",
|
||||
"exchangeProxy": "0x2ebb94cc79d7d0f1195300aaf191d118f53292a8",
|
||||
"exchangeProxyAllowanceTarget": "0x3eab3df72fd584b50184ff7d988a0d8f9328c866",
|
||||
"exchangeProxyTransformerDeployer": "0x5409ed021d9299bf6814279a6a1411a7e866a631",
|
||||
"exchangeProxyFlashWallet": "0xdec8629610e2f4087bd9cc441d10ca8be0c6f6c5",
|
||||
"exchangeProxyFlashWallet": "0x8362c3ebd90041b30ec45908332e592721642637",
|
||||
"transformers": {
|
||||
"wethTransformer": "0xb125995f5a4766c451cd8c34c4f5cac89b724571",
|
||||
"payTakerTransformer": "0x10a736a7b223f1fe1050264249d1abb975741e75",
|
||||
"fillQuoteTransformer": "0x33def1aa867be09809f3a01ce41d5ec1888846c9",
|
||||
"affiliateFeeTransformer": "0xc7124963ab16c33e5bf421d4c0090116622b3074"
|
||||
"wethTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",
|
||||
"payTakerTransformer": "0xc6b0d3c45a6b5092808196cb00df5c357d55e1d5",
|
||||
"fillQuoteTransformer": "0xc7124963ab16c33e5bf421d4c0090116622b3074",
|
||||
"affiliateFeeTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
"fix": "tslint --fix --format stylish --project .--exclude **/lib/**/*",
|
||||
"test:circleci": "run-s test:coverage",
|
||||
"test": "yarn run_mocha",
|
||||
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js lib/test/global_hooks.js --timeout 20000 --bail --exit",
|
||||
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js lib/test/global_hooks.js --timeout 30000 --bail --exit",
|
||||
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
|
||||
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
|
||||
"prettier": "prettier --write **/* --config ../../.prettierrc",
|
||||
|
@ -5,6 +5,10 @@
|
||||
{
|
||||
"note": "Add ERC20 Transformer utils and export useful constants.",
|
||||
"pr": 2604
|
||||
},
|
||||
{
|
||||
"note": "Add `getOrderHash()`, `getExchangeTransactionHash()`, `getExchangeProxyTransactionHash()`",
|
||||
"pr": 2610
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -64,6 +64,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.8",
|
||||
"@0x/contract-addresses": "^4.10.0",
|
||||
"@0x/contract-wrappers": "^13.7.0",
|
||||
"@0x/json-schemas": "^5.0.8",
|
||||
"@0x/utils": "^5.5.0",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import { BigNumber, NULL_ADDRESS, NULL_BYTES } from '@0x/utils';
|
||||
import { MethodAbi } from 'ethereum-types';
|
||||
|
||||
@ -150,6 +151,27 @@ export const constants = {
|
||||
{ name: 'transactionSignature', type: 'bytes' },
|
||||
],
|
||||
},
|
||||
MAINNET_EXCHANGE_PROXY_DOMAIN: {
|
||||
name: 'ZeroEx',
|
||||
version: '1.0.0',
|
||||
chainId: 1,
|
||||
verifyingContract: getContractAddressesForChainOrThrow(1).exchangeProxy,
|
||||
},
|
||||
EXCHANGE_PROXY_MTX_SCEHMA: {
|
||||
name: 'MetaTransactionData',
|
||||
parameters: [
|
||||
{ name: 'signer', type: 'address' },
|
||||
{ name: 'sender', type: 'address' },
|
||||
{ name: 'minGasPrice', type: 'uint256' },
|
||||
{ name: 'maxGasPrice', type: 'uint256' },
|
||||
{ name: 'expirationTimeSeconds', type: 'uint256' },
|
||||
{ name: 'salt', type: 'uint256' },
|
||||
{ name: 'callData', type: 'bytes' },
|
||||
{ name: 'value', type: 'uint256' },
|
||||
{ name: 'feeToken', type: 'address' },
|
||||
{ name: 'feeAmount', type: 'uint256' },
|
||||
],
|
||||
},
|
||||
ERC20_METHOD_ABI,
|
||||
ERC721_METHOD_ABI,
|
||||
MULTI_ASSET_METHOD_ABI,
|
||||
|
@ -5,11 +5,12 @@ import {
|
||||
EIP712Object,
|
||||
EIP712TypedData,
|
||||
EIP712Types,
|
||||
ExchangeProxyMetaTransaction,
|
||||
Order,
|
||||
SignedZeroExTransaction,
|
||||
ZeroExTransaction,
|
||||
} from '@0x/types';
|
||||
import { hexUtils, signTypedDataUtils } from '@0x/utils';
|
||||
import { BigNumber, hexUtils, signTypedDataUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from './constants';
|
||||
@ -131,4 +132,21 @@ export const eip712Utils = {
|
||||
);
|
||||
return typedData;
|
||||
},
|
||||
createExchangeProxyMetaTransactionTypedData(mtx: ExchangeProxyMetaTransaction): EIP712TypedData {
|
||||
return eip712Utils.createTypedData(
|
||||
constants.EXCHANGE_PROXY_MTX_SCEHMA.name,
|
||||
{
|
||||
MetaTransactionData: constants.EXCHANGE_PROXY_MTX_SCEHMA.parameters,
|
||||
},
|
||||
_.mapValues(
|
||||
_.omit(mtx, 'domain'),
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
v => (BigNumber.isBigNumber(v) ? v.toString(10) : v),
|
||||
) as EIP712Object,
|
||||
{
|
||||
...constants.MAINNET_EXCHANGE_PROXY_DOMAIN,
|
||||
...mtx.domain,
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
|
29
packages/order-utils/src/hash_utils.ts
Normal file
29
packages/order-utils/src/hash_utils.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { ExchangeProxyMetaTransaction, Order, ZeroExTransaction } from '@0x/types';
|
||||
import { hexUtils, signTypedDataUtils } from '@0x/utils';
|
||||
|
||||
import { eip712Utils } from './eip712_utils';
|
||||
import { orderHashUtils } from './order_hash_utils';
|
||||
import { transactionHashUtils } from './transaction_hash_utils';
|
||||
|
||||
/**
|
||||
* Compute the EIP712 hash of an order.
|
||||
*/
|
||||
export function getOrderHash(order: Order): string {
|
||||
return orderHashUtils.getOrderHash(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the EIP712 hash of an Exchange meta-transaction.
|
||||
*/
|
||||
export function getExchangeMetaTransactionHash(tx: ZeroExTransaction): string {
|
||||
return transactionHashUtils.getTransactionHash(tx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the EIP712 hash of an Exchange Proxy meta-transaction.
|
||||
*/
|
||||
export function getExchangeProxyMetaTransactionHash(mtx: ExchangeProxyMetaTransaction): string {
|
||||
return hexUtils.toHex(
|
||||
signTypedDataUtils.generateTypedDataHash(eip712Utils.createExchangeProxyMetaTransactionTypedData(mtx)),
|
||||
);
|
||||
}
|
@ -49,6 +49,8 @@ export {
|
||||
ZeroExTransaction,
|
||||
SignedZeroExTransaction,
|
||||
ValidatorSignature,
|
||||
ExchangeProxyMetaTransaction,
|
||||
SignedExchangeProxyMetaTransaction,
|
||||
} from '@0x/types';
|
||||
|
||||
export {
|
||||
@ -77,6 +79,8 @@ export {
|
||||
decodeAffiliateFeeTransformerData,
|
||||
} from './transformer_data_encoders';
|
||||
|
||||
export { getOrderHash, getExchangeMetaTransactionHash, getExchangeProxyMetaTransactionHash } from './hash_utils';
|
||||
|
||||
import { constants } from './constants';
|
||||
export const NULL_ADDRESS = constants.NULL_ADDRESS;
|
||||
export const NULL_BYTES = constants.NULL_BYTES;
|
||||
|
@ -1,14 +1,16 @@
|
||||
import { schemas } from '@0x/json-schemas';
|
||||
import {
|
||||
ECSignature,
|
||||
ExchangeProxyMetaTransaction,
|
||||
Order,
|
||||
SignatureType,
|
||||
SignedExchangeProxyMetaTransaction,
|
||||
SignedOrder,
|
||||
SignedZeroExTransaction,
|
||||
ValidatorSignature,
|
||||
ZeroExTransaction,
|
||||
} from '@0x/types';
|
||||
import { providerUtils } from '@0x/utils';
|
||||
import { hexUtils, providerUtils } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { SupportedProvider } from 'ethereum-types';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
@ -16,6 +18,7 @@ import * as _ from 'lodash';
|
||||
|
||||
import { assert } from './assert';
|
||||
import { eip712Utils } from './eip712_utils';
|
||||
import { getExchangeProxyMetaTransactionHash } from './hash_utils';
|
||||
import { orderHashUtils } from './order_hash_utils';
|
||||
import { transactionHashUtils } from './transaction_hash_utils';
|
||||
import { TypedDataError } from './types';
|
||||
@ -187,6 +190,96 @@ export const signatureUtils = {
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Signs an Exchange Proxy meta-transaction and returns a SignedExchangeProxyMetaTransaction.
|
||||
* First `eth_signTypedData` is requested then a fallback to `eth_sign` if not
|
||||
* available on the supplied provider.
|
||||
* @param supportedProvider Web3 provider to use for all JSON RPC requests
|
||||
* @param transaction The ExchangeProxyMetaTransaction to sign.
|
||||
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
||||
* must be available via the supplied Provider.
|
||||
* @return A SignedExchangeProxyMetaTransaction containing the order and
|
||||
* elliptic curve signature with Signature Type.
|
||||
*/
|
||||
async ecSignExchangeProxyMetaTransactionAsync(
|
||||
supportedProvider: SupportedProvider,
|
||||
transaction: ExchangeProxyMetaTransaction,
|
||||
signerAddress: string,
|
||||
): Promise<SignedExchangeProxyMetaTransaction> {
|
||||
assert.doesConformToSchema('transaction', transaction, schemas.zeroExTransactionSchema, [schemas.hexSchema]);
|
||||
try {
|
||||
const signedTransaction = await signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync(
|
||||
supportedProvider,
|
||||
transaction,
|
||||
signerAddress,
|
||||
);
|
||||
return signedTransaction;
|
||||
} catch (err) {
|
||||
// HACK: We are unable to handle specific errors thrown since provider is not an object
|
||||
// under our control. It could be Metamask Web3, Ethers, or any general RPC provider.
|
||||
// We check for a user denying the signature request in a way that supports Metamask and
|
||||
// Coinbase Wallet. Unfortunately for signers with a different error message,
|
||||
// they will receive two signature requests.
|
||||
if (err.message.includes('User denied message signature')) {
|
||||
throw err;
|
||||
}
|
||||
const transactionHash = getExchangeProxyMetaTransactionHash(transaction);
|
||||
const signatureHex = await signatureUtils.ecSignHashAsync(
|
||||
supportedProvider,
|
||||
transactionHash,
|
||||
signerAddress,
|
||||
);
|
||||
const signedTransaction = {
|
||||
...transaction,
|
||||
signature: signatureHex,
|
||||
};
|
||||
return signedTransaction;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Signs an Exchange Proxy meta-transaction using `eth_signTypedData` and
|
||||
* returns a SignedZeroExTransaction.
|
||||
* @param supportedProvider Web3 provider to use for all JSON RPC requests
|
||||
* @param transaction The Exchange Proxy transaction to sign.
|
||||
* @param signerAddress The hex encoded Ethereum address you wish
|
||||
* to sign it with. This address must be available via the supplied Provider.
|
||||
* @return A SignedExchangeProxyMetaTransaction containing the
|
||||
* ExchangeProxyMetaTransaction and elliptic curve signature with Signature Type.
|
||||
*/
|
||||
async ecSignTypedDataExchangeProxyMetaTransactionAsync(
|
||||
supportedProvider: SupportedProvider,
|
||||
transaction: ExchangeProxyMetaTransaction,
|
||||
signerAddress: string,
|
||||
): Promise<SignedExchangeProxyMetaTransaction> {
|
||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||
assert.isETHAddressHex('signerAddress', signerAddress);
|
||||
assert.doesConformToSchema('transaction', transaction, schemas.zeroExTransactionSchema, [schemas.hexSchema]);
|
||||
const web3Wrapper = new Web3Wrapper(provider);
|
||||
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
|
||||
const normalizedSignerAddress = signerAddress.toLowerCase();
|
||||
const typedData = eip712Utils.createExchangeProxyMetaTransactionTypedData(transaction);
|
||||
try {
|
||||
const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData);
|
||||
const ecSignatureRSV = parseSignatureHexAsRSV(signature);
|
||||
const signatureHex = hexUtils.concat(
|
||||
ecSignatureRSV.v,
|
||||
ecSignatureRSV.r,
|
||||
ecSignatureRSV.s,
|
||||
SignatureType.EIP712,
|
||||
);
|
||||
return {
|
||||
...transaction,
|
||||
signature: signatureHex,
|
||||
};
|
||||
} catch (err) {
|
||||
// Detect if Metamask to transition users to the MetamaskSubprovider
|
||||
if ((provider as any).isMetaMask) {
|
||||
throw new Error(TypedDataError.InvalidMetamaskSigner);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Signs a hash using `eth_sign` and returns its elliptic curve signature and signature type.
|
||||
* @param supportedProvider Web3 provider to use for all JSON RPC requests
|
||||
@ -245,12 +338,7 @@ export const signatureUtils = {
|
||||
* @return Hex encoded string of signature (v,r,s) with Signature Type
|
||||
*/
|
||||
convertECSignatureToSignatureHex(ecSignature: ECSignature): string {
|
||||
const signatureBuffer = Buffer.concat([
|
||||
ethUtil.toBuffer(ecSignature.v),
|
||||
ethUtil.toBuffer(ecSignature.r),
|
||||
ethUtil.toBuffer(ecSignature.s),
|
||||
]);
|
||||
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
|
||||
const signatureHex = hexUtils.concat(ecSignature.v, ecSignature.r, ecSignature.s);
|
||||
const signatureWithType = signatureUtils.convertToSignatureWithType(signatureHex, SignatureType.EthSign);
|
||||
return signatureWithType;
|
||||
},
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "3.2.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `ExchangeProxyMetaTransaction` and `SignedExchangeProxyMetaTransaction`",
|
||||
"pr": 2610
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1592969527,
|
||||
"version": "3.1.3",
|
||||
|
@ -54,6 +54,30 @@ export interface SignedZeroExTransaction extends ZeroExTransaction {
|
||||
signature: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange Proxy meta transaction struct.
|
||||
*/
|
||||
export interface ExchangeProxyMetaTransaction {
|
||||
signer: string;
|
||||
sender: string;
|
||||
minGasPrice: BigNumber;
|
||||
maxGasPrice: BigNumber;
|
||||
expirationTimeSeconds: BigNumber;
|
||||
salt: BigNumber;
|
||||
callData: string;
|
||||
value: BigNumber;
|
||||
feeToken: string;
|
||||
feeAmount: BigNumber;
|
||||
domain: EIP712DomainWithDefaultSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* `ExchangeProxyMetaTransaction` with `signature` field.
|
||||
*/
|
||||
export interface SignedExchangeProxyMetaTransaction extends ExchangeProxyMetaTransaction {
|
||||
signature: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Elliptic Curve signature
|
||||
*/
|
||||
|
@ -29,6 +29,10 @@
|
||||
{
|
||||
"note": "Update `ZeroExRevertErrors`",
|
||||
"pr": 2597
|
||||
},
|
||||
{
|
||||
"note": "Add more revert errors to `ZeroExRevertErrors`",
|
||||
"pr": 2610
|
||||
}
|
||||
],
|
||||
"timestamp": 1592969527
|
||||
|
@ -52,4 +52,6 @@ export const ZeroExRevertErrors = {
|
||||
Spender: require('./revert_errors/zero-ex/spender_revert_errors'),
|
||||
TransformERC20: require('./revert_errors/zero-ex/transform_erc20_revert_errors'),
|
||||
Wallet: require('./revert_errors/zero-ex/wallet_revert_errors'),
|
||||
MetaTransactions: require('./revert_errors/zero-ex/meta_transaction_revert_errors'),
|
||||
SignatureValidator: require('./revert_errors/zero-ex/signature_validator_revert_errors'),
|
||||
};
|
||||
|
@ -0,0 +1,144 @@
|
||||
import { RevertError } from '../../revert_error';
|
||||
import { Numberish } from '../../types';
|
||||
|
||||
// tslint:disable:max-classes-per-file
|
||||
export class InvalidMetaTransactionsArrayLengthsError extends RevertError {
|
||||
constructor(mtxCount?: Numberish, signatureCount?: Numberish) {
|
||||
super(
|
||||
'InvalidMetaTransactionsArrayLengthsError',
|
||||
'InvalidMetaTransactionsArrayLengthsError(uint256 mtxCount, uint256 signatureCount)',
|
||||
{
|
||||
mtxCount,
|
||||
signatureCount,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MetaTransactionAlreadyExecutedError extends RevertError {
|
||||
constructor(mtxHash?: string, executedBlockNumber?: Numberish) {
|
||||
super(
|
||||
'MetaTransactionAlreadyExecutedError',
|
||||
'MetaTransactionAlreadyExecutedError(bytes32 mtxHash, uint256 executedBlockNumber)',
|
||||
{
|
||||
mtxHash,
|
||||
executedBlockNumber,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MetaTransactionUnsupportedFunctionError extends RevertError {
|
||||
constructor(mtxHash?: string, selector?: string) {
|
||||
super(
|
||||
'MetaTransactionUnsupportedFunctionError',
|
||||
'MetaTransactionUnsupportedFunctionError(bytes32 mtxHash, bytes4 selector)',
|
||||
{
|
||||
mtxHash,
|
||||
selector,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MetaTransactionWrongSenderError extends RevertError {
|
||||
constructor(mtxHash?: string, sender?: string, expectedSender?: string) {
|
||||
super(
|
||||
'MetaTransactionWrongSenderError',
|
||||
'MetaTransactionWrongSenderError(bytes32 mtxHash, address sender, address expectedSender)',
|
||||
{
|
||||
mtxHash,
|
||||
sender,
|
||||
expectedSender,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MetaTransactionExpiredError extends RevertError {
|
||||
constructor(mtxHash?: string, time?: Numberish, expirationTime?: Numberish) {
|
||||
super(
|
||||
'MetaTransactionExpiredError',
|
||||
'MetaTransactionExpiredError(bytes32 mtxHash, uint256 time, uint256 expirationTime)',
|
||||
{
|
||||
mtxHash,
|
||||
time,
|
||||
expirationTime,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MetaTransactionGasPriceError extends RevertError {
|
||||
constructor(mtxHash?: string, gasPrice?: Numberish, minGasPrice?: Numberish, maxGasPrice?: Numberish) {
|
||||
super(
|
||||
'MetaTransactionGasPriceError',
|
||||
'MetaTransactionGasPriceError(bytes32 mtxHash, uint256 gasPrice, uint256 minGasPrice, uint256 maxGasPrice)',
|
||||
{
|
||||
mtxHash,
|
||||
gasPrice,
|
||||
minGasPrice,
|
||||
maxGasPrice,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MetaTransactionInsufficientEthError extends RevertError {
|
||||
constructor(mtxHash?: string, ethBalance?: Numberish, ethRequired?: Numberish) {
|
||||
super(
|
||||
'MetaTransactionInsufficientEthError',
|
||||
'MetaTransactionInsufficientEthError(bytes32 mtxHash, uint256 ethBalance, uint256 ethRequired)',
|
||||
{
|
||||
mtxHash,
|
||||
ethBalance,
|
||||
ethRequired,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MetaTransactionInvalidSignatureError extends RevertError {
|
||||
constructor(mtxHash?: string, signature?: string, errData?: string) {
|
||||
super(
|
||||
'MetaTransactionInvalidSignatureError',
|
||||
'MetaTransactionInvalidSignatureError(bytes32 mtxHash, bytes signature, bytes errData)',
|
||||
{
|
||||
mtxHash,
|
||||
signature,
|
||||
errData,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MetaTransactionCallFailedError extends RevertError {
|
||||
constructor(mtxHash?: string, callData?: string, returnData?: string) {
|
||||
super(
|
||||
'MetaTransactionCallFailedError',
|
||||
'MetaTransactionCallFailedError(bytes32 mtxHash, bytes callData, bytes returnData)',
|
||||
{
|
||||
mtxHash,
|
||||
callData,
|
||||
returnData,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const types = [
|
||||
InvalidMetaTransactionsArrayLengthsError,
|
||||
MetaTransactionAlreadyExecutedError,
|
||||
MetaTransactionUnsupportedFunctionError,
|
||||
MetaTransactionWrongSenderError,
|
||||
MetaTransactionExpiredError,
|
||||
MetaTransactionGasPriceError,
|
||||
MetaTransactionInsufficientEthError,
|
||||
MetaTransactionInvalidSignatureError,
|
||||
MetaTransactionCallFailedError,
|
||||
];
|
||||
|
||||
// Register the types we've defined.
|
||||
for (const type of types) {
|
||||
RevertError.registerType(type);
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import { RevertError } from '../../revert_error';
|
||||
|
||||
// tslint:disable:max-classes-per-file
|
||||
|
||||
export enum SignatureValidationErrorCodes {
|
||||
AlwaysInvalid = 0,
|
||||
InvalidLength = 1,
|
||||
Unsupported = 2,
|
||||
Illegal = 3,
|
||||
WrongSigner = 4,
|
||||
}
|
||||
|
||||
export class SignatureValidationError extends RevertError {
|
||||
constructor(code?: SignatureValidationErrorCodes, hash?: string, signerAddress?: string, signature?: string) {
|
||||
super(
|
||||
'SignatureValidationError',
|
||||
'SignatureValidationError(uint8 code, bytes32 hash, address signerAddress, bytes signature)',
|
||||
{
|
||||
code,
|
||||
hash,
|
||||
signerAddress,
|
||||
signature,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const types = [SignatureValidationError];
|
||||
|
||||
// Register the types we've defined.
|
||||
for (const type of types) {
|
||||
RevertError.registerType(type);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user