Lets discuss Metamask's hackathon on generalized meta transactions

IMHO:

  • A meta-transaction should not be considered as executed (nonce increase, …) unless the included call succeded. There is either enough gas, and the meta-tx succeded or not enough and then it fails and can be replayed
  • If the meta-tx targets a contract that has try-catch behaviour, it’s up to this contract to check that the try has enough gas and revert otherwize.
  • The only personne that needs to protect itself against reverted transaction is the relayer. It wants to ensure the repayer pay regardless of the outcome.
  • It’s up to the relayer to say how much gas he wants the relayer to include in the transaction.

Therefore, I believe gas is an agreement between relayer and repayer and part of the relaying infrastructure. I believe it is not par of the generic meta-tx format.

If the relayer and repayer uses on-chain repayment, then they have to nest meta-tx and include gas settings in the body of the wrapping meta-tx

1 Like

A meta-transaction should not be considered as executed (nonce increase, …) unless the included call succeded. There is either enough gas, and the meta-tx succeded or not enough and then it fails and can be replayed

That’s a valid approach indeed and was what the first metatx implementation did.

I’ll then add that as another dimension where metatx implementation differs, let’s call it
E) meta tx failure responsibility

I am not sure if that is great option to give relayer full responsibility though as this means it harder for them to decide whether to submit the tx or not. They’ll basically be suffering from state changes that are potentially unrelated to their decision.
Imagine a trade tx, why should the relayer suffer from the trade failing because another tx came through first (maybe even in the same block)

In the case where txGas is included and the reward is given even if the metatx fails, the relayer has just to check the balance of the token and can make a more accurate prediction.
The relayer will still suffer in some case but this is less likely and would be more predictable.

The only personne that needs to protect itself against reverted transaction is the relayer. It wants to ensure the repayer pay regardless of the outcome.

Exactly

It’s up to the relayer to say how much gas he wants the relayer to include in the transaction.

Yes obviously, only the relayer will dictate how much gas it given in its own tx, and this is not a problem indeed if we assume that relayer takes all responsibility for meta tx failure. But if we consider (like normal tx) that user take responsibility of their actions, then we need to protect them from a relayer putting not enough gas, hence the need for ‘txGas’ in that case.

To me the only benefit for giving full responsibility to relayers is simplicity and this is indeed a valid reason but relayers’ protection from unpredictable outcome is in my opinion quite important

Why dont use EIP-1344 as application parameter of the signature? Its like in Ethereum then.

I see the problem you are trying to solve, your approach could work but it open the system to unknowns, as the relayer can decide too much on the parameters, and you are trying to make the incentives on the relayer to execute the transaction properly, giving an overhead on the decision making of relayer.

I disagree on this, because in ethereum when you dont provide enough gas, the transaction fail and the nonce still incrases, and a new signature is required. Why here it should be different?

If the meta-tx targets a contract that has try-catch behaviour, it’s up to this contract to check that the try has enough gas and revert otherwize.

Why it should care? It should do the agreed and executing the call with the provided gas limit, what happens inside does not matters. Also, there is no way to verify what outcome was intended.

The only personne that needs to protect itself against reverted transaction is the relayer. It wants to ensure the repayer pay regardless of the outcome.

I disagree, both parties should care only about their part of the transaction. The signer need to care how much the transaction they trying to execute cost (gasLimit) and how much they are willing to pay for each unit of gas used (gasPrice) in the offered token (gasToken). Meanwhile the Relayer should only provide enough gas to make the transaction execute + pay the gasToken.

Just that I did not want to bother with proper chainId handling in the demo.

To what are you referring to here ?

I disagree, both parties should care only about their part of the transaction. The signer need to care how much the transaction they trying to execute cost (gasLimit) and how much they are willing to pay for each unit of gas used (gasPrice) in the offered token (gasToken). Meanwhile the Relayer should only provide enough gas to make the transaction execute + pay the gasToken.

I agree too but when I replied “exactly” to the same text, I assumed that it was obvious that the signer will indeed take responsibility on the parameter it signs. The point was that given the signed message, it is the relayer that take the risk of the tx (not the metatx) failing.

When a eth transaction fails, this includes a cost to the sender (that protect the miners). The nonce gets increased because this behaviour should not be replayed (othewise a miner could include a failling transaction many times and drain your wallet). This is a protection for the “transaction payment” not being duplicated, I don’t thing it has anything to do with the “transaction call” not being duplicated (it reverts and it could be interresting to see it being repeated until it succeeds).

In (my vision of) a metatransaction, the sender doesn’t care about the payment. He just wants a transaction to successed. The economic of this transaction succeeding is a an agreement between the relayer and the repayer, which is to be implemented in the repaying layer at an app level, not into the meta-tx validation protocol. Again, I believe meta-transaction validation and meta-transaction relaying repayment are 2 distinct layers.

I’ve done an actual implementation that build gas repayment economic using my generic meta-transaction (that does not include gas). This can be used as proxy to reparate clearly relayer, repayer and user:
The repayment contract: GMTX/example/solidity/contracts/GasRepayer.sol at master · Amxx/GMTX · GitHub
Example of usage: GMTX/example/solidity/test/GasRepayer.js at master · Amxx/GMTX · GitHub

  • A metastraction only succeed if the call included does not revert
  • Repayment is handled by a separate application (powered by meta-transaction)
  • A repayment meta-transaction succeedes event if the nested call fails, assuming enough gas was provided, and can thus not be replayed
  • If the repayment meta-transaction did not include enough gas, if is considered a “succesfull faillure” and the included user meta-transaction isn’t consumed → you can try to replay it with more gas in a new repayment meta-transaction.

In this context, relayer only consider relaying metatransaction that target the relayAndRepay function of the GasRepayer contract. This can be done by checking the target and the first 4 bytes of the meta-transaction. If the sender has a sufficient balance, then it is guarantied to be reimbursed.
The repayer just takes any meta-transaction from any user, and just encapsulates it into a meta-transaction that meets this pattern.
→ Relayer is assured to get payed
→ Repayer pays any gas usage
→ Users transaction isn’t consumed unless the Repayer actually included enough gas.

In the Web2 world, when you do an operation you are unlikelly to care about of even be aware of its processing cost. I believe it should be the same for meta-transaction. The end user who signs it doesn’t care.
The relayer does care about the relaying cost! Depending on the situation it’s either going to accept it as a lost, ask for repayment in fiat offchain, or ask for a repayment onchain. In that last case there should be a protocole, including signatures, that ensures relayer and repayer are on the same page. But again, unless the user actually the repayer, it should not care about gas. Only the repayer does. Thus, the repaying protocole shouldn’t be part of the meta-transaction signature/verification/replay-protection/ordering.

After re-reading my code, I’ve partially change my mind. I believe you are right for the gasLimit of a meta-tx which should be set by the signer.
My mind didn’t change however regarding the gasPrice (cf my separation of relayer, repayer and user)

I updated my code consequently

1 Like

When I say that the signer have to care, it dont mean that any one here is guess gas limits… But that the user wallet (or a service) would do that calculation, and estimate the cost, which user only have to approve. A wallet could be automated to dont even ask for approval and do all in backend, but I wouldnt use something like that because I care about what Im signing as costs money.

I understand your concept, and technically I agree it would work, but I think that whats safer is to enforce the correct parameters from the beginning, not to allow an third party set then for me.
If it was not possible to calculate those parameters in the user wallet, I would agree with your vision, but as is possible to calculate it, then Ill do it.

The only possible advantage would be if I am signing without a blockchain signed (where the prediction is not possible), right now, in ethereum the wallet can only give a very high gasLimit that “will guarantee to work”.

IMO it is better for the standard to allow the user to pay if desired instead of having yet another system for it. After all, this is not so much to add and having it, does not preclude using yet another system later if it turns ou to be inadequate.

I would also like to add than having token support allow for more than just paying for the gas. My solution actually also include token transfer in that it can approve the recipient, allowing recipient to receive token without prior approval, a current pain in web3 user experience.

In your system if the user wanted to be the repayer, which in my opinion is an obvious case, (as even if there were a third party repayer, it might want to charge the users tokens, maybe to implement some sort of rate-limit), it would have to make 2 meta-transaction, one inside the other.

By the way, your code that specify the gas amount for the inner call is not enough. The gas amount specified is just a maximum value and does not error out if there is not enough gas to fulfil it. This also means this does not ensure the gas provided is that amount. So unless you do extra check like my implementation do, you run the risk of the relayer making the inner tx fails even if it was signed with the correct gas amount. If a repayer is involved, this means it can lose money while not fulfilling the signer message.

Another important aspect of gas consumption that will affect the repayer is that you also need to account for the extra gas require to process the meta-tx itlesf in order to be accurate. You do with a hardcoded value FLAT_GAS_USAGE

For that EIP-1776 propose the baseGas parameter that allow the system to remains independent of opcode pricing. Relayer can simply reject meta-tx that do not provide enough baseGas to ensure they can compute the price of the meta-tx accurately.

By the way EIP-1776 focus on the message format, so that multiple implementation are possible. This means that if the refunding mechansim (gas price) is specified as part of the format, your solution could simply ignore it.

That’s why I believe at this stage we should focus on the message format so that we can experiment with multiple implementation in parallel and not block ourselves in one.

We still need to agree on some other thing though, the thing that will affect the receivers, like how the signer is identified (whether through data appended to the call or through reusing the first parameter of functions)…

@Amxx , @3esmit, @wighawag,

I have proposed msg.signer in the solidity-dev group and it looks like we could get good support there. @chriseth was quite positive about the proposal.

All in all it means we could get some Meta-Tx functionality bootstrapped in Solidity.
msg.signer is the 1st candidate, but probably we need more.

Could you, please, review @chriseth’s answer and his question in gitter? It is about how we see it implemented. As I understood him, we could (should?) progress quite fast here.

1 Like

Sorry, I fear you misunderstood my answer in gitter. By “keeping flexible” I meant I only want to add features to the language that are necessary and which do not include “arbitrary” decisions that might change later.

Is this feature something that can be done without the help of the compiler?

@Ethernian I already asked for this in this issue https://github.com/ethereum/solidity/issues/8160
Join there, lets make some noise xD

@chriseth,
I really beg your pardon, if I forced you to answer here to rewind my optimism. :slight_smile:
Thank you for clarifications.

There are different implementation of general Meta-Tx made by different people. A small discussion should be started to understand if the functionality can be implemented as a function.

@3esmit, I aggre to the gas limit being in the metatransaction, and hidden by the wallet (see my code here)

I’m worried that if this is part of the standard, some smartcontract that do not have access to user’s assets would have difficulties implementing the required feature.

Usually, in solidity, msg.xxx or tx.xxx are relevant to stuff that is accessible through evm opcodes / in the evm stack. I could make it work … but in the end the best solution would be to have meta-tx nativelly supported by EVM. Similarly to how EIP155 modified the transaction siganture to account for additional data (chainId) we could have a new EIP that propose some signature on top of classic transactions:

  • If the transaction has the EIP155 format, process it as usual
  • If the transaction has an additional (relaying) signature on top (EIPxxx format), use the additional signature to retreive the relayer address.

We could then have
→ msg.sender = address of the one paying the gas
→ msg.signer = address of the signer (equal to msg.sender for EIP155, equal to the inner signature for EIPxxx)

or
→ msg.sender = address of the the inner transaction
→ msg.relayer = address of the one paying the gas (different in EIPxxx, equal to msg.sender in EIP155)

Oh, but in my solution this only need to be implemented by the singleton meta tx processor. receiver do not need to deal with any of that. They just need to trust the singleton contract to do its job.

That would be great to have something like that to reduce the code complexity of supporting meta-tx.
I would not call it msg.signer though as this can only be trusted to be the signer if the msg.sender is a trusted contract that verify the signature.

But having a msg.tailData or something could be useful, though it is not that big improvement I guess.

unsure we understand each other.

I proposed, that a solidity bootstrap code should check if there is a trailing signature signing the msg.data, then set the msg.sender accordingly and then and the call target function as usual. Who is the msg.sender is not important for this.

How it corresponds to trusted contract you mentioned?