-
Notifications
You must be signed in to change notification settings - Fork 5.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EIP-3005: Batched meta transactions (ERC-20 extension function) #3005
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The EIPs repository is a place for standards, not "good ideas". In this case, no standard is being specified so I recommend instead writing this up as an Ethereum Magicians thread, blog post, etc. instead of an EIP.
Hi @MicahZoltu ! I admit being a bit internally conflicted with 2 views on how to continue with this EIP: View A) I started this whole project as a new standard proposal for a function called The reason why I then changed my mind and decided to publish it as an Informational EIP (instead of Standards Track) is that the gas cost tests that I conducted did not turn out the way I wished they would. Meaning there were not such meaningful gas reductions (or none at all) due to batching meta transactions, as opposed to doing normal on-chain token transactions. View B) On the other hand, my opinion on whether gas reductions of this implementation are "good enough" is obviously a subjective opinion. I imagine someone else would see the gas usage results differently. Further more, someone could say that gas usage costs don't even matter for them, because they value the feature of gas-less transactions over the gas cost reduction. And that the code should be agnostic to how people use it or perceive its value. In this case, the EIP should be changed to Standards Track and issued as an ERC-20 extension proposal (in a similar fashion as the already mentioned EIP-2612). I'm looking forward to other opinions to clear the dilemma that I have. I have opened two topics on Ethereum Magicians, one before issuing this pull request (link), and the other one after (link). |
If you decide to go forward with an ERC (specify some standard function that contracts would implement) then a pull request to this repository is a very reasonable course of action. If you decide to go with a suggestion on how people implement a thing, then other avenues would be superior. A lot of people have the misconception that you need to have an EIP to gain adoption of a thing in the Ethereum ecosystem, but in reality there are many EIPs that are never adopted (despite being good ideas) and there are many good ideas out there that are not EIPs but are widely used. I encourage you to not first decide "I want an EIP" and then try to shoehorn the idea into an EIP to meet that goal. Instead, figure out what an ideal solution to your problem would look like and then create an EIP if that is a natural part of the process, or don't if it isn't. |
Yeah, good point. A new ERC proposal (or extension) should be separate from an opinion piece - I tried to cram these two things into one. I'll rewrite this whole submission so that it's "only" an ERC proposal. And I'll write my opinion article (about the economics and gas usage) as a blog post somewhere else. Thanks for your feedback, I appreciate it a lot! |
@MicahZoltu I have updated the EIP, hope now it's more suitable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The specification, abstract, and simple summary sections all contain information that should be in other sections. Please move the content of these sections elsewhere or remove the content.
Note: Generally speaking, a good EIP is a short EIP. This whole EIP could probably be quite short if you remove most of the non-normative content. In general, I recommend limiting non-normative content to the bare minimum required to understand the EIP and instead put that content in a blog article or something.
address[] memory recipients, | ||
uint256[] memory amounts, | ||
uint256[] memory relayerFees, | ||
uint256[] memory blocks, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint256[] memory timestamps,
It is very uncommon that you actually want block numbers. Timestamps is almost always what is desired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking from the point-of-view that timestamps can be manipulated (by block producers), while block numbers cannot. That said, I don't have a strong opinion regarding this, either way is probably okay.
EIPS/eip-3005.md
Outdated
A meta transaction is a cryptographically signed message that a user sends to a relayer who then makes an on-chain transaction based on the meta transaction data. A relayer effectively pays gas fees in Ether, while a meta tx sender can compensate the relayer in tokens (a "gas-less" transaction). | ||
|
||
This proposal offers a solution to relay **multiple** meta transactions as a batch in one on-chain transaction. This reduces the gas cost that the relayer needs to pay, which in turn reduces the relayer fee that each meta tx sender pays in tokens. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A meta transaction is a cryptographically signed message that a user sends to a relayer who then makes an on-chain transaction based on the meta transaction data. A relayer effectively pays gas fees in Ether, while a meta tx sender can compensate the relayer in tokens (a "gas-less" transaction). | |
This proposal offers a solution to relay **multiple** meta transactions as a batch in one on-chain transaction. This reduces the gas cost that the relayer needs to pay, which in turn reduces the relayer fee that each meta tx sender pays in tokens. | |
Defines an ERC-20 extension for processing a batch of transfers by signature validation. |
Simple summary should be quite short. Think email subject line or sub-title on a forum.
EIPS/eip-3005.md
Outdated
The current meta transaction implementations (such as Gas Station Network - [EIP-1613](https://eips.ethereum.org/EIPS/eip-1613)) only relay one meta transaction through one on-chain transaction (1-to-1: 1 sender, 1 receiver). Gnosis Safe does the same, but can also relay a batch of meta transactions coming from **the same** sender (a 1-to-M batch: 1 sender, many receivers). | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current meta transaction implementations (such as Gas Station Network - [EIP-1613](https://eips.ethereum.org/EIPS/eip-1613)) only relay one meta transaction through one on-chain transaction (1-to-1: 1 sender, 1 receiver). Gnosis Safe does the same, but can also relay a batch of meta transactions coming from **the same** sender (a 1-to-M batch: 1 sender, many receivers). |
This is motivation. The abstract should be a fairly terse human readable description of the specification.
EIPS/eip-3005.md
Outdated
## Specification | ||
|
||
### How the system works | ||
|
||
A user sends a meta transaction to a relayer (through relayer's web app, for example). The relayer waits for multiple meta txs to arrive until the meta tx fees (paid in tokens) cover the cost of the on-chain gas fee (plus some margin that the relayer wants to earn). | ||
|
||
Then the relayer relays a batch of meta transactions using one on-chain transaction to the token contract (triggering the `processMetaBatch()` function). | ||
|
||
![](../assets/eip-3005/meta-txs-directly-to-token-smart-contract.png) | ||
|
||
Technically, the implementation means **adding a couple of functions** to the existing **ERC-20** token standard: | ||
|
||
- `processMetaBatch()` | ||
- `nonceOf()` | ||
|
||
You can see the proof-of-concept implementation in this file: [ERC20MetaBatch.sol](https://github.com/defifuture/erc20-batched-meta-transactions/blob/master/contracts/ERC20MetaBatch.sol). This is an extended ERC-20 contract with added meta tx batch transfer capabilities (see function `processMetaBatch()`). | ||
|
||
### `processMetaBatch()` | ||
|
||
The `processMetaBatch()` function is responsible for receiving and processing a batch of meta transactions that change token balances. | ||
|
||
```solidity | ||
function processMetaBatch(address[] memory senders, | ||
address[] memory recipients, | ||
uint256[] memory amounts, | ||
uint256[] memory relayerFees, | ||
uint256[] memory blocks, | ||
uint8[] memory sigV, | ||
bytes32[] memory sigR, | ||
bytes32[] memory sigS) public returns (bool) { | ||
|
||
address sender; | ||
uint256 newNonce; | ||
uint256 relayerFeesSum = 0; | ||
bytes32 msgHash; | ||
uint256 i; | ||
|
||
// loop through all meta txs | ||
for (i = 0; i < senders.length; i++) { | ||
sender = senders[i]; | ||
newNonce = _metaNonces[sender] + 1; | ||
|
||
if(sender == address(0) || recipients[i] == address(0)) { | ||
continue; // sender or recipient is 0x0 address, skip this meta tx | ||
} | ||
|
||
// the meta tx should be processed until (including) the specified block number, otherwise it is invalid | ||
if(block.number > blocks[i]) { | ||
continue; // if current block number is bigger than the requested number, skip this meta tx | ||
} | ||
|
||
// check if meta tx sender's balance is big enough | ||
if(_balances[sender] < (amounts[i] + relayerFees[i])) { | ||
continue; // if sender's balance is less than the amount and the relayer fee, skip this meta tx | ||
} | ||
|
||
// check if the signature is valid | ||
msgHash = keccak256(abi.encode(sender, recipients[i], amounts[i], relayerFees[i], newNonce, blocks[i], address(this), msg.sender)); | ||
if(sender != ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", msgHash)), sigV[i], sigR[i], sigS[i])) { | ||
continue; // if sig is not valid, skip to the next meta tx | ||
} | ||
|
||
// set a new nonce for the sender | ||
_metaNonces[sender] = newNonce; | ||
|
||
// transfer tokens | ||
_balances[sender] -= (amounts[i] + relayerFees[i]); | ||
_balances[recipients[i]] += amounts[i]; | ||
relayerFeesSum += relayerFees[i]; | ||
} | ||
|
||
// give the relayer the sum of all relayer fees | ||
_balances[msg.sender] += relayerFeesSum; | ||
|
||
return true; | ||
} | ||
``` | ||
|
||
> Note that the OpenZeppelin ERC-20 implementation was used here. Some other implementation may have named the balances mapping differently, which would require minor changes in the `processMetaBatch()` function. | ||
|
||
### `nonceOf()` | ||
|
||
Nonces are needed due to the replay protection (see *Replay attacks* under *Security Considerations*). | ||
|
||
```solidity | ||
mapping (address => uint256) private _metaNonces; | ||
|
||
// ... | ||
|
||
function nonceOf(address account) public view returns (uint256) { | ||
return _metaNonces[account]; | ||
} | ||
``` | ||
|
||
> The EIP-2612 (`permit()` function) also requires a nonce mapping. At this point, I'm not sure yet if this mapping should be **re-used** in case a smart contract implements both EIP-3005 and EIP-2612. | ||
> | ||
> At the first glance, it seems the nonce mapping could be re-used, but this should be thought through (and tested) for possible security implications. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
## Specification | |
### How the system works | |
A user sends a meta transaction to a relayer (through relayer's web app, for example). The relayer waits for multiple meta txs to arrive until the meta tx fees (paid in tokens) cover the cost of the on-chain gas fee (plus some margin that the relayer wants to earn). | |
Then the relayer relays a batch of meta transactions using one on-chain transaction to the token contract (triggering the `processMetaBatch()` function). | |
![](../assets/eip-3005/meta-txs-directly-to-token-smart-contract.png) | |
Technically, the implementation means **adding a couple of functions** to the existing **ERC-20** token standard: | |
- `processMetaBatch()` | |
- `nonceOf()` | |
You can see the proof-of-concept implementation in this file: [ERC20MetaBatch.sol](https://github.com/defifuture/erc20-batched-meta-transactions/blob/master/contracts/ERC20MetaBatch.sol). This is an extended ERC-20 contract with added meta tx batch transfer capabilities (see function `processMetaBatch()`). | |
### `processMetaBatch()` | |
The `processMetaBatch()` function is responsible for receiving and processing a batch of meta transactions that change token balances. | |
```solidity | |
function processMetaBatch(address[] memory senders, | |
address[] memory recipients, | |
uint256[] memory amounts, | |
uint256[] memory relayerFees, | |
uint256[] memory blocks, | |
uint8[] memory sigV, | |
bytes32[] memory sigR, | |
bytes32[] memory sigS) public returns (bool) { | |
address sender; | |
uint256 newNonce; | |
uint256 relayerFeesSum = 0; | |
bytes32 msgHash; | |
uint256 i; | |
// loop through all meta txs | |
for (i = 0; i < senders.length; i++) { | |
sender = senders[i]; | |
newNonce = _metaNonces[sender] + 1; | |
if(sender == address(0) || recipients[i] == address(0)) { | |
continue; // sender or recipient is 0x0 address, skip this meta tx | |
} | |
// the meta tx should be processed until (including) the specified block number, otherwise it is invalid | |
if(block.number > blocks[i]) { | |
continue; // if current block number is bigger than the requested number, skip this meta tx | |
} | |
// check if meta tx sender's balance is big enough | |
if(_balances[sender] < (amounts[i] + relayerFees[i])) { | |
continue; // if sender's balance is less than the amount and the relayer fee, skip this meta tx | |
} | |
// check if the signature is valid | |
msgHash = keccak256(abi.encode(sender, recipients[i], amounts[i], relayerFees[i], newNonce, blocks[i], address(this), msg.sender)); | |
if(sender != ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", msgHash)), sigV[i], sigR[i], sigS[i])) { | |
continue; // if sig is not valid, skip to the next meta tx | |
} | |
// set a new nonce for the sender | |
_metaNonces[sender] = newNonce; | |
// transfer tokens | |
_balances[sender] -= (amounts[i] + relayerFees[i]); | |
_balances[recipients[i]] += amounts[i]; | |
relayerFeesSum += relayerFees[i]; | |
} | |
// give the relayer the sum of all relayer fees | |
_balances[msg.sender] += relayerFeesSum; | |
return true; | |
} | |
``` | |
> Note that the OpenZeppelin ERC-20 implementation was used here. Some other implementation may have named the balances mapping differently, which would require minor changes in the `processMetaBatch()` function. | |
### `nonceOf()` | |
Nonces are needed due to the replay protection (see *Replay attacks* under *Security Considerations*). | |
```solidity | |
mapping (address => uint256) private _metaNonces; | |
// ... | |
function nonceOf(address account) public view returns (uint256) { | |
return _metaNonces[account]; | |
} | |
``` | |
> The EIP-2612 (`permit()` function) also requires a nonce mapping. At this point, I'm not sure yet if this mapping should be **re-used** in case a smart contract implements both EIP-3005 and EIP-2612. | |
> | |
> At the first glance, it seems the nonce mapping could be re-used, but this should be thought through (and tested) for possible security implications. |
This whole specification section should be reduced down to just the public interface. The rest is implementation details and suggestions on how to use it. Implementations should be either included in the ## Implementation
section as a reference implementation, or should be part of some other GitHub repository. Descriptions on how to use this at a high level should be a blog post, GitHub readme elsewhere, etc.
Technical specifications are often very short/terse. In this case, you need to describe the interface and what the parameters represent, but that is it. You will need to mention the nonces, though see my other comments on the nonce situation.
EIPS/eip-3005.md
Outdated
type: Standards Track | ||
category: ERC | ||
created: 2020-09-25 | ||
requires: 20 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to have a hard dependency on ERC-20. It could work with ERC-777 or any other token really. The only real requirement is that the contract has a fungible thing that can be transferred (needs to be fungible due to the relay fee mechanism). Leaving it as dependent on ERC-20 is probably fine if that is the easiest path forward, but I don't think it is actually necessary here.
@MicahZoltu I've updated the document based on your feedback. A few things I'm not sure about:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if you fix the trailing space in the frontmatter the bot will green light this and we can approve as a draft. More work needs to be done to get this into a state that can move past draft like clearly specifying the interface and the expected behavior, but that can be iterated on during draft phase.
At the moment, this reads like a good idea/suggestion rather than a specification.
EIPS/eip-3005.md
Outdated
eip: 3005 | ||
title: Batched meta transactions | ||
author: Matt (@defifuture) | ||
discussions-to: https://ethereum-magicians.org/t/eip-3005-the-economic-viability-of-batched-meta-transactions/4673 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
discussions-to: https://ethereum-magicians.org/t/eip-3005-the-economic-viability-of-batched-meta-transactions/4673 | |
discussions-to: https://ethereum-magicians.org/t/eip-3005-the-economic-viability-of-batched-meta-transactions/4673 |
I think this is what the bot is complaining about.
EIPS/eip-3005.md
Outdated
![](../assets/eip-3005/meta-txs-directly-to-token-smart-contract.png) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Motivation is the best place I can think of for this image. It isn't necessary for the specification, which means it probably shouldn't be part of summary/abstract/specification, and rationale makes even less sense than Motivation. 😄
EIPS/eip-3005.md
Outdated
- a relayer address | ||
- a signature | ||
|
||
Not all of these data needs to be sent to the function by the relayer. Some of the data can be deduced or extracted from other sources (from transaction data and contract state). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not all of these data needs to be sent to the function by the relayer. Some of the data can be deduced or extracted from other sources (from transaction data and contract state). | |
Not all of the data needs to be sent to the function by the relayer. Some of the data can be deduced or extracted from other sources (from transaction data and contract state). |
EIPS/eip-3005.md
Outdated
|
||
### Meta transaction data | ||
|
||
In order to successfully validate and transfer tokens, the proposed function needs to process the following data about a meta transaction: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the proposed function
EIPs don't propose, they assert. Write this assuming the person reading it has already decided to implement this and they are just looking to see what to implement. Think email addresses, when you read the RFC for them to correctly implement an email address parser you are expecting to see assertions rather than proposals.
- sender address | ||
- receiver address | ||
- token amount | ||
- relayer fee | ||
- a (meta tx) nonce | ||
- a timestamp or a block number (which represents a due date to process a meta tx) | ||
- a token address | ||
- a relayer address | ||
- a signature |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order for this standard to be implementable, this will need to be much more strictly defined. Often people will include a Solidity interface definition for the new function, though you also could strictly define the full calldata layout if you preferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think I should write this the way it's defined in EIP-2612 (the solidity pseudocode in the Specification)? Or are there any better EIP examples to learn from (applicable to my case, of course)? 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm looking now at EIP-20 and EIP-777 - do these two have a good Specification section?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EIP-20 and EIP-777 would be a reasonable place to start. The goal is to make it so someone can read the Specification section only and get exactly enough information required to implement something that is compatible with the standard. "Here are the things you must do to be considered compatible."
@MicahZoltu I have fixed the trailing space error, moved the image to the Motivation section and did some other minor text fixes. The automated checking system has successfully completed. I guess the main issue now remains making the Specification section better. Can you recommend some EIPs as good practice examples regarding this? (P.S.: I'm very grateful for your feedback and help! 🙂) |
…reum#3005) Defines an extension function for ERC-20 (and other fungible token standards), which allows receiving and processing a batch of meta transactions.
…reum#3005) Defines an extension function for ERC-20 (and other fungible token standards), which allows receiving and processing a batch of meta transactions.
Simple Summary
Defines an extension function for ERC-20 (and other fungible token standards), which allows receiving and processing a batch of meta transactions.
Abstract
This EIP defines a new function called
processMetaBatch()
that extends any fungible token standard, and enables batched meta transactions coming from many senders in one on-chain transaction.The function must be able to receive multiple meta transactions data and process it. This means validating the data and the signature, before proceeding with token transfers based on the data.
The function enables senders to make gasless transactions, while reducing the relayer's gas cost due to batching.
Motivation
Meta transactions have proven useful as a solution for Ethereum accounts that don't have any ether, but hold ERC-20 tokens and would like to transfer them (gasless transactions).
The current meta transaction relayer implementations only allow relaying one meta transaction at a time. Some also allow batched meta transactions from the same sender. But none offers batched meta transactions from multiple senders.
The motivation behind this EIP is to find a way to allow relaying batched meta transactions from many senders in one on-chain transaction, which also reduces the total gas cost that a relayer needs to cover.
Specification
Meta transaction data
In order to successfully validate and transfer tokens, the
processMetaBatch()
function needs to process the following data about a meta transaction:Not all of the data needs to be sent to the function by the relayer. Some of the data can be deduced or extracted from other sources (from transaction data and contract state).
Meta transaction nonce
The token smart contract must keep track of a meta transaction nonce for each token holder.
Meta transaction validation
Validation requirements:
Token transfers
If validation is successful, the meta nonce can be increased by 1 and the token transfers can occur:
msg.sender
)Implementation
The reference implementation adds a couple of functions to the existing ERC-20 token standard:
processMetaBatch()
nonceOf()
You can see the implementation of both functions in this file: ERC20MetaBatch.sol. This is an extended ERC-20 contract with added meta transaction batch transfer capabilities.
processMetaBatch()
The
processMetaBatch()
function is responsible for receiving and processing a batch of meta transactions that change token balances.nonceOf()
Nonces are needed due to the replay protection (see Replay attacks under Security Considerations).
The link to the complete implementation (along with gas usage results) is here: https://github.com/defifuture/erc20-batched-meta-transactions.
Rationale
All-in-one
Alternative implementations (like GSN) use multiple smart contracts to enable meta transactions, although this increases gas usage. This implementation (EIP-3005) intentionally keeps everything within one function which reduces complexity and gas cost.
The
processMetaBatch()
function thus does the job of receiving a batch of meta transactions, validating them, and then transferring tokens from one address to another.Function parameters
As you can see, the
processMetaBatch()
function in the reference implementation takes the following parameters:msg.sender
)Each item in these arrays represents data of one meta transaction. That's why the correct order in the arrays is very important.
If a relayer gets the order wrong, the
processMetaBatch()
function would notice that (when validating a signature), because the hash of the meta transaction values would not match the signed hash. A meta transaction with an invalid signature is skipped.The alternative way of passing meta transaction data into the function
The reference implementation takes parameters as arrays. There's a separate array for each meta transaction data category (the ones that cannot be deduced or extracted from other sources).
A different approach would be to bitpack all data of a meta transaction into one value and then unpack it within the smart contract. The data for a batch of meta transactions would be sent in an array, but there would need to be only one array (of packed data), instead of multiple arrays.
Why is nonce not one of the parameters in the reference implementation?
Meta nonce is used for constructing a signed hash (see the
msgHash
line where akeccak256
hash is constructed - you'll find a nonce there).Since a new nonce has to always be bigger than the previous one by exactly 1, there's no need to include it as a parameter array in the
processMetaBatch()
function, because its value can be deduced.This also helps avoid the "Stack too deep" error.
Can EIP-2612 nonces mapping be re-used?
The EIP-2612 (
permit()
function) also requires a nonce mapping. At this point, I'm not sure yet if this mapping should be re-used in case a smart contract implements both EIP-3005 and EIP-2612.At the first glance, it seems the
nonces
mapping from EIP-2612 could be re-used, but this should be thought through (and tested) for possible security implications.Token transfers
Token transfers in the reference implementation could alternatively be done by calling the
_transfer()
function (part of the OpenZeppelin ERC-20 implementation), but it would increase the gas usage and it would also revert the whole batch if some meta transaction was invalid (the current implementation just skips it).Another gas usage optimization is to assign total relayer fees to the relayer at the end of the function, and not with every token transfer inside the for loop (thus avoiding multiple SSTORE calls that cost 5'000 gas).
Backwards Compatibility
The code implementation of batched meta transactions is backwards compatible with any fungible token standard, for example, ERC-20 (it only extends it with one function).
Test Cases
Link to tests: https://github.com/defifuture/erc20-batched-meta-transactions/tree/master/test.
Security Considerations
Here is a list of potential security issues and how are they addressed in this implementation.
Forging a meta transaction
The solution against a relayer forging a meta transaction is for a user to sign the meta transaction with their private key.
The
processMetaBatch()
function then verifies the signature usingecrecover()
.Replay attacks
The
processMetaBatch()
function is secure against two types of a replay attack:Using the same meta transaction twice in the same token smart contract
A nonce prevents a replay attack where a relayer would send the same meta transaction more than once.
Using the same meta transaction twice in different token smart contracts
A token smart contract address must be added into the signed hash (of a meta transaction).
This address does not need to be sent as a parameter into the
processMetaBatch()
function. Instead, the function usesaddress(this)
when constructing a hash in order to verify the signature. This way a meta transaction not intended for the token smart contract would be rejected (skipped).Signature validation
Signing a meta transaction and validating the signature is crucial for this whole scheme to work.
The
processMetaBatch()
function validates a meta transaction signature, and if it's invalid, the meta transaction is skipped (but the whole on-chain transaction is not reverted).Why not reverting the whole on-chain transaction? Because there could be only one problematic meta transaction, and the others should not be dropped just because of one rotten apple.
That said, it is expected of relayers to validate meta transactions in advance before relaying them. That's why relayers are not entitled to a relayer fee for an invalid meta transaction.
Malicious relayer forcing a user into over-spending
A malicious relayer could delay sending some user's meta transaction until the user would decide to make the token transaction on-chain.
After that, the relayer would relay the delayed meta transaction which would mean that the user would have made two token transactions (over-spending).
Solution: Each meta transaction should have an "expiry date". This is defined in a form of a block number by which the meta transaction must be relayed on-chain.
Front-running attack
A malicious relayer could scout the Ethereum mempool to steal meta transactions and front-run the original relayer.
Solution: The protection that
processMetaBatch()
function uses is that it requires the meta transaction sender to add the relayer's Ethereum address as one of the values in the hash (which is then signed).When the
processMetaBatch()
function generates a hash it includes themsg.sender
address in it:If the meta transaction was "stolen", the signature check would fail because the
msg.sender
address would not be the same as the intended relayer's address.A malicious (or too impatient) user sending a meta transaction with the same nonce through multiple relayers at once
A user that is either malicious or just impatient could submit a meta transaction with the same nonce (for the same token contract) to various relayers. Only one of them would get the relayer fee (the first one on-chain), while the others would get an invalid meta transaction.
Solution: Relayers could share a list of their pending meta transactions between each other (sort of an info mempool).
The relayers don't have to fear that someone would steal their respective pending transactions, due to the front-running protection (see above).
If relayers see meta transactions from a certain sender address that have the same nonce and are supposed to be relayed to the same token smart contract, they can decide that only the first registered meta transaction goes through and others are dropped (or in case meta transactions were registered at the same time, the remaining meta transaction could be randomly picked).
At a minimum, relayers need to share this meta transaction data (in order to detect meta transaction collision):
Too big due block number
The relayer could trick the meta transaction sender into adding too big due block number - this means a block by which the meta transaction must be processed. The block number could be far in the future, for example, 10 years in the future. This means that the relayer would have 10 years to submit the meta transaction.
One way to solve this problem is by adding an upper bound constraint for a block number within the smart contract. For example, we could say that the specified due block number must not be bigger than 100'000 blocks from the current one (this is around 17 days in the future if we assume 15 seconds block time).
This addition could open new security implications, that's why it is left out of this proof-of-concept. But anyone who wishes to implement it should know about this potential constraint, too.
The other way is to keep the
processMetaBatch()
function as it is and rather check for the too big due block number on the relayer level. In this case, the user could be notified about the problem and could issue a new meta transaction with another relayer that would have a much lower block parameter (and the same nonce).Copyright
Copyright and related rights are waived via CC0.