pcaversaccio / snekmate

State-of-the-art, highly opinionated, hyper-optimised, and secure ๐ŸVyper smart contract building blocks.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

๐Ÿ”’ Disallow Address Poisoning in `ERC20`

pcaversaccio opened this issue ยท comments

I open this issue to track whether we should implement that zero transferFrom calls should be reverted. The implementation would look like this:

@internal
def _spend_allowance(owner: address, spender: address, amount: uint256):
    current_allowance: uint256 = self.allowance[owner][spender]
    if (current_allowance != max_value(uint256)):
        assert current_allowance >= max(amount, 1), "ERC20: insufficient allowance"
        self._approve(owner, spender, unsafe_sub(current_allowance, amount))

The reason why we should at least discuss it is that zero transferFrom calls can be invoked by anyone and allow for a certain type of phishing, see e.g. here.

Also linking the OpenZeppelin discussion here.

I ran a Twitter poll here that clearly voted "No".
image

commented

I would say allow zero transfers.

  1. Ethereum is still a resource constrained environment and transfers are among the most important paths to optimize in token contracts.
  2. The standard explicitly says to treat zero transfers like normal transfers. Whether or not it is the best choice, I don't know, but there are plenty of suboptimal choices we still follow for the sake of standardization and this one seems mostly negligible.
  3. While it is a phishing vector, there will always be phishing vectors; not to say we shouldn't protect against them when we can, but this boils down to user awareness and/or reliance on services like Etherscan to hide obvious phishing attempts by default.

Helps users save a bit of gas, while spammers burn more tokens to lower supply.

I think users just have to be a bit more careful.

Fat finger mistakes like copy-pasting an address wrongly can be prevented at the client side pretty efficiently.

A generic ban blocking zero transfers/transferFrom, seems bad since everyone using the contracts needs to add code to check if they are performing a zero transfer and not send it if they are. This puts extra code complexity all over the place.

However this code only blocks zero transfers with zero approvals. This seems like a fairly sane policy on the surface. I liked it at first. However, we've just pushed the same problem as before, but into a narrower area. Now you can't do an approve of an exact amount and then call something that transferFrom's that amount, without adding extra zero checks.

So, slightly regretfully, I think it's best to just let zero transfers on zero approvals through. Clean, understandable downstream code is important. On chain state. is not modified. The only real issue here is the transfer event.

(I think that not emitting the transfer event in the zero transfer/zero auth case is also not a good idea because sometimes people built monitoring systems that depend on tracking transfer events to find when other things happen._)

After careful consideration, I decided to not disallow the address poisoning attack in the ERC20 implementation. However, I documented this behavior in the code base. See my PR #71. Let me know if you agree or disagree with my comments.

I agree with your decision. ๐Ÿ‘

Here is an example of a contract which would have failed if zero transfer was reverting: https://etherscan.io/address/0xa5407eae9ba41422680e2e00537571bcc53efbfd#code

Although this is not per spec, but I think what really should be done is no event if amount is zero. This doesn't break anything, and if the spec explicitly wants poisoning allowed - that's too bad for the spec.