ERC777 implementation of ERC20's transfer function: _callTokensToSend and _callTokensReceived

Hi!

I was looking to discuss a feature I noticed today while developing an ERC777. Here is the code of the transfer function for the ERC777:

function transfer(address recipient, uint256 amount) external returns (bool) {
        require(recipient != address(0), "ERC777: transfer to the zero address");

        address from = msg.sender;

        _callTokensToSend(from, from, recipient, amount, "", "");

        _move(from, from, recipient, amount, "", "");

        _callTokensReceived(from, from, recipient, amount, "", "", false);

        return true;
    }

I will focus on the _callTokensReceived hook, through this might applicable to _callTokensToSend aswell, to a lesser extent.

function _callTokensReceived(
        address operator,
        address from,
        address to,
        uint256 amount,
        bytes memory userData,
        bytes memory operatorData,
        bool requireReceptionAck
    )
        private
    {
        address implementer = _erc1820.getInterfaceImplementer(to, TOKENS_RECIPIENT_INTERFACE_HASH);
        if (implementer != address(0)) {
            IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData);
        } else if (requireReceptionAck) {
            require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient");
        }
    }

The transfer function does not require the recipient to implement the tokensReceived hook, because it is passing false to the requireReceptionAck boolean in _callTokensReceived. Still, in the event that the recipient has registered an implementer of this function, it will trigger the mentioned tokensReceived hook.

I 'd argue that if I am looking to use vanilla ERC20 transfer, this hook should not be called. For example, if I am implementing an automated payments contract and I want to trigger a batch transfer in a single transaction (and yes, I am pushing the payment here), I prefer ERC20 's transfer() instead of ERC777 's send() because I do not want to check things like reentrancy or gas harvesting attacks, which I am exposed to due to the tokensReceived hook.

Maybe I am missing something, so… thoughts on this?

1 Like

Hi @madcake,

Thanks for asking this question.

I had a look through the code and the EIP and my reading of this is that the behavior follows what is specified in the EIP-777

tokensReceived

...

ERC20 compatibility requirement :
This hook takes precedence over ERC20 and MUST be called (if registered) when calling ERC20’s transfer and transferFrom event. When called from a transfer , operator MUST be the same value as the from . When called from a transferFrom , operator MUST be the address which issued the transferFrom call.

tokensToSend

...

ERC20 compatibility requirement :
This hook takes precedence over ERC20 and MUST be called (if registered) when calling ERC20’s transfer and transferFrom event. When called from a transfer , operator MUST be the same value as the from . When called from a transferFrom , operator MUST be the address which issued the transferFrom call.

1 Like

Oh, alright. then it is the standard specification, I don’t know why I didn’t look up there first (well I do know, but it is too embarrasing :joy:).

Thank you for the information!

1 Like

Hi @madcake,

I had to check the standard specification too.

All questions are good. Next time someone asks, I can point them to this, plus I know now too. :smile: