ERC777 Token

The ERC777 Token Standard improves on the popular ERC20 standard.

It's most defining feature is the use of the new ERC1820 interface standard which it uses in such a way, that each time a token is sent two things happen:

  1. The ERC777 contract It checks wether the sender of the transaction is a contract and wether that contract implements a tokensToSend(_operator, _from, _to, _amount, _data, _operatorData) method.

  2. It checks wether the receiver of the transaction is a contract and wether that contract implements a tokensToSend(_operator, _from, _to, _amount, _data, _operatorData) method.

If the methods exist, then the code inside of both methods is executed.

The exiting thing is, that there are no restrictions on what the code inside of the two methods looks like or what it does.

Further information on the standard can be found here:

Deploying

The contract accesses the ERC1820Registry contract in its constructor. It is therefore necessary that the ERC1820Registry contract exists on the (test) network to where the erc777 token contract gets deployed.

In migrations/1_initial_migration.js a check is performed to determine if the ERC1820Registry contract exists - if it doesn't it is deployed.

Implementation in Vyper

erc777.vy
erc777TokenSender.vy
erc777TokenReceiver.vy
erc777.vy
# Author: Sören Steiger, twitter.com/ssteiger_
# License: MIT
# ERC777 Token Standard
# https://eips.ethereum.org/EIPS/eip-777
# Interface for ERC1820 registry contract
# https://eips.ethereum.org/EIPS/eip-1820
contract ERC1820Registry:
def setInterfaceImplementer(
_addr: address,
_interfaceHash: bytes32,
_implementer: address,
): modifying
def getInterfaceImplementer(
_addr: address,
_interfaceHash: bytes32,
) -> address: modifying
# Interface for ERC777Tokens sender contracts
contract ERC777TokensSender:
def tokensToSend(
_operator: address,
_from: address,
_to: address,
_amount: uint256,
_data: bytes[256],
_operatorData: bytes[256]
): modifying
# Interface for ERC777Tokens recipient contracts
contract ERC777TokensRecipient:
def tokensReceived(
_operator: address,
_from: address,
_to: address,
_amount: uint256,
_data: bytes[256],
_operatorData: bytes[256]
): modifying
Sent: event({
_operator: indexed(address), # Address which triggered the send.
_from: indexed(address), # Token holder.
_to: indexed(address), # Token recipient.
_amount: uint256, # Number of tokens to send.
_data: bytes[256], # Information provided by the token holder.
_operatorData: bytes[256] # Information provided by the operator.
})
Minted: event({
_operator: indexed(address), # Address which triggered the mint.
_to: indexed(address), # Recipient of the tokens.
_amount: uint256, # Number of tokens minted.
_data: bytes[256], # Information provided for the recipient.
_operatorData: bytes[256] # Information provided by the operator.
})
Burned: event({
_operator: indexed(address), # Address which triggered the burn.
_from: indexed(address), # Token holder whose tokens are burned.
_amount: uint256, # Token holder whose tokens are burned.
_data: bytes[256], # Information provided by the token holder.
_operatorData: bytes[256] # Information provided by the operator.
})
AuthorizedOperator: event({
_operator: indexed(address), # Address which became an operator of tokenHolder.
_holder: indexed(address) # Address of a token holder which authorized the operator address as an operator.
})
RevokedOperator: event({
_operator: indexed(address), # Address which was revoked as an operator of tokenHolder.
_holder: indexed(address) # Address of a token holder which revoked the operator address as an operator.
})
erc1820Registry: ERC1820Registry
erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
name: public(string[64])
symbol: public(string[32])
totalSupply: public(uint256)
granularity: public(uint256)
balanceOf: public(map(address, uint256))
defaultOperatorsList: address[4]
defaultOperatorsMap: map(address, bool)
operators: map(address, map(address, bool))
@public
def __init__(
_name: string[64],
_symbol: string[32],
_totalSupply: uint256,
_granularity: uint256,
_defaultOperators: address[4]
):
self.name = _name
self.symbol = _symbol
self.totalSupply = _totalSupply
# The granularity value MUST be greater than or equal to 1
assert _granularity >= 1
self.granularity = _granularity
self.defaultOperatorsList = _defaultOperators
for i in range(4):
assert _defaultOperators[i] != ZERO_ADDRESS
self.defaultOperatorsMap[_defaultOperators[i]] = True
self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress)
self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777Token"), self)
@private
def _checkForERC777TokensInterface_Sender(
_operator: address,
_from: address,
_to: address,
_amount: uint256,
_data: bytes[256]="",
_operatorData: bytes[256]=""
):
implementer: address = self.erc1820Registry.getInterfaceImplementer(_from, keccak256("ERC777TokensSender"))
if implementer != ZERO_ADDRESS:
ERC777TokensSender(_from).tokensToSend(_operator, _from, _to, _amount, _data, _operatorData)
@private
def _checkForERC777TokensInterface_Recipient(
_operator: address,
_from: address,
_to: address,
_amount: uint256,
_data: bytes[256]="",
_operatorData: bytes[256]=""
):
implementer: address = self.erc1820Registry.getInterfaceImplementer(_to, keccak256("ERC777TokensRecipient"))
if implementer != ZERO_ADDRESS:
ERC777TokensRecipient(_to).tokensReceived(_operator, _from, _to, _amount, _data, _operatorData)
@private
def _transferFunds(
_operator: address,
_from: address,
_to: address,
_amount: uint256,
_data: bytes[256]="",
_operatorData: bytes[256]=""
):
# any minting, sending or burning of tokens MUST be a multiple of the granularity value.
assert _amount % self.granularity == 0
# check for 'tokensToSend' hook
if _from.is_contract:
self._checkForERC777TokensInterface_Sender(_operator, _from, _to, _amount, _data, _operatorData)
self.balanceOf[_from] -= _amount
self.balanceOf[_to] += _amount
# check for 'tokensReceived' hook
# but only if transfer is not a burn
if _to != ZERO_ADDRESS:
if _to.is_contract:
self._checkForERC777TokensInterface_Recipient(_operator, _from, _to, _amount, _data, _operatorData)
@public
@constant
def defaultOperators() -> address[4]:
return self.defaultOperatorsList
@public
@constant
def isOperatorFor(_operator: address, _holder: address) -> bool:
return (self.operators[_holder])[_operator] or self.defaultOperatorsMap[_operator] or _operator == _holder
@public
def authorizeOperator(_operator: address):
(self.operators[msg.sender])[_operator] = True
log.AuthorizedOperator(_operator, msg.sender)
@public
def revokeOperator(_operator: address):
# MUST revert if it is called to revoke the holder as an operator for itself
assert _operator != msg.sender
(self.operators[msg.sender])[_operator] = False
log.RevokedOperator(_operator, msg.sender)
@public
def send(_to: address, _amount: uint256, _data: bytes[256]=""):
assert _to != ZERO_ADDRESS
operatorData: bytes[256]=""
self._transferFunds(msg.sender, msg.sender, _to, _amount, _data, operatorData)
log.Sent(msg.sender, msg.sender, _to, _amount, _data, operatorData)
@public
def operatorSend(
_from: address,
_to: address,
_amount: uint256,
_data: bytes[256]="",
_operatorData: bytes[256]=""
):
assert _to != ZERO_ADDRESS
assert self.isOperatorFor(msg.sender, _from)
self._transferFunds(msg.sender, _from, _to, _amount, _data, _operatorData)
log.Sent(msg.sender, _from, _to, _amount, _data, _operatorData)
@public
def burn(_amount: uint256, _data: bytes[256]=""):
operatorData: bytes[256]=""
self._transferFunds(msg.sender, msg.sender, ZERO_ADDRESS, _amount, _data, operatorData)
self.totalSupply -= _amount
log.Burned(msg.sender, msg.sender, _amount, _data, operatorData)
@public
def operatorBurn(
_from: address,
_amount: uint256,
_data: bytes[256]="",
_operatorData: bytes[256]=""
):
# _from: Token holder whose tokens will be burned (or 0x0 to set from to msg.sender).
fromAddress: address
if _from == ZERO_ADDRESS:
fromAddress = msg.sender
else:
fromAddress = _from
assert self.isOperatorFor(msg.sender, fromAddress)
self._transferFunds(msg.sender, fromAddress, ZERO_ADDRESS, _amount, _data, _operatorData)
self.totalSupply -= _amount
log.Burned(msg.sender, fromAddress, _amount, _data, _operatorData)
# NOTE: ERC777 intentionally does not define specific functions to mint tokens.
@public
def mint(
_to: address,
_amount: uint256,
_operatorData: bytes[256]=""
):
assert _to != ZERO_ADDRESS
# any minting, sending or burning of tokens MUST be a multiple of the granularity value.
assert _amount % self.granularity == 0
# only operators are allowed to mint
assert self.defaultOperatorsMap[msg.sender]
self.balanceOf[_to] += _amount
self.totalSupply += _amount
data: bytes[256]=""
if _to.is_contract:
self._checkForERC777TokensInterface_Recipient(msg.sender, ZERO_ADDRESS, _to, _amount, data, _operatorData)
log.Minted(msg.sender, _to, _amount, data, _operatorData)
erc777TokenSender.vy
# Author: Sören Steiger, twitter.com/ssteiger_
# License: MIT
# ERC777 Token Sender
# https://eips.ethereum.org/EIPS/eip-777
# Interface for ERC1820 registry contract
# https://eips.ethereum.org/EIPS/eip-1820
contract ERC1820Registry:
def setInterfaceImplementer(
_addr: address,
_interfaceHash: bytes32,
_implementer: address,
): modifying
TokensSent: event({
_operator: indexed(address),
_from: indexed(address),
_to: indexed(address),
_amount: uint256,
_data: bytes[256],
_operatorData: bytes[256]
})
erc1820Registry: ERC1820Registry
erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
@public
def __init__():
self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress)
self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777TokensSender"), self)
@public
def tokensToSend(
_operator: address,
_from: address,
_to: address,
_amount: uint256,
_data: bytes[256],
_operatorData: bytes[256]
):
log.TokensSent(_operator, _from, _to, _amount, _data, _operatorData)
erc777TokenReceiver.vy
# Author: Sören Steiger, twitter.com/ssteiger_
# License: MIT
# ERC777 Token Receiver
# https://eips.ethereum.org/EIPS/eip-777
# Interface for ERC1820 registry contract
# https://eips.ethereum.org/EIPS/eip-1820
contract ERC1820Registry:
def setInterfaceImplementer(
_addr: address,
_interfaceHash: bytes32,
_implementer: address,
): modifying
TokensReceived: event({
_operator: indexed(address),
_from: indexed(address),
_to: indexed(address),
_amount: uint256,
_data: bytes[256],
_operatorData: bytes[256]
})
erc1820Registry: ERC1820Registry
erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
@public
def __init__():
self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress)
self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777TokensRecipient"), self)
@public
def tokensReceived(
_operator: address,
_from: address,
_to: address,
_amount: uint256,
_data: bytes[256],
_operatorData: bytes[256]
):
log.TokensReceived(_operator, _from, _to, _amount, _data, _operatorData)