Deep Dive — OpenZeppelin’s ERC777 Implementation
OpenZeppelin recently published their implementation of the surging fungible-token standard ERC777. The purpose of ERC777 is to improve upon ERC20 while maintaining backward compatibility. The contract comes with two hooks, tokensToSend
and tokensReceived
, that addresses may implement to control and revert token operations. Accounts can now receive funds and a notification within a single transaction, supplanting the two-step process (approve
/transferFrom
) in ERC20. Let’s jump right in.
The Interface
A glaring difference between 777 and ERC20 is the addition of operators. Token holders can authorize and revoke trusted entities to act on their behalf. Contract deployers may define default operators who can move tokens for all addresses. Notice that send
is used in place of transfer
and transferFrom
, mirroring the transfer of Ether.
The Contract
We begin with the contract definition and variable declarations. ERC777 inherits from the interface defined in the EIP as well as from the ERC20 interface. An introspection registry (ERC1820) where contracts and regular addresses publish the functionality they implement, is required. The two hardcoded hashes will see use later when we call our send/receive hooks.
The constructor intakes three arguments: the name
of the token, the symbol
of the token (DAI, BAT…etc), and an array defaultOperators
to hold a list of addresses. Private variables are assigned. The contract then proclaims its ERC777/ERC20 interfaces with the registry.
The view
functions look as expected. ERC20 compliance requires the implementation of decimals
.
Send
We’ve arrived at send
, the quintessential method which moves tokens between accounts. The _send
call nested inside will also be called by operatorSend
(which we will get to later).
_send
requires that from and to cannot be the zero address. Notice that _move
is the one who moves the needle (and emit two events
, one for each token standard). _callTokensToSend
& _callTokensReceived
is the duo responsible for calling the previously mentioned hook functions.
The Send Hook
_callTokensToSend
first checks with the introspection registry that the from
address in our transaction implements the send-hook. This enables us to call IERC777Sender(implementer).tokensToSend
. Upon firing, the from
address should receive a prompt (This will depend on the wallet implementation of the interface) allowing the sender to revert the transaction.
The Receive Hook
_callTokensReceived
should feel familiar. The else if
at the end will revert if requireReceptionAck
is true
&& to
is a contract. requireReceptionAck
is false
only when an ERC20 function (transfer
, transferFrom
) calls _callTokensReceived
. Recall that ERC777 inherits from ERC20. Upon invoking tokensReceived
, the receiver will get a notification that someone is sending them some tokens, and allowing the receiver to revert.
Mint
A 777-derived contract is meant to call _mint
since the authors chose to remain agnostic in token creation methodologies. State variables are updated to reflect the minting. The receiver of the newly minted coins gets notified so long as the receive-hook is at the ready. Finally, the method emits the Minted
(ERC777) and Transfer
(ERC20) events.
Burn
The functions burn
and operatorBurn
both invoke the underlying _burn
method. The send-hook allows the burner to revert. _totalSupply
and _balances[from]
are updated appropriately.
Operator send / burn
As expected, these functions call the _send
and _burn
methods on behalf of the token holder.
Operator Utils
The authorizeOperator
& revokeOperator
both require msg.sender
to not be the argument supplied. Both functions check to see if the operator
is part of the list of default operators so that it may modify the corresponding array.
isOperatorFor
checks if a given user authorizes a given operator.
Backward Compatibility
All of the ERC20 functions are implemented.