ethereum / EIPs

The Ethereum Improvement Proposal repository
https://eips.ethereum.org/
Creative Commons Zero v1.0 Universal
12.9k stars 5.28k forks source link

EIP-2612: 712-signed token approvals #2613

Closed MrChico closed 1 year ago

MrChico commented 4 years ago

This EIP is now final. Discussions about final EIPs should be on https://ethereum-magicians.org/

This is a place for discussing https://github.com/ethereum/EIPs/pull/2612, which proposes an additional method permit for making approvals by way of signed messages rather than direct transactions

maurelian commented 4 years ago

IMO the 'security considerations' section should mention the front running attack that can occur when an allowance is reduced, either by approve or the new permit function. I don't think this proposal changes the risk or possible mitigations in any way, but I haven't thought hard about it.

If you have a strong desire to do some bike shedding, you could even suggest a specific mitigation for it. 😝

Nipol commented 4 years ago

I was looking forward to talking about this. I'd like to talk about the need for a "deadline"

What aspects of Tx Relayer do we need to take into account "deadline"? The Tx Relayer may have a response that causes tx while setting tx fee low. or no response.

And when a user signs for an interface, it requires more information. or more knowledge. Is usability not compromised? and more complexity for Tx Relayer.

So I think the deadline is unnecessary. 🤔

maurelian commented 4 years ago

I like deadline, as it solves the problem of worrying about all the approvals one has floating out in the world. That said, I am sympathetic to the argument that it is inconsistent with the existing approve interface.

MrChico commented 4 years ago

Thanks @maurelian, added a comment about the approval front running attack. @nipol The deadline parameter is a way to mitigate the free option that the relaying party gets when receiving a signed message (it is just a deadline for the Permit itself, not for the approval so I don't think it solves the problem of random tokens being given allowance). In cases where the free option is not a concern, deadline can simply be set to uint(-1), so it should be seen as an optional parameter

MrChico commented 4 years ago

Actually @maurelian I was just made aware of the xDai staking token implementation of permit which I think does what exactly what you are alluding to here. It shares the same ABI as Dai and Chai, but not the same semantics. I guess we better document all three flavors in the ERC

Nipol commented 4 years ago

In terms of "Free Options," I think deadline has sufficient requirements.

But in terms of EIP-712, deadline is not convincing. deadline is a difficult option for most signer to understand. at now, no example of an interface receiving a deadline value exists.

We can't show you the "Free Options" article every time ask for a signature. If we cannot effectively explain deadline to the signer and Tx Relayer, uint256(-1) will be used in most cases.

Furthermore, deadline is a consideration that does not appear in the EIP-712 specification. I think it does not have to appear as an exception in Permit.

If all of these signatures necessary a "deadline" option, if all of EIP-712 signatures have the same security level, I think it's better to update the EIP-712 specification. 🙂

What is the best option? What do you think?

MrChico commented 4 years ago

We can't show you the "Free Options" article every time ask for a signature. If we cannot effectively explain deadline to the signer and Tx Relayer, uint256(-1) will be used in most cases.

I'm perfectly happy with uint(-1) being used in most cases.

deadline is a difficult option for most signer to understand. at now, no example of an interface receiving a deadline value exists.

There are already interfaces which deal with deadline, uniswap being one of them:

Screen Shot 2020-04-27 at 12 21 12

or, for Permit https://stablecoin.services automatically sets deadline 4 minutes into the future.

I can agree with your point that it may a difficult point for users to understand, and one that may require some EIP-712 tweaking to improve. Another ux improving solution could also be to introduce Date as a type to 712, to improve how it is rendered to the user.

I do think that these options fall outside the scope of this ERC though, especially since this in some sense more of a descriptive erc than a prescriptive erc; I am highlighting what has been implemented and the motivations behind it. We can certainly make tweaks in new versions, but I am mainly interested in documenting what already exists here.

axic commented 4 years ago

I've seen on twitter that apparently there are at least 3 different semantics contracts follow and still call it "permits". @MrChico you proposed to discuss all in the EIP/ERC.

I think an EIP should be unambiguous and promote a single way of doing things. Is it possible to do that while perhaps mentioning "semi-compatible" contracts? If not, would it make sense having 3 different EIPs?

MrChico commented 4 years ago

@axic I'm planning on mentioning the Stake-flavored implementation under "backwards compatibility", making that, the dai and chai implementations all belong to this " semi-compatible" class, but keep the specification as is for specificity

axic commented 4 years ago

Nice, sounds good!

Nipol commented 4 years ago

@MrChico Good! I think we've written quite a meaningful conversation in publicly.

It will be the data we or the next people will refer to when working in the future! 😉

ptrwtts commented 4 years ago

The standard ERC-20 race condition for approvals applies to permit as well.

Why not take the opportunity to fix this? Otherwise, we might end up with proliferations of safePermit too. Is the assumption that the allowances will be spent immediately, so it's not actually a problem?

MrChico commented 4 years ago

The standard ERC-20 race condition for approvals applies to permit as well.

Why not take the opportunity to fix this? Otherwise, we might end up with proliferations of safePermit too. Is the assumption that the allowances will be spent immediately, so it's not actually a problem?

To be honest I don't find the approval race condition to be much of an issue in practice – it's only ever a problem when allowing addresses that can "spontaneously" spend your allowance. In most use cases, you give your allowance to a smart contract in such a way that only you can later initialize the transferFrom from your own address.

I'm all for people implementing safePermit if they feel that this is a concern, but in order to limit the scope of what I'm trying to do here I think such alternatives should be in a different ERC

nventuro commented 4 years ago

It wasn't clear to me from a cursory read of the EIP that the following getter is also part of the required interface, I'd make it more explicit:

function nonces(address owner) external view returns (uint256);

I'd also consider changing its name to something else, since nonces is quite generic and non-descript.

MrChico commented 4 years ago

I'd also consider changing its name to something else, since nonces is quite generic and non-descript.

Do you have a suggestion?

MrChico commented 4 years ago

Keep in mind that a change in this name would make this etc incompatible with Uniswap v2 (and even more incompatible with dai and chai)

petejkim commented 4 years ago

I'd also consider changing its name to something else, since nonces is quite generic and non-descript.

Do you have a suggestion?

permitNonces

MrChico commented 4 years ago

Its okay for nonces to be generic, in some cases it can be an advantage. The erc doesn't specify other methods can't use nonces for other purpose

MrChico commented 4 years ago

But most of the time, this doesn't seem like a particularly important concern

wighawag commented 4 years ago

I think it would be useful to have a function that let smart contract know if the permit call will succeed if executed. For example, when a contract called via static_call need to check the validity of the signature before handing the operation to another contract/call.

This could be like :

function isValidPermit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external view returns (bool);
MrChico commented 4 years ago

I think it would be useful to have a function that let smart contract know if the permit call will succeed if executed. For example, when a contract called via static_call need to check the validity of the signature before handing the operation to another contract/call.

This could be like :

function isValidPermit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external view returns (bool);

This is interesting. But if they were called with staticcall they wouldn't be able to execute the permit later anyway? In the cases I can imagine, it seems like you'd want to submit the permit whenever if it is well formed so it seems a try-catch pattern would be sufficient for this use case. Or is there some scenario you can think of in which you would NOT want to submit a well-formed permit?

MrChico commented 4 years ago

Hmmm, I guess one could imagine a sort of pure validity checking contract which would be called with staticcall and return true to the original caller if all is ok, and the original caller would not be constrained to a STATIC environment.... Did you have something like this in mind? Do people actually write contracts like this?

wighawag commented 4 years ago

Yes, that's basically it. GSN (https://docs.opengsn.org/learn/index.html) have this architecture where a "view" call is made first to a "paymaster" contract that can answer whether it can pay for the metatx. If it answer no, the metatx do not proceed and it is not charged. If it says yes, another call will be made where the "paymaster" can now call permit. The gas consumed here will be charged

MrChico commented 4 years ago

@wighawag couldn't you just as well check the validity of the permit in the "paymaster" in that case? As per the spec, you need to validate:

all of which can be verified in a static environment.

MrChico commented 4 years ago

Although the nonce is not given as an argument in the permit function, it will equal to Token.nonces[owner] if it is indeed a valid signature.

nventuro commented 4 years ago

I think this EIP can do without the view method described above. The reason why it's there in GSN is because part of that function is calling into a contract and performing an arbitrary query: by encapsulating the call there clients can get a boolean return value without having to know anything about the end recipient.

wighawag commented 4 years ago

@MrChico @nventuro That's exactly what I do as a workaround, see here : https://github.com/wighawag/gsn-playground/blob/5f40cc949031129374a48e43c8a0b71b83e52bf1/contracts/src/DAIPaymaster.sol#L101-L119

but this does not scale if my paymaster wanted to support all token that support the permit standard (unless A DOMAIN_SEPARATOR view was also part of the standard), hence why in my opinion a function like isValidPermit should be part of the standard.

By the way in regard to EIP-712 domain. I am not sure this should be part of the standard. EIP-2612 Contract implementer should be free to set the EIP-712 domain they see fit.

MrChico commented 4 years ago

but this does not scale if my paymaster wanted to support all token that support the permit standard (unless A DOMAIN_SEPARATOR view was also part of the standard), hence why in my opinion a function like isValidPermit should be part of the standard.

Ah, yes you are right that this requires DOMAIN_SEPARATOR to be publicly accessible. I think requiring it to be accessible is a good idea and should be added to the spec

nventuro commented 4 years ago

Is that because the version field is unknown? All other members of the domain separator seem to be obtainble, but I'm not an expert on 712.

MicahZoltu commented 4 years ago

While the current mechanism for nonce management saves on gas (one storage slot per transaction rather than one storage slot per user), it means that a user cannot sign several permits and hand them out to multiple providers because inclusion order is not known at permit signing time. It may be worth the storage costs to have the nonce be included. An implementation can always do some tricks like storing 256 nonce-used flags in a single variable so there are 255 storage updates for every storage addition, thus greatly reducing the gas costs.

defifuture commented 4 years ago

I'm wondering if using block.number as a deadline would be a better choice than block.timestamp, since it's a value that can't be manipulated and you can be sure by which block the permit() tx must go through. What do you guys think?

petejkim commented 4 years ago

@MicahZoltu USDCv2 implements both Uniswap's flavor of permit() and USDC's own gas abstraction methods precisely for that reason. (both are great!) I'll try to work on an EIP this weekend.

wighawag commented 4 years ago

Is there any progress on this EIP?

I see that the DOMAIN_SEPARATOR requirement is not in the current spec : https://eips.ethereum.org/EIPS/eip-2612 @MrChico you agreed it was good to add it. Is that work in progress ?

Also, the current spec is defining the full EIP-712 message format, it should leave the domain separator parameters ( EIP712Domain) up to the implementation. Only the Permit message should be defined. If that is already the intent, it should be clarified in the EIP.

MrChico commented 4 years ago

Yeah, I will address your comments shortly @wighawag

k06a commented 4 years ago

@defifuture: https://consensys.github.io/smart-contract-best-practices/recommendations/#the-15-second-rule

The 15-second Rule¶ The Yellow Paper (Ethereum's reference specification) does not specify a constraint on how much blocks can drift in time, but it does specify that each timestamp should be bigger than the timestamp of its parent. Popular Ethereum protocol implementations Geth and Parity both reject blocks with timestamp more than 15 seconds in future. Therefore, a good rule of thumb in evaluating timestamp usage is:

If the scale of your time-dependent event can vary by 15 seconds and maintain integrity, it is safe to use a block.timestamp.

Eenae commented 4 years ago

I suggest clarifying what bytes(x) means in the solidity pseudocode snippet. Because implementations tell us that addresses are getting padded up to 32 bytes (by abi.encode) - that is not obvious from the spec.

Btw, in an earlier version of the spec I saw abi.encodePacked which yields different results. I understand that addresses padding came from EIP 712, but still, clarification will be beneficial IMO.

juniset commented 4 years ago

I'm a bit late to the party but it would be nice if this EIP was compatible with EIP-1271 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1271.md) and also accepts signatures from smart-contract wallets.

Smart-contract wallets (Argent, Gnosis, Authereum, etc) natively support gas abstraction but interactions with dapps that rely on permit fail through e.g. WalletConnect because the signature is not being validated. We either need to ask dapps to support 2 different flows (which is bad) or permit should accept signatures from EOAs and smart-contract wallets.

Ro5s commented 3 years ago

This may be naive, but can this implementation work with NFT/erc721 where ‘value’ might represent a ‘tokenId’? (Or in simple adaptation, this param would be explicit?)

remote-gildor commented 3 years ago

I think the permit() method should return a bool to go in line with other ERC-20 methods:

function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external returns (bool success)

What do you think?

MrChico commented 3 years ago

@remote-gildor is there any particular reason you need this bool return? Imho the bool return in ERC20 was largely a mistake and I don't see a reason for including it here.

remote-gildor commented 3 years ago

Hey @MrChico 🙂 I was looking at this list of unexpected ERC-20 behaviours and saw the missing return value section: https://github.com/xwvvvvwx/weird-erc20#missing-return-values

Then I googled a bit more and found this article (Missing Return Value bug): https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca

Not sure how exactly could this be exploited with the permit() method, but I guess it doesn't hurt if the method returns a bool.

alcueca commented 3 years ago

I've just written some integration with USDC v2 eip2612 implementation. USDC v2 uses the "2" version in the domain separator, but there isn't any way of getting the version out of the contract, thus making automated integration impossible.

I'd suggest that since the eip712 domain separator of eip2162 requires a version, a getter for it should be part of the specification.

Potential implementation, that allows overriding of the version by inheriting contracts.

MrChico commented 3 years ago

Hmmm, as it stands, this specification does not specify the form of the domain separator. Anything that satisfies https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator will do. This EIP mandates the DOMAIN_SEPARATOR() to be public though. It would be hard to go further without mandating a format on the DOMAIN_SEPARATOR. I suppose you are suggesting that the DOMAIN_SEPARATOR() should be standardized here as well, and that all of its fields should be available in publicly callable functions?

alcueca commented 3 years ago

I didn't realize that I'm supposed to pull the DOMAIN_SEPARATOR of the contract, instead of computing it off-chain. That makes things easier.

petejkim commented 3 years ago

version is still required if you want to use eth_signTypedData[vX], so I think it's a good idea to add a version() function. Will be adding it to USDC in the next version.

alfredolopez80 commented 3 years ago

I'm a bit late to the party but it would be nice if this EIP was compatible with EIP-1271 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1271.md) and also accepts signatures from smart-contract wallets.

Smart-contract wallets (Argent, Gnosis, Authereum, etc) natively support gas abstraction but interactions with dapps that rely on permit fail through e.g. WalletConnect because the signature is not being validated. We either need to ask dapps to support 2 different flows (which is bad) or permit should accept signatures from EOAs and smart-contract wallets.

it was resolved, with Open Zeppelin Library available in version 4.1 https://docs.openzeppelin.com/contracts/4.x/api/utils#SignatureChecker

@juniset

frangio commented 3 years ago

I didn't realize that I'm supposed to pull the DOMAIN_SEPARATOR of the contract, instead of computing it off-chain. That makes things easier.

@alcueca I don't think this is right. If you just pull the domain separator from the contract you have no way of verifying that it is the appropriate chain id. Some other solution is needed, but it sounds like a broader problem with EIP712 rather than in ERC20-permit.

I've asked about this in the discussion for EIP712.

juniset commented 3 years ago

I'm a bit late to the party but it would be nice if this EIP was compatible with EIP-1271 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1271.md) and also accepts signatures from smart-contract wallets. Smart-contract wallets (Argent, Gnosis, Authereum, etc) natively support gas abstraction but interactions with dapps that rely on permit fail through e.g. WalletConnect because the signature is not being validated. We either need to ask dapps to support 2 different flows (which is bad) or permit should accept signatures from EOAs and smart-contract wallets.

it was resolved, with Open Zeppelin Library available in version 4.1 https://docs.openzeppelin.com/contracts/4.x/api/utils#SignatureChecker

@juniset

@alfredolopez80 Not really since the current spec still mentions r, s and v is a valid secp256k1 signature from owner of the message: which suggests that the owner is an EOA.

We are pushing for OZ to use https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol in their implementation of permit (see https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2845) but they want to be sure that they are compliant with the spec.

It would be great if the spec could be clarified to say e.g. `r, s and v is a valid secp256k1 signature from owner of the message or a valid EIP1271 signature when owner of the message is a smart-contract'.

k06a commented 3 years ago

Really sad that EIP have r, s, v arguments instead of bytes signature. Having string would allow to use SignatureChecker.sol abstraction on top of EOA/SC signatures and to use EIP-2098 compact signatures

frangio commented 3 years ago

Yes, bytes signature would be better, but unlikely to change at this point. But even with r,s,v it would be possible to use SignatureChecker for ECDSA-style EIP1271 signatures, if the EIP allows it, which is a backwards compatible improvement.