Lets discuss Metamask's hackathon on generalized meta transactions

Philosophical:
To be honest, I think we are trying to work around EVM limitations that should be lifted not in this way but in some EVM HardFork Upgrade.

There is a damn old Account Abstraction proposal, that is aimed to solve all problems like this.

If there are still hard problems preventing implementation, probably a smaller upgrade separating the message signer and the gas spender could be possible?

Ethereum gave up the Bitcoin-level immutability for the ability to make breaking protocol upgrades for high speed development - and doesn’t use it. That’s bad!

Thats a really good point. We might be trying to build a dirty ERC that badly solves something that an EIP could do much better.

1 Like

Well, this is really similar to what I do. I have an entry point that receives meta-transaction as struct, which then calls itself with the calldata included in the meta-tx AND appends the sender at the end of the calldata.

From other function you just run as usual, except that if the calls comes from yourself you overwrite the msg.sender with the 20 last bytes of the calldata. If calling yourself is something you do under normal operation then you just use a mirror to distinguish relayed calls from “normal” self calls.

1 Like

Hey @Amxx thanks for bringing the discussion here and focusing on collaboration.

I have actually proposed an EIP for native meta transaction a while ago but for some reason it there was not much attention to it

Have a look here: https://github.com/ethereum/EIPs/issues/1776

It is actualy already live in sandbox.game but we did not enabled our relayer yet.

It solves many of the intricacies of metattx while remaining flexible: it can be used per token or in a general processor.

I am focusing for the Hackathon on the later.

It solves the msg.sender thing in a very simple manner, the first parameter is ensures to be the metattx signer.this make it compatible with many erc that have from function (erc20, erc721, erc1155)

Once I find some time I ll have a look at what you did.

Really looking forward to the outcome of this!

1 Like

Hi all,

I made a new implementation of EIP-1776 for the hackathon and you can play with it on Gorli or Rinkeby) here : https://metatx.eth.link
it is also obviously accessible via metatx.eth but because metamask will try to resolve ENS name on the network it is connected to (and that I did not register the name on Rinkeby or Gorli) it will fails to resolve it unless you go first on the mainnet.

The implementation is a Singleton proxy contract that implement all the meta-tx intricacies, including relayer repayment and forward the call to the destination. It is compatible with EIP-1776 (which I modified slightly for some improvements).

As a result, the requirement for metatx recipient is just kept to a very minimum. They simply need to check msg.sender to be the address of the singleton. No much need for a base class, except maybe for modifiers.

You can find the code here : https://github.com/wighawag/singleton-1776-meta-transaction

@Amxx I had a look at your proposal, I like the fact that it explores another method of implementation, that of the recipient being the meta-tx processor. What you call, a “no-proxy” implementation.

I have thus found at least 4 dimensions on which different meta-tx implementation differentiate themselves.

A) Type of implementation

First of all, It seems we have thus so far the following type of meta-tx implementation

  1. Account-contract Based (a la Gnosis Safe, etc…) where recipient do not need any modification but that require user to get a deployed account contract.
  2. Singleton Proxy where the recipient simply need to check for the singleton address and where all the logic of metatx is implemented in the singleton. It can support charging with tokens and even provide token payments
  3. Token Proxy where the recipient simply need to check for the token address and where all the logic of metatx is implemented in the token. This is the approach originally taken by @austingriffith in “Native Meta Transaction”. It is usually limited to be used for meta-tx to be paid in the specific token. Relayer would then need to trust each token for repayment.
  4. No Proxy where the recipient is the meta-tx processor and where all the logic get implemented. While it can support relayer repayment, relayer would have to somehow trust each recipient implementation.
  5. Create2 based? I personally did not explore these much but this could be used to provide a mechanism by which user are still using EOA to sign metatx (same like account-contract based) but have an account-contract created on-demand (maybe when the first metatx is executed, in which case the relayer could be paying the cost in ether in exchange of some tokens).

Note that EIP-1776 is agnostic to the type of implementation.

B) Relayer refund

Another differentiation is the ability of relayer to get paid.
In my opinion, It is such an important feature for relayers that we should ensure it is at least possible to implement it on top, if not already present.
In that regard one thing that becomes important as soon as a relayer get paid, is that there is a mechanism to ensure the relayer cannot make the meta-tx fails. hence the need for txGas in EIP-1776.

Another important EIP that would help here is EIP-1930

C) Token Transfer / Approval

While relayer-refund can be on its own, I found that it is trivial to also add the ability for meta-transaction processors to support transfering tokens to recipient.
This is a very powerful feature as it remove the need to pre-approve recipient, if they already support meta-tx.

D) MetaTx Signer Verification

Finally, another differentation possible for non-account based metatx is how the signer is being picked up by the recipient.

In EIP-1776 it assumes that recipient can easily add a from field to their functions as this is already a common practise in many standard.

in @Amxx and GSN version, the signer is appended to the data of the call.

I would be happy to update EIP-1776 to use this method if that is objectively better. For now, I feel the “first param” is simpler and can fulfil the same purpose, While it can in some case, requires recipient contracts to add, otherwise unecessary, extra function, most EIP, like EIP-20, EIP-721 and EIP-1155, have already functions that take the from as first parameter.

I am aiming into an integrated solution for Proof-of-Stake Ethereum.

I’ve proposed a soft fork to enable miners (and in future stakers/validators) to be relayers (just like they do with regular transactions), see https://github.com/ethereum/EIPs/pull/2473
This changes can be done to geth/others, without a consensus change, and then smart contracts can then always forward gas payment to block.coinbase and stop having to deal with a separate ecosystem for relayers, instead the same gas market gets extended for other tokens.

For the use-case of EIP-2473 become more efficient, I proposed EIP-2474, which allows block.coinbase to make calls, see. https://github.com/ethereum/EIPs/pull/2474

In regards of EIP-2473, it mentions EIP-1077, but we could support multiple standards if needed.

I’ve also updated, and is still WIP into a new EIP-1077 interface which encodes only gas payment stuff, leaving the rest to be evaluated by the wallet/account contract, and considering the gasBase to enable the refund of the gas payment to relayer.

1 Like

Your ERC712 domain does not include the chainId. I think this is really dangerous has it opens the door to replay between chains if contracts are deployed at the same address.

My idea with the no-proxy model is to separate the meta-transaction validation by the recipient form the relayer repayment. In that sens the recipient should not deal with relayer repayment. Relayer repayment is either perform by an additional on-chain layer (such as GSN) or left to off-chain accounting (including non decentralized payment in fiat).

Your ERC712 domain does not include the chainId. I think this is really dangerous has it opens the door to replay between chains if contracts are deployed at the same address.

This go even far deep. In case of a chain split, the chainID inside the domain separator wont work. See the discussion I opened at GnosisSafe: https://github.com/gnosis/safe-contracts/issues/170

It won’t work if the domain separator is only computed in the constructor, but it’s easy to add an unrestricted public function that recomputes it at any time using the value given by the chainId opcode. I’ve done that in a few contracts were I want to avoid recomputing the domain hash everytime.

function initialize(/* params */)
external onlyOwner()
{
	require(EIP712DOMAIN_SEPARATOR == bytes32(0), "already-configured");
	EIP712DOMAIN_SEPARATOR = _domain().hash();
	// extra stuff
}
function updateDomainSeparator()
external
{
	require(EIP712DOMAIN_SEPARATOR != bytes32(0), "not-configured");
	EIP712DOMAIN_SEPARATOR = _domain().hash();
}
function _chainId()
internal pure returns (uint256 id)
{
	assembly { id := chainid() }
}
function _domain()
internal view returns (IexecODBLibOrders_v4.EIP712Domain memory)
{
	return IexecODBLibOrders_v4.EIP712Domain({
		name:              "iExecODB",
		version:           "3.0-alpha",
		chainId:           _chainId(),
		verifyingContract: address(this)
	});
}

Yeah, I suggested that, if gnosis safe wants to use EIP712, they have to compute the domainhash every transaction…

The best would be to update EIP712, or use EIP191 with EIP1344 in the application data.

I am aware of it :slight_smile: you can find my reasoning about the current chainId opcode in this forum (I proposed an alternative opcode that was safe under minority fork and did not require any caching but unfortunately got rejected, see EIP-1965) and I agree that chainId is important but require more complex handling that simply injecting in the constructor (or even the chainId opcode as it is currently exposed) hence why I decided to not included it here.

But let’s not focus on that for the discussion here as this is just the domain separator. In that we can deal with that, once we agree on the rest :slight_smile:
Let’s discuss the important potentially incompatible difference between our perspective and figure out the best of each

This is not about no-proxy vs proxy. In both case it can be dealt with outside contract.

It is about B) and if a repaymemt is supported, the signer would always need to be in control, hence why I think it is important it is part of the message standard, and not yet another.

That is why one of the goal of EIP-1776 is to be implementation independent so wallet can still process different implementation

This is assuming the message signer will be the repayer.

I believe the original idea with metatransactions was about separating the user and the transaction sender, by moving the gas cost in eth away from the user to the relayer. If you require the user to repay the relayer you are bringing back some of the original issue.
I believe that user, relayer, and repayer should be 3 separate entities. In some case 2 of those might be the same (user and repayer) but I think it would be great if as a project I can repay for some of my user meta-transactions while not being in charge of the relaying infrastructure myself.

My proposal is about allowing the receiver to process the user meta-transaction without any repayment support, and In front of that I would have any relaying mechanism, including some where there is a on-chain repayment mechanism.

My proposal do not require the user to pay, it allows the user to pay.
The thing though that is important in all case is that if someone else than a relayer is going to pay, there need to be checks in place (txgas among them) that ensure the relayer can’t get the reward while not ensuring the metatx get submitted as intended

As for having a repayer being different than the relayer this can be done by whatever relay system is in place.

This does not remove the need to ensure the signer is in total control of how its metatx get included.

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.