Closed MrChico closed 1 year 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. 😝
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. 🤔
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.
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
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
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?
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:
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.
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?
@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
Nice, sounds good!
@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! 😉
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?
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
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.
I'd also consider changing its name to something else, since
nonces
is quite generic and non-descript.
Do you have a suggestion?
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)
I'd also consider changing its name to something else, since
nonces
is quite generic and non-descript.Do you have a suggestion?
permitNonces
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
But most of the time, this doesn't seem like a particularly important concern
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);
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?
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?
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
@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:
deadline
.owner
is not the zero address.Token.nonces[owner]
is equal to nonce provided nonce
.r
, s
and v
is a valid secp256k1
signature from owner
of an ERC-712 typed Permit
message.all of which can be verified in a static environment.
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.
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.
@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.
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
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.
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.
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?
@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.
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.
Yeah, I will address your comments shortly @wighawag
@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.
I suggest clarifying what bytes(x)
means in the solidity pseudocode snippet. Because implementations tell us that address
es 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.
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.
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?)
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?
@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.
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.
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.
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?
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.
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.
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) orpermit
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
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.
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) orpermit
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'.
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
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.
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