* Strip erc20 package of legacy nonsense and add foundry basics * Make foundry build * Remove obsoleted test/UntransferrableDummyERC20Token.sol contract * Remove obsoleted ERC20 lib variant contracts * Remove obsoleted DummyMultipleReturnERC20Token and DummyNoReturnERC20Token contracts * Move test contract to dedicated folder and remove obsoleted TypeScript contract wrappers * Remove src/interfaces/IEtherToken.sol only used in v3 staking which is being obsoleted [skip ci] * Add foundry test for token * Migrate ZRX token tests to foundry * Fix paths to erc20 contracts * Remove obsoleted references * Pin erc20-contracts package on treasury * Ignore foundry imports in link checker * Run only forge tests for erc20 contracts * Remove DummyERC20Token and its dependencies * Merge IERC20TokenV06 and IERC20TokenV08 into range pragma to cover solidity 0.6.5 to 0.8.x * Merge IEtherTokenV06 and IEtherTokenV08 into range pragma to cover solidity 0.6.5 to 0.8.x * Migrate weth9 tests to foundry * Upload code coverage for erc20 package * Update changelog * Fix review comments Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com> --------- Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com>
234 lines
9.9 KiB
ReStructuredText
234 lines
9.9 KiB
ReStructuredText
###############################
|
|
Meta-Transactions
|
|
###############################
|
|
|
|
Meta-Transactions are signed messages that instruct the 0x Protocol to run function(s) in the context of the signer. This signed mtx can then be shared off-chain, allowing anyone to execute on-chain. This is useful for integrators who would like to subsidize the Ethereum Gas Fee, or add custom smart contract logic to run atomically a fill. A signed meta-transaction can only be executed once.
|
|
|
|
A common use case for this is in Request For Quote (RFQ) systems. The Maker creates an order; the Taker signs a mtx permitting 0x Protocol to fill the order on their behalf; the mtx is returned to the Maker who submits it on-chain.
|
|
|
|
.. image:: ../_static/img/rfqm.png
|
|
:alt: Meta-Transaction Example
|
|
:align: center
|
|
|
|
|
|
|
|
Constructing
|
|
============
|
|
|
|
To construct a Meta-Transaction, abi-encode the following struct and sign it.
|
|
|
|
.. code-block:: solidity
|
|
|
|
/// @dev Describes an exchange proxy meta transaction.
|
|
struct MetaTransactionData {
|
|
// Signer of meta-transaction. On whose behalf to execute the MTX.
|
|
address payable 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`.
|
|
IERC20Token feeToken;
|
|
// ERC20 fee amount.
|
|
uint256 feeAmount;
|
|
}
|
|
|
|
The ``calldata`` field is specific to the function you wish to execute. At this time, the following functions are supported:
|
|
|
|
- `fillLimitOrder <../basics/functions.html#filllimitorder>`_
|
|
- `fillRfqOrder <../basics/functions.html#fillrfqorder>`_
|
|
- `transformERC20 <../advanced/erc20_transformations.html>`_
|
|
|
|
Signing
|
|
=======
|
|
|
|
Meta-Transactions use the same signing technique as 0x Orders; see the `How to Sign <../basics/orders.html#how-to-sign>`_ section of the Orders documentation. See `getMetaTransactionHash`_ for generating a unique hash for your mtx.
|
|
|
|
Functionality
|
|
=============
|
|
|
|
+----------------------------------------+------------------------------------------------------------------------------------------------+
|
|
| Function | Overview |
|
|
+----------------------------------------+------------------------------------------------------------------------------------------------+
|
|
| `executeMetaTransaction`_ | Executes a single meta-transaction |
|
|
+----------------------------------------+------------------------------------------------------------------------------------------------+
|
|
| `batchExecuteMetaTransactions`_ | Executes a batch of meta-transactions. |
|
|
+----------------------------------------+------------------------------------------------------------------------------------------------+
|
|
| `getMetaTransactionExecutedBlock`_ | Returns the block that a meta-transaction was executed at. |
|
|
+----------------------------------------+------------------------------------------------------------------------------------------------+
|
|
| `getMetaTransactionHashExecutedBlock`_ | Same as ``getMetaTransactionExecutedBlock``, only this function takes a meta-transaction hash. |
|
|
+----------------------------------------+------------------------------------------------------------------------------------------------+
|
|
| `getMetaTransactionHash`_ | Returns the hash of a meta-transaction. |
|
|
+----------------------------------------+------------------------------------------------------------------------------------------------+
|
|
|
|
|
|
|
|
executeMetaTransaction
|
|
----------------------
|
|
|
|
A single Meta-Transaction is executed by calling ``executeMetaTransaction``. A batch of mtx's can be executed by calling ``batchExecuteMetaTransactions``.
|
|
|
|
.. code-block:: solidity
|
|
|
|
/// @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,
|
|
LibSignature.Signature calldata signature
|
|
)
|
|
external
|
|
payable
|
|
returns (bytes memory returnResult);
|
|
|
|
A `MetaTransactionExecuted <../basics/events.html#metatransactionexecuted>`_ event is emitted on succes. The ``returnResult`` contains the raw return data for the executed function. For example, if the function returns a ``uint256`` then the ``returnResult`` could be abi-decoded into a ``uint256``.
|
|
|
|
This call will revert in the following scenarios:
|
|
|
|
- The address in the ``mtx.sender`` field does not match ``msg.sender``.
|
|
- The mtx has expired.
|
|
- The Ethereum transaction's gas price (``tx.gasprice``) is outside of the range ``[mtx.minGasPrice..mtx.maxGasPrice]``
|
|
- The ETH sent with the mtx is less than ``mtx.value``
|
|
- The allowance/balance of ``signer`` is insufficient to pay ``feeAmount`` of ``feeToken`` to the ``sender`` (if specified)
|
|
- The signature is invalid.
|
|
- The mtx was already executed
|
|
- The underlying function is not supported by meta-transactions (see list above).
|
|
- The underlying function call reverts.
|
|
|
|
batchExecuteMetaTransactions
|
|
----------------------------
|
|
|
|
.. code-block:: solidity
|
|
|
|
/// @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,
|
|
LibSignature.Signature[] calldata signatures
|
|
)
|
|
external
|
|
payable
|
|
returns (bytes[] memory returnResults);
|
|
|
|
A `MetaTransactionExecuted <../basics/events.html#metatransactionexecuted>`_ event is emitted for each mtx on succes. The ``returnResult`` contains the raw return data for the executed function This call will revert if the one of the ``mtxs`` reverts. Any exceess Ether will be refunded to the ``msg.sender``.
|
|
|
|
|
|
getMetaTransactionExecutedBlock
|
|
-------------------------------
|
|
|
|
The ``block.number`` is stored on-chain when a mtx is executed. This value can be retrieved using the following function.
|
|
|
|
.. code-block:: solidity
|
|
|
|
/// @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);
|
|
|
|
getMetaTransactionHashExecutedBlock
|
|
-----------------------------------
|
|
|
|
This is a more gas-efficient implementation of ``getMetaTransactionExecutedBlock``.
|
|
|
|
.. code-block:: solidity
|
|
|
|
/// @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);
|
|
|
|
|
|
getMetaTransactionHash
|
|
----------------------
|
|
|
|
The hash of the mtx is used to uniquely identify it inside the protocol. It is computed following the `EIP712 spec <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md>`_ standard. In solidity, the hash is computed using:
|
|
|
|
.. code-block:: solidity
|
|
|
|
/// @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);
|
|
|
|
The simplest way to generate an order hash is by calling this function, ex:
|
|
|
|
.. code-block:: solidity
|
|
|
|
bytes32 orderHash = IZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF).getMetaTransactionHash(mtx);
|
|
|
|
The hash can be manually generated using the following code:
|
|
|
|
.. code-block:: solidity
|
|
|
|
bytes32 orderHash = keccak256(abi.encodePacked(
|
|
'\x19\x01',
|
|
// The domain separator.
|
|
keccak256(abi.encode(
|
|
// The EIP712 domain separator type hash.
|
|
keccak256(abi.encodePacked(
|
|
'EIP712Domain(',
|
|
'string name,',
|
|
'string version,',
|
|
'uint256 chainId,',
|
|
'address verifyingContract)'
|
|
)),
|
|
// The EIP712 domain separator values.
|
|
'ZeroEx',
|
|
'1.0.0',
|
|
1, // For mainnet
|
|
0xDef1C0ded9bec7F1a1670819833240f027b25EfF, // Address of the Exchange Proxy
|
|
)),
|
|
// The struct hash.
|
|
keccak256(abi.encode(
|
|
// The EIP712 type hash.
|
|
keccak256(abi.encodePacked(
|
|
"MetaTransactionData("
|
|
"address signer,"
|
|
"address sender,"
|
|
"uint256 minGasPrice,"
|
|
"uint256 maxGasPrice,"
|
|
"uint256 expirationTimeSeconds,"
|
|
"uint256 salt,"
|
|
"bytes callData,"
|
|
"uint256 value,"
|
|
"address feeToken,"
|
|
"uint256 feeAmount"
|
|
")"
|
|
)),
|
|
// The struct values.
|
|
mtx.signer,
|
|
mtx.sender,
|
|
mtx.minGasPrice,
|
|
mtx.maxGasPrice,
|
|
mtx.expirationTimeSeconds,
|
|
mtx.salt,
|
|
keccak256(mtx.callData),
|
|
mtx.value,
|
|
mtx.feeToken,
|
|
mtx.feeAmount
|
|
))
|
|
));
|
|
|