Skip to content
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

ERC-1776 Native Meta Transactions #1776

Closed
wighawag opened this issue Feb 25, 2019 · 19 comments
Closed

ERC-1776 Native Meta Transactions #1776

wighawag opened this issue Feb 25, 2019 · 19 comments
Labels

Comments

@wighawag
Copy link
Contributor

wighawag commented Feb 25, 2019

Introduction

Native Meta transactions (a term first coined by Austin Thomas Griffith here : https://medium.com/gitcoin/native-meta-transactions-e509d91a8482) allows users that simply own ERC20 or ERC777 (ERC1155 could be added too) tokens to perform operations on the ethereum network without needing to own ether themselves by letting a third party, the relayer, the responsibility to execute a transaction on the ethereum network carrying the desired operations (the so called meta transaction) in exchange of a token reward to cover the relayer's gas cost. They differ from traditional meta transactions (https://medium.com/uport/making-uport-smart-contracts-smarter-part-3-fixing-user-experience-with-meta-transactions-105209ed43e0) in that they only require support from the meta transaction processor contract itself and do not need the user wallet to be contract based.

The proposal here define the message format and meta transaction processor contract interface required for web3 wallets and browsers to provide a meaningful meta-transaction display when users are requested to approve. This could even allow wallets/browsers to not bother users by displaying an ether balance when such users do not have any ether or when the application being used does not require it.

It does not dictate how such signed message get injected on the network except for the meaning of each message parameters and their security requirements. More precisely, it does not dictate the ABI signature for the smart contract function executing the meta-transaction (the one signed and broadcast by the relayer). This is left as work for another standard.

Nevertheless due to nature of ERC20 this proposal also need to define how smart contract recipient of such meta transaction need to behave when being called. In particular it specify how the from field is to be verified.

Why native meta transactions?

It is common today for users to own ERC20 tokens without owning any ether. More and more applications reward their users without requiring prior on-chain interactions. It is thus possible for users to have been given tokens without them ever owning ether. With native meta transactions and a willing relayer (the company behind the token for example), such users can now interact with ethereum.

Without meta transactions, it would be impossible for them to interact with the ethereum network when required. Indeed, unless they have ether they can't interact with the ethereum network, requiring them to go through a difficult and costly process to acquire it. They can't even exchange their token for ether without having ether to pay the gas associated with such transaction.

While normal meta-transactions are possible using smart contract based wallet (like gnosis safe: https://safe.gnosis.io), there are currently more users with EOA (Externally Owned Account) based wallet and this might be the case for a while as there is an inherent cost to smart contract wallet. Native meta transactions also allow new tokens to be used without requiring generic relayer support on the part of the smart contract wallet. They thus offer native support for meta-transactions without any requirement from the users except to have a private key and enough tokens to pay for the gas.

As mentioned, Native Meta Transactions are great for applications whose users do not necessarily own or even know about ether. This is also useful for applications that want to distribute their tokens to new non-crypto users. Indeed, with such meta-transactions they can then start interacting in ethereum without having to think about ether. Application with the support of web3 wallet and browsers can then provide a less confusing experience where users can simply operate on one currency, at least until their horizon expands.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

For operability and the ability for wallet to display a more meaningful UI the following need to be defined:

  1. a signed message format that include all the required information for performing a meta transaction and protecting the user
  2. a public getter on the meta transaction processor smart Contract for the current nonce
  3. a way to verifiy the meta transaction signer.

Message format

The proposal is using a message format based on ERC712 so that wallet that support ERC712 but do not support the proposal described here can still offer an approval display showing all the information albeit in a less than ideal presentation.

Here is the proposed ERC712 message format :
typeHash = keccak256("ERC20MetaTransaction(address from,address to,address tokenContract,uint256 amount,bytes data,uint256 batchId,uint256 batchNonce,uint256 expiry,uint256 txGas,uint256 baseGas,uint256 tokenGasPrice,address relayer)");

The meaning of each field is as follow:

  • from: the account from which the meta-transaction is executed for and from which the token will be deduced. It MUST be either equal to the resulting message signer or have been given execution rights to the signer (via ERC1271 or ERC1654)
  • to: the target that will receive the token amount (if any, see below). If it is a contract's address the data(see below) passed will be executed on it.
  • tokenContract: token address that will be used for relayer payment and the amount field if non-zero. This allows the standard to work for both per-token metatx implementation and general implementation that would support multiple token.
  • amount: the amount in token to be sent to to
  • data: the bytes to be executed at to (if empty, only a transfer will be executed)
  • batchId: Nonces are 2 dimensional, the batchId part can be randomly generated so that meta-transaction that have no prior requirement can be included without waiting for previous meta-tx
  • batchNonce: the second part of the nonce, is the batchNonce, that allow a user to batch multiple meta-transaction and ensure they get included in order
  • expiry: the time limit at which the meta-transaction must be executed. If that meta-tx do not get executed in time, it reverts at the expense of the relayer.
  • txGas: the exact amount of gas to be used to execute the meta-transaction. This field also prevents attacks that could happen if the logic of the call relies on gas provided for whatever reason. (note though that the total cost of the transaction executing the meta-transaction will obviously have a higher cost)
  • baseGas: As meta tx has extra operation to be performed on top of calling the receiving address, this value determine the minimum gas the relayer will be paid on top. This is thus independent of opcode pricing and relayer are free to reject meta tx with a too low baseGas
  • tokenGasPrice the gasPrice set in token, (the relayer will decide to accept meta-transaction or not depending on the value of such).
  • relayer (optionally set to zero) used to protect the chosen relayer from front running attacks where the intended executor would lose its gas by someone inserting the transaction before. Letting it to be zero (acts as a wildcard) could still be worthwhile for situations where the users would benefit of having different relayers. This is out of scope of this proposal to define how such relayers network would work together.

batch and nonce

In order for the wallet or application to request a valid meta transaction it needs to be able to know the current nonce

The token contract MUST implement a getter for the current nonce as follow:

function meta_nonce(address from, uint256 batchId) external view returns(uint256);

Nonces work similarly to normal ethereum transactions: a transaction can only be executed if it matches the last nonce + 1, and once a transaction has occurred, the lastNonce will be updated to the current one. This prevents transactions to be executed out of order or more than once.

But instead of being one-dimensional, each nonce is actually associated to a batchId. This is offer great flexibility to the user and allow them to batch meta-transaction together if so desried while still allowing them to submit other meta-tx in paralel.

expiry and EIP-1681

While a previous version of this standard was using a minGasPrice to ensure that the user meta-transaction get included at a minimum price, such field becomes unecessary if we have an expiry field that have also a more meaningful purpose.

One of the danger, minGasPrice were protecting against, was relayers that would include the tx at a very low gas price to get a higher profit. With an expiry field, the relayer has to ensure the gasPrice is adequate so that the meta-tx is included in time.

On the other hand, the relayer is now risking to submit the transaction just a bit too late. To solve this, we can rely on EIP-1681 that can ensure the relayers that if the cannot get included after the expiry time-limit.

execute transaction and receiver verification

When executing the meta-transaction the contract must then verify the signature and if the nonce matches as specified above.

Then for the case of ERC20 the implementation need to ensure that the first parameter of the call being made is equal to from. The receiver will thus be able to accept calls from the token by knowing that the first parameter is indeed the from and not some arbitrary address. This means only such receiver will be able to accept such meta transactions securely.

In order to do it, the receiver simply check if msg.sender is the meta transaction processor contract itself and if so can assume that first parameter is equal to the from specified as part of the meta transaction message,

Remember, such ERC20 meta transaction receiver need to have as first parameters the "sender" whose token will be deduced.

for example:

function anyMethod(address sender, ERC20 token, uint256 value) external {
  require(sender ==  msg.sender ||  msg.sender ==  <metaTransactionProcessor>, "sender has to be the actual sender or the meta transaction processor contract itself");
  require(value == price, "value != price");
  token.transferFrom(sender, address(this), value);
  balance += value;
  ...
}

The meta transaction processor will ensure the ERC20 token as the permitted allowance and

Balance checks

To protect from malicious user the relayers also need to ensure the user (from) has enough balance. While it is technically possible for the user to withdraw token just in time (between the meta-tx is send and mined), it is unlikely to happen since it is unlikely to benefit the user unless it wishes to cancel the last minute. They could achieve a similar feat anyway by publishing a different signed message with the same nonce (albeit at a higher gas cost than a simple transfer). This is a risk that need to be taken by the relayer.

Gas accounting and refund

The txGas set as part of the message represent the gas passed to the contract call made (the meta transaction). This ensure the signer that its call will be executed as intended with as many gas as it asked for. This means though that the total gas cost of the realyer's transaction will be higher than that.

As mentioned above, this is solved with baseGas parameter that can be updated if opcode pricing changes. While this might feel like yet another extra field, it is important to note that the alternative (not having it) is worse since either the smart contract hard code the extra gas or the relayer have to pay the cost.

Gas estimate

In order to estimate the txGas to use for the meta transaction, the meta transaction call data can be used. The behaviour will be identical. As such there is no need to expose an estimateGas function for that.

As for the extra gas required by the relayer, the whole meta transaction call can be estimated as usual.

Relayer cooperations

One possibility that remains a problem for relayers is that a user could submit the meta-tx message and signature to multiple relayer at once.
This possibility pose a problem to relayers as they run the risk that their tx get included after another.

With the addition of expiry, we could imagine a simple method to avoid such issues:

Every meta-tx that include both an expiry field and relayer field, can always be included in a block if it reaches before expiry. If the nonce is already used, the actual meta-tx is not executed, but the relayer is getting rewarded for the gas spent (+ baseGas).

This would ensure relayers that they get paid for the work they do, while still allowing users to choose which relayer they want. The expiry field would also allow.

Example Implementation

see https://github.com/wighawag/singleton-1776-meta-transaction/blob/master/contracts/src/GenericMetaTxProcessor.sol

wallet / browser

Web3 wallet and browsers MUST provide a meaningful interface when such signed message is being requested. They could for example show a similar UI to traditional ether transaction except it should clearly state the token being used as well as the other parameters. For the data field, they should also be able to use function signature registry to display the function being called and the arguments.

@wighawag wighawag changed the title Native Meta Transactions ERC1776 Native Meta Transactions Feb 25, 2019
@wighawag wighawag changed the title ERC1776 Native Meta Transactions ERC-1776 Native Meta Transactions Feb 25, 2019
@PhABC
Copy link
Contributor

PhABC commented Feb 26, 2019

Nice!

Two initial comments:

  1. I would personally pack the gas receipt arguments in the _data byte arrays, allowing people to omit these arguments if not used and do something like this.

  2. I would suggest specifying somewhere that a signer can't be 0x0. ecrecover() can return 0x0, so a token that implements native meta-tx methods could see people generate signature on the behalf of 0x0, which could lead to undesirable consequences if 0x0 is considered a "burn" address.

@austintgriffith
Copy link

^^ Such good input. @PhABC, is this similar to your "Meta20" work? Do you smell a collaboration or should these two remain separate?

@PhABC
Copy link
Contributor

PhABC commented Feb 26, 2019

Yes, it's very closely related to the MetaTX ERC20 wrapper indeed. Ideally the ERC-20 wrapper can comply with this standard and vice versa. For those wondering what Austin is referring to, here's the relevant repository: https://github.com/horizon-games/ERC20-meta-wrapper. The native metaTX token contract is here.

@wighawag
Copy link
Contributor Author

yes we could easily add support for ERC-1155. it should work as ERC-777. We could simply a third EIP712 message type definition with the same data

  • I would personally pack the gas receipt arguments in the _data byte arrays, allowing people to omit these arguments if not used and do something like this.

Currently the standard as it stands to not preocuppy itself with the function that execute the meta transaction. If it did, I would like to know more about the reasoning behind the optionality of the arguments : which one, what would be the default values / behavior in each case ?

Would the signature scheme need to change ?
It seems to me that this won't the case as we could have them set to zero values (like the relayer field).

  • I would suggest specifying somewhere that a signer can't be 0x0. ecrecover() can return 0x0, so a token that implements native meta-tx methods could see people generate signature on the behalf of 0x0, which could lead to undesirable consequences if 0x0 is considered a "burn" address.

Good point, I ll add that

@PhABC
Copy link
Contributor

PhABC commented Mar 1, 2019

I want to discuss the current data passed related to the "gas payment" first, perhaps how this data is encoded in function calls is a different topic indeed.

gasPrice:

I would personally remove gasPrice, because that's something the relayer can choose on their own and is therefore not needed. If the tokenGasPrice is not enough for current market fee, they just won't execute the transaction. Otherwise, they can have their own transaction fee model.

gasLimit:

To keep, this is important.

tokenGasPrice:

I would rename that to gasPrice since I would remove the former gasPrice

relayer:

Should leave in as well.

gasToken :

I would add a field such that the user can specify how they will pay for gas: In WETH? In DAI? In WBTC?

baseGas:

I was once pondering if we should add a baseGas value, which is a value that accounts for gas not tracked within the MetaTX call, but that still needs to be spent by relayer. For instance, these costs include 21000 transaction cost, gas per byte of CALLDATA (function arguments), etc. However, it seems like each contract could hardcode it's own base gasCost that it would add when doing the reimbursement. This is not perfect, but it would remove the need for an additional field. Another way to deal with this would be to increase the gasPrice such that it covers the cost of baseFee. All in all, it's possible to avoid needing a baseGas fee, but I was curious if other thought the simplicity of this additional field would be worth it.

In our native meta-tx, we are currently using this as a "gasReceipt" struct:

// Gas Receipt
struct GasReceipt {
  uint256 gasLimit;             // Max amount of gas that can be reimbursed
  uint256 baseGas;              // Base gas cost (includes things like 21k, CALLDATA size, etc.)
  uint256 gasPrice;             // Price denominated in token X per gas unit
  address feeToken;             // Token to pay for gas as `uint256(tokenAddress)`, where 0x0 is MetaETH
  address payable feeRecipient; // Address to send payment to
}

Will probably remove the baseGas field however.

@PhABC
Copy link
Contributor

PhABC commented Mar 1, 2019

I'm also thinking of whether we should add a general data field in there as well, which could be used for additional logic. For instance, perhaps one wants to pay the gasFee via an ERC-721, ERC-20 or ERC-1155 token, which isn't really possible with gasToken alone. Perhaps some contract would like to add the baseFee that we removed. I'm guessing most of the time it would be 0x0 however, but in my case I know that in one of my use case I need to specify whether the gasToken is ERC-20 or ERC-1155. I'm just not sure what the best way to handle this is.

@wighawag
Copy link
Contributor Author

wighawag commented Mar 4, 2019

Hi @PhABC
thanks for the feedback.

gasPrice::

I would personally remove gasPrice, because that's something the relayer can choose on their own and is therefore not needed. If the tokenGasPrice is not enough for current market fee, they just won't execute the transaction. Otherwise, they can have their own transaction fee model.

I made this decision to include the actual ethereum gasPrice as a message field for 2 reasons :

  1. Unless the gasPrice is specified by the user, there is no incentive for the relayer to not reduce the gasPrice. If the user increase the token gas price it can't be sure the relayer will increase the gasPrice use for the transaction itself. gasPrice should be included so the signer is in control of the transaction speed.

  2. We should aim for full determinisim. gasPrice being a variable decided by the transaction signer, it can affect the logic of the call being made. While it is true that gasPrice is not usually used it could be.

gasLimit:

To keep, this is important.

Indeed, but unless it was clear enough, such gasLimit represent the amount of gas passed into the call (executed by the meta-transaction as part of the transaction executed by the relayer) so that the call is deterministic based solely on the input of the signer.

As such the meta-transaction implementation need to ensure enough gas is passed to the transaction so that when calling _to.call.gas(_gasLimit)(_data); the exact _gasLimit is passed in (solidity does not enforce this by itself and less could be passed in).
Also anything executed after that (gas refund tx...) need to have enough gas so the transaction does not run out of gas at the expense of the relayer. This extra gas is what baseGas would need to cover (see below)

gasToken :

I would add a field such that the user can specify how they will pay for gas: In WETH? In DAI? In WBTC?

Interesting, I actually did not thought about that because for our use case we did not plan to support such.
Thinking about it, thought I am not sure it should be included as part of this standard. It feels like it belongs to a different one where wallet can let user choose the token they wish to use to operate the transaction. This brings more questions and to me this should not be the responsibility of tokens to support it. After all we would only need one canonical implementation ( a la 820) and token that support native meta transaction would not need to implement it then as any application could use the canonical implementation instead (which is more likely to be already approved).

At the same time, the change is minimal and for contract that do not want to support different token, they would not even need to add that to the calldata.

Still, how wallet would know whether other tokens (non-native) are supported ?

Maybe the field could still be added but wallet should not think that they can let the user chose any token. So when an EIP712 signature is requested with the field gasToken specified, the wallet do not let the user change. On the other hand when it is not specified, it request the user to choose one.

This forbids the ability to suggest a default one though, unless the standard evolve to be a different RPC call (I liked the idea of being solely a EIP712 message definition, for several reason, one being backward compatibility).

So unless we can clarify the idea , my current stance is to not include it as part of this standard, that focus on native meta transaction.

baseGas:

I was once pondering if we should add a baseGas value, which is a value that accounts for gas not tracked within the MetaTX call, but that still needs to be spent by relayer. For instance, these costs include 21000 transaction cost, gas per byte of CALLDATA (function arguments), etc. However, it seems like each contract could hardcode it's own base gasCost that it would add when doing the reimbursement. This is not perfect, but it would remove the need for an additional field.

baseGas is indeed an important topic and that is what I was alluding to by the "extra gas required by the relayer". One thing is certain is that wallet/browser will need a way to know that value to display it to the user. I was at some point thinking of adding a mandatory function like baseGas() returns (uint256) that would let the wallet know what baseGas will be needed. We would need one for ERC-20 and one for ERC-777 , etc....

By the way, as mentioned about gasLimit, baseGas is not only the 21000 + calldata etc, but also the gas cost of gas refund transfer and other as the gasLimit specified in the signed message is about the gasLimit used in the call itself.

Another way to deal with this would be to increase the gasPrice such that it covers the cost of baseFee.

This is not that simple. a user could simply specify a high gasLimit to make believe the relayer that its margin will cover the extra fee, while the actual transaction consume very little.

All in all, it's possible to avoid needing a baseGas fee, but I was curious if other thought the simplicity of this additional field would be worth it.

Instead of including it in the message, I was thinking that the wallet should estimate gas of the relayer transaction to predict the actual amount of token being refunded. This would require us to define the abi signature of such function and is not ideal in my opinion as it gives you the total gas cost and can't predict the actual gas used when executed for real.

I currently like to have it included as part of the message to sign to avoid ambiguity and ensure the data is the same for everybody (wallet, user, relayer).

This does not allow the wallet/browser to figure it by itself though. But is that even desired ?

To summarize our 3 options are :

  1. adding meta_erc20_baseGas meta_erc777_baseGas
  2. adding baseGas field
  3. using estimateGas

1 and 2 together would :

  • allows wallet to display the value to the user
  • allows the wallet to compute it itself for generic application that might not know the value required
  • allows wallet to display the value when supporting only erc-712 and not ERC-1776

Having said that, I am not sure whether wallet need to be able to figure the value by themselves. At the end of the day, a relayer would be needed and such relayer would not accept random tokens. As such the relayer would be able to know what baseGas to use and the wallet would be able to know without having the smart contract to publicly tell.

As such the baseGas message field might be all what we need.

Note though that if baseGas is a fixed value (worst case value) the smart contract can avoid requiring it to be passed as calldata as such there is not much cost of baseGas being included in the message.

I'm also thinking of whether we should add a general data field in there as well, which could be used for additional logic. For instance, perhaps one wants to pay the gasFee via an ERC-721, ERC-20 or ERC-1155 token, which isn't really possible with gasToken alone. Perhaps some contract would like to add the baseFee that we removed. I'm guessing most of the time it would be 0x0 however, but in my case I know that in one of my use case I need to specify whether the gasToken is ERC-20 or ERC-1155. I'm just not sure what the best way to handle this is.

Hmm, if you only need to differentiate between ERC-20 and ERC-1155 you could do as the current draft do in regard to ERC777 and ERC20 : 2 different signature scheme with the same data

For instance, perhaps one wants to pay the gasFee via an ERC-721, ERC-20 or ERC-1155 token, which isn't really possible with gasToken alone.

I would say, let's focus on something concrete we have now. We could always create a new standard for wallet to support ERC-721... (as an extension)

@PhABC
Copy link
Contributor

PhABC commented Mar 5, 2019

Unless the gasPrice is specified by the user, there is no incentive for the relayer to not reduce the gasPrice. If the user increase the token gas price it can't be sure the relayer will increase the gasPrice use for the transaction itself. gasPrice should be included so the signer is in control of the transaction speed.

This doesn't really matter because the relayer can always wait before submitting the tx. If multiple relayers can execute, then this is no problem and if the relayer is enforced by you, you can change relayer if the service they provide isn't good. Think of exchanges like Binance that will withdraw your funds with a gas price of 50 Gwei to ensure good customer service. It also gives no room to the relayer to adjust the gas price if market changes, which reduces the chances that the tx will be submitted.

It feels like it belongs to a different one where wallet can let user choose the token they wish to use to operate the transaction.

Only supporting the current token makes the MetaTX market less efficient however, since now relayers are "forced" to accept the given token if they want to serve their users, instead of only accepting DAI or WBTC for instance. This adds complexity to the relayer and could fragment relayers. It's likely that smaller/new tokens would not be able to find reliable relayers as the risk for the relayers is too large. However, if users could specify DAI (or other common currency), then all MetaTX for all tokens with native metaTX are equivalent for relayers.

Still, how wallet would know whether other tokens (non-native) are supported?

Wallet just need to check token balances and check the approvals. Wallet could by default use the native token of the contract, which offers the same experience as your current proposal, but with the added flexibility that one could use another token to pay for gas.

As such the meta-transaction implementation need to ensure enough gas is passed to the transaction so that when calling _to.call.gas(_gasLimit)(_data); the exact _gasLimit is passed in (solidity does not enforce this by itself and less could be passed in).

Here again I would not enforce this per say and leave it to the relayer, where the contract reimburses up to the gas limit. A relayer will therefore have no incentive to exceed and the transaction will still go through if the relayer undervalues the amount of gas required. In your proposal, if gasLimit becomes insufficient, the tx is not-executable anymore, while in what i'm proposing, so long as there is a margin for profit, the tx is executable even if gasLimit is too low. Here again I personally think the flexibility improves the user experience and the probability that the tx will be executed correctly.

This is not that simple. a user could simply specify a high gasLimit to make believe the relayer that its margin will cover the extra fee, while the actual transaction consume very little.

Miners need to make the same type of decisions, where if a transaction gasLimit can be much higher than what is actually consumed. I don't think this will be a problem in practice with good enough tools.

Note though that if baseGas is a fixed value (worst case value) the smart contract can avoid requiring it to be passed as calldata as such there is not much cost of baseGas being included in the message.

That sounds reasonable. The simplicity of including a baseGas field for most implementations seems indeed worth it.

I would say, let's focus on something concrete we have now. We could always create a new standard for wallet to support ERC-721... (as an extension)

Without gasToken the current standard only seem to support ERC-20, but not ERC-721 nor ERC-1155, which is a mistake imo. I'm not sure what is the easiest way to generalize the standard however.

@wighawag
Copy link
Contributor Author

wighawag commented Mar 7, 2019

This doesn't really matter because the relayer can always wait before submitting the tx. If multiple relayers can execute, then this is no problem and if the relayer is enforced by you, you can change relayer if the service they provide isn't good. Think of exchanges like Binance that will withdraw your funds with a gas price of 50 Gwei to ensure good customer service. It also gives no room to the relayer to adjust the gas price if market changes, which reduces the chances that the tx will be submitted.

I am not convinced:

For the case when there is many relayers, indeed, the gasPrice used will tend towards the ethereum gasPrice equivalent to the tokenGasPrice value but then this is mostly equivalent to letting the user dictate the gasPrice.

If there is only one relayer (whether it was the only specified or not) on the other hand, the user becomes at the mercy of its decision. That relayer is incentivised to get paid more than what it is providing and so the user will wait longer than it should for its transaction. While we could argue that's the nature of such small market, my point is that the tokenGasPrice can still increase to offer incentive for such relayer to relay, on the other hand the actual ethereum gasPrice should be in the hand of the signer, so the relayer can't malicious grab the price and provide a slow experience. In such situation, the user can't simply increase the tokenGasPrice as he has no guarantee the relayer will increase teh gasPrice in exchange.
If the relayer is not broadcasting the transaction, the user can simply look for another one. This is the same in both case.

In other words, the only advantage a gasPrice determined by the relayer bring to the user is when the relayer (by potentially losing money, at least its margin) increase the gasPrice. The other way could only be to the detriment of the user.

As such for gas market price changes, which is only a problem for the user when the gas price is not sufficient anymore. We could allow the relayer to increase it but never make it lower than specified by the user.

This conflict though with the other issue arising from letting the relayer set the gas price: the user is not in control of the execution data anymore. In principle, I think it is important that the relayer should not have any say to the data being sent as part of the transaction. gasPrice, is such data.

I agree though that we could specify the need to not use gasPrice in receiver call as part of the standard. Personally I do not see any use case that would be restricted but is that a safe assumption?

if that is safe, I would agree to let the relayer increase the gasPrice but not decrease it.

By the way, there is another potential side benefit of a fixed gasPrice: it might help a network of relayers to coordinate since there would be no easy way for a late relayer to grab the price by submiting a transaction later.

Only supporting the current token makes the MetaTX market less efficient however, since now relayers are "forced" to accept the given token if they want to serve their users, instead of only accepting DAI or WBTC for instance. This adds complexity to the relayer and could fragment relayers. It's likely that smaller/new tokens would not be able to find reliable relayers as the risk for the relayers is too large. However, if users could specify DAI (or other common currency), then all MetaTX for all tokens with native metaTX are equivalent for relayers.

I think you convinced me on this one, not from this response specifically but from the general idea of generic meta transaction. This has some implications though and I am still ensure if they all fine. For example the current draft allow the transaction to specify a token amount to send to the destination. If we want to support that, we probably want to add yet another field tokenToSend but this also implies that we need a different way to support ERC-20 tokens. Approval is not transitive and as such the sender will need to either approve the receiver before hand (not great user experience) or the contract will need to transfer token first and let the receiver know of the token received (like a erc-20 tokensReceiced hook) and revert the transfer if the call failed (with all the gas cost it implies).

One thing worth noting here is that as I said we would only need one smart contract implementation for all native meta transaction, then. The current proposal could still focus on the message format, each field's role, receivers expectation and wallet support though.

Still, how wallet would know whether other tokens (non-native) are supported?
Wallet just need to check token balances and check the approvals. Wallet could by default use the native token of the contract, which offers the same experience as your current proposal, but with the added flexibility that one could use another token to pay for gas.

The question was about how the wallet know that the token supporting meta transaction support a specific token, not that the user approved the token. But now that I think a bit more clearly about such generic meta transaction contract, I can see that there is not reason the implementation limit which tokens are supported, this is up to the receiver (relayer and destination) to accept them or not.

Here again I would not enforce this per say and leave it to the relayer, where the contract reimburses up to the gas limit. A relayer will therefore have no incentive to exceed and the transaction will still go through if the relayer undervalues the amount of gas required.

Again by principle, the relayer should not have any say in the data passed to the call and this is even more important for gas than gasPrice.

But more importantly, if the relayer decide what amount of gas is passed to the call, when it fails, who is to pay the meta transaction? This should not be the user if the call failed due to the lack of gas.

In your proposal, if gasLimit becomes insufficient, the tx is not-executable anymore, while in what i'm proposing, so long as there is a margin for profit, the tx is executable even if gasLimit is too low

The only time the gasLimit would be too low in my proposal is when the user miscalculate the cost. It can't become insuficient.
To be more clear, in the current proposal, gasLimit is not the gasLimit the relayer need to send, it is the gaslimit sent as part of the call. It is up to the relayer to provide enough gas to reach the call executing with enough gas to complete the transaction. If the relayer do not, it lose its gas.

As such if ever the user sign a gasLimit too low, the relayer (provided it sent enough gas for the whole tx) will be rewarded for the execution anyway (and the meta nonce increased) since we can calculate that it was the user's mistake not the relayer.

Miners need to make the same type of decisions, where if a transaction gasLimit can be much higher than what is actually consumed. I don't think this will be a problem in practice with good enough tools.

That's not a fair comparison. In the miner case, there is not extra margin decided by the miner The miner simply knows that they will be paid x for y gas.

Without gasToken the current standard only seem to support ERC-20, but not ERC-721 nor ERC-1155, which is a mistake imo. I'm not sure what is the easiest way to generalize the standard however.

As said about ERC1155 we can easily add ERC712 specific message preamble in the TYPEHASH.
We could standardize it like <ERCXXX>MetaTransaction(... and the gasToken field would define the id.

I' ll add that to the proposal.

We could also simply add a string field instead that state the ERC to be used like : tokenERC. I personally prefers the preamble but I would not mind changing it.

@sboucherit
Copy link

sboucherit commented Apr 24, 2019

Hello. Can we compare what has been done here with EIP1613/Tabookey ( https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1613.md ) ? Can we combine both ? (Native MetaTX in your token contract + you use Tabookey's relay)

@wighawag
Copy link
Contributor Author

Updated the draft with latest improvement.

Most notably, the addition of baseGas which allows implementation to be fully independent of opcode pricing

@wighawag
Copy link
Contributor Author

Just made a new update, that include the following:

  1. the use of an expiry time replace the need for minTxGas and allow for a more friendly experience as the user can be sure that after a certain time, the tx has been canceled. Plus with EIP-1681, this will not affect relayer.

  2. I also updated the message format with a 2 dimensional nonce (batchId, batchNonce). This offers great flexibility to the user when it comes to submitting meta-tx in different applications.

I also added a paragraph on the use of expiry and relayer field to protect relayer from other relayers.

@lirazsiri
Copy link
Contributor

lirazsiri commented Jan 22, 2020

Hello. Can we compare what has been done here with EIP1613/Tabookey ( https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1613.md ) ? Can we combine both ? (Native MetaTX in your token contract + you use Tabookey's relay)

There seems to be a bit of a misunderstanding here as the Gas Station Network (EIP 1613) has always supported native meta transactions signed by externally owned accounts. From day one. The differences between the GSN and EIP 1776 is that:

  • The GSN includes a robust, decentralized solution to the decentralized relay discovery problem (the on-chain RelayHub registry)
  • The GSN supports relaying meta-transactions on behalf of externally owned accounts AND account contracts.
  • The GSN contracts that handle meta transations have passed security audits and have been live on mainnet since Aug 2019.
  • The GSN has been integrated into multiple middleware layers including ZeppelinOS

An update to the GSN is in progress that adds:

  • EIP 712 support so that meta transactions are encoded in a standard format.
  • Batched transactions
  • Improved native support for unmodified destination contracts already live on mainnet. This can already be accomplished with the current version of the GSN but the implementation is cumbersome.

@tomarsachin2271
Copy link
Contributor

EIP712 format is created to make it easy for user to understand and see what he is signing which is way better than signing a hexadecimal string. And as more and more DApps will be including meta transactions and blockchain mainstream adoption increases, with the current fields in the format given in this EIP, I still feel a normal end-user would not know what is happening in the meta transaction.

So a description field in the format should be added, that'll include the description of transaction in a human readable form.
Even though standard transactions like token transfer or approval or native value transfer can be interpreted by the wallet but its not possible to interpret every general contract interaction, by the wallet.

@wighawag
Copy link
Contributor Author

@tomarsachin2271 thanks for your feedback.

The goal of this standard is so that when it get accepted, wallets will be able to show meaningful information, instead of the raw EIP-712 message. They would also let the user specify/change some of the parameter, like expiryTime.

@tina1998612
Copy link

Can someone point me out the main differences between this and ERC865?

I looked into the main function that recovers the sender from the signature and then let a third party submit the transaction while paying for the gas fee and receives tokens as reward. Looks the same to me.

Thanks:)

@wighawag
Copy link
Contributor Author

wighawag commented Dec 9, 2020

@tina1998612 the main difference is that EIP-1776 support any call, while ERC865 only support erc20 transfer

@github-actions
Copy link

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

@github-actions github-actions bot added the stale label Nov 20, 2021
@github-actions
Copy link

github-actions bot commented Dec 4, 2021

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants
@wighawag @lirazsiri @austintgriffith @PhABC @tina1998612 @tomarsachin2271 @sboucherit and others