Solidity – Why ERC20/ERC721 `safeTransferFrom` Requires a `from` Address Instead of `safeTransferTo`

erc-20erc-721soliditytransfertransferfrom

I can understand that the from address in the safeTransferFrom function could be used by an approved operator. But I also wonder why not have another function for actual owners called just safeTransferTo that doesn't need to provide the from address?

safeTransferTo(address to, uint tokenId, bytes memory_data);

In this case, the from is implicit as it's coming from the msg.sender address. Isn't this much more convenient?

Is there any implications for doing it this way such as security concerns that I don't see people are doing it like this? If I would like to, should I implement such a function in my own contract?

Best Answer

Isn't this much more convenient?

Not really. When you do this through a gui that operates under this assumption, you aren't going to be filling in the from by hand--it'll be done on your behalf by javascript crafting the transaction in the dApp/website; you just sign off on the transaction when you are then prompted with it by metamask.

On the other hand, deploying extra code to the blockchain is expensive. One function that does it all in this case means less gas costs to deploy and less code to create bugs in and to audit.

Is there any implications for doing it this way such as security concerns that I don't see people are doing it like this? If I would like to, should I implement such a function in my own contract?

On the surface, why not? In practice, though, yes, probably many people would lose a lot of tokens... Or, after taking these recommendations into account, the function would likely never end up being used. :)

Because an ERC20 is not ETH, and sending ERC20 to a contract cannot trigger a receive() or fallback() function, the way we almost always send ERC20 to a contract is by approving() the contract, and then letting the contract pull the ERC20 from you, not pushing the ERC20 to it. (ERC777 handles this instead by implementing hooks and operators btw, eliminating the need for the approve() step, and its associated cost and problems.)

Trying to push ERC20 to a contract is basically the number one way it gets lost--just two months ago, a guy accidentally burned $500k USD (on etherscan) (news) by pushing ERC20 wETH to the wETH contract. Why is that a problem? Because the wETH contract is almost certainly designed, like all other ERC20 manipulating contracts, to pull the tokens, not receive them from a push.

A quick point to understand the rest:

While you may see safeTransferFrom() in both ERC20 and ERC721 related code, the two functions have basically nothing to do with each other and have very different reasons for claiming to be 'safe'. It is an official part of the ERC721 spec, but not a part of the ERC20 spec!

The ERC721 spec checks to make sure that, if the recipient is a contract, the recipient can return a magic value in order to confirm that it was designed to receive ERC721 NFTs.

The ERC20 implementation that I'm familiar with comes from Open Zeppelin's SafeERC20.sol, where the "safe" refers to interacting with and handling non-standards-compliant tokens that don't fail properly. Their tldr:

Wrappers around ERC20 operations that throw on failure (when the token contract returns false). Tokens that return no value (and instead revert or throw on failure) are also supported, non-reverting calls are assumed to be successful.

You could handle either/both of those concerns, and yet because you pushed it instead of letting the contract pull it, it would be lost forever because, in general, the contract would have no way of knowing who sent it.

As a part of safeTransferTo(), you'd have to specify a new EIP165-esque check to make sure the receiver, if it is a contract, expects and will handle tokens pushed to it directly. However, given that no project out there knows about your token and your token will be the only one doing this to start (getting momentum on new paradigms is hard, and there's a reason we don't do the more intuitive to instead of from, as elucidated at the beginning of this answer), this function likely won't end up seeing much use.

ERC20 is a problematic, creaky old standard that we only keep using because of its wide interoperability, even though we all know it's error-prone. Rather than throw in some new standards-non-conforming function, probably best to keep using SafeERC20--or just switch to ERC777!

...just make sure you have fully anticipated the re-entrancy consequences that can result from hooks. :)