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. 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. 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
1
# Author: Sören Steiger, twitter.com/ssteiger_
2
# License: MIT
3
4
# ERC777 Token Standard
5
# https://eips.ethereum.org/EIPS/eip-777
6
7
8
# Interface for ERC1820 registry contract
9
# https://eips.ethereum.org/EIPS/eip-1820
10
contract ERC1820Registry:
11
def setInterfaceImplementer(
12
_addr: address,
13
_interfaceHash: bytes32,
14
_implementer: address,
15
): modifying
16
def getInterfaceImplementer(
17
_addr: address,
18
_interfaceHash: bytes32,
19
) -> address: modifying
20
21
# Interface for ERC777Tokens sender contracts
22
contract ERC777TokensSender:
23
def tokensToSend(
24
_operator: address,
25
_from: address,
26
_to: address,
27
_amount: uint256,
28
_data: bytes[256],
29
_operatorData: bytes[256]
30
): modifying
31
32
# Interface for ERC777Tokens recipient contracts
33
contract ERC777TokensRecipient:
34
def tokensReceived(
35
_operator: address,
36
_from: address,
37
_to: address,
38
_amount: uint256,
39
_data: bytes[256],
40
_operatorData: bytes[256]
41
): modifying
42
43
44
Sent: event({
45
_operator: indexed(address), # Address which triggered the send.
46
_from: indexed(address), # Token holder.
47
_to: indexed(address), # Token recipient.
48
_amount: uint256, # Number of tokens to send.
49
_data: bytes[256], # Information provided by the token holder.
50
_operatorData: bytes[256] # Information provided by the operator.
51
})
52
53
Minted: event({
54
_operator: indexed(address), # Address which triggered the mint.
55
_to: indexed(address), # Recipient of the tokens.
56
_amount: uint256, # Number of tokens minted.
57
_data: bytes[256], # Information provided for the recipient.
58
_operatorData: bytes[256] # Information provided by the operator.
59
})
60
61
Burned: event({
62
_operator: indexed(address), # Address which triggered the burn.
63
_from: indexed(address), # Token holder whose tokens are burned.
64
_amount: uint256, # Token holder whose tokens are burned.
65
_data: bytes[256], # Information provided by the token holder.
66
_operatorData: bytes[256] # Information provided by the operator.
67
})
68
69
AuthorizedOperator: event({
70
_operator: indexed(address), # Address which became an operator of tokenHolder.
71
_holder: indexed(address) # Address of a token holder which authorized the operator address as an operator.
72
})
73
74
RevokedOperator: event({
75
_operator: indexed(address), # Address which was revoked as an operator of tokenHolder.
76
_holder: indexed(address) # Address of a token holder which revoked the operator address as an operator.
77
})
78
79
80
erc1820Registry: ERC1820Registry
81
erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
82
83
name: public(string[64])
84
symbol: public(string[32])
85
totalSupply: public(uint256)
86
granularity: public(uint256)
87
88
balanceOf: public(map(address, uint256))
89
90
defaultOperatorsList: address[4]
91
defaultOperatorsMap: map(address, bool)
92
93
operators: map(address, map(address, bool))
94
95
96
@public
97
def __init__(
98
_name: string[64],
99
_symbol: string[32],
100
_totalSupply: uint256,
101
_granularity: uint256,
102
_defaultOperators: address[4]
103
):
104
self.name = _name
105
self.symbol = _symbol
106
self.totalSupply = _totalSupply
107
# The granularity value MUST be greater than or equal to 1
108
assert _granularity >= 1
109
self.granularity = _granularity
110
self.defaultOperatorsList = _defaultOperators
111
for i in range(4):
112
assert _defaultOperators[i] != ZERO_ADDRESS
113
self.defaultOperatorsMap[_defaultOperators[i]] = True
114
self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress)
115
self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777Token"), self)
116
117
118
@private
119
def _checkForERC777TokensInterface_Sender(
120
_operator: address,
121
_from: address,
122
_to: address,
123
_amount: uint256,
124
_data: bytes[256]="",
125
_operatorData: bytes[256]=""
126
):
127
implementer: address = self.erc1820Registry.getInterfaceImplementer(_from, keccak256("ERC777TokensSender"))
128
if implementer != ZERO_ADDRESS:
129
ERC777TokensSender(_from).tokensToSend(_operator, _from, _to, _amount, _data, _operatorData)
130
131
132
@private
133
def _checkForERC777TokensInterface_Recipient(
134
_operator: address,
135
_from: address,
136
_to: address,
137
_amount: uint256,
138
_data: bytes[256]="",
139
_operatorData: bytes[256]=""
140
):
141
implementer: address = self.erc1820Registry.getInterfaceImplementer(_to, keccak256("ERC777TokensRecipient"))
142
if implementer != ZERO_ADDRESS:
143
ERC777TokensRecipient(_to).tokensReceived(_operator, _from, _to, _amount, _data, _operatorData)
144
145
146
@private
147
def _transferFunds(
148
_operator: address,
149
_from: address,
150
_to: address,
151
_amount: uint256,
152
_data: bytes[256]="",
153
_operatorData: bytes[256]=""
154
):
155
# any minting, sending or burning of tokens MUST be a multiple of the granularity value.
156
assert _amount % self.granularity == 0
157
158
# check for 'tokensToSend' hook
159
if _from.is_contract:
160
self._checkForERC777TokensInterface_Sender(_operator, _from, _to, _amount, _data, _operatorData)
161
162
self.balanceOf[_from] -= _amount
163
self.balanceOf[_to] += _amount
164
165
# check for 'tokensReceived' hook
166
# but only if transfer is not a burn
167
if _to != ZERO_ADDRESS:
168
if _to.is_contract:
169
self._checkForERC777TokensInterface_Recipient(_operator, _from, _to, _amount, _data, _operatorData)
170
171
172
@public
173
@constant
174
def defaultOperators() -> address[4]:
175
return self.defaultOperatorsList
176
177
178
@public
179
@constant
180
def isOperatorFor(_operator: address, _holder: address) -> bool:
181
return (self.operators[_holder])[_operator] or self.defaultOperatorsMap[_operator] or _operator == _holder
182
183
184
@public
185
def authorizeOperator(_operator: address):
186
(self.operators[msg.sender])[_operator] = True
187
log.AuthorizedOperator(_operator, msg.sender)
188
189
190
@public
191
def revokeOperator(_operator: address):
192
# MUST revert if it is called to revoke the holder as an operator for itself
193
assert _operator != msg.sender
194
(self.operators[msg.sender])[_operator] = False
195
log.RevokedOperator(_operator, msg.sender)
196
197
198
@public
199
def send(_to: address, _amount: uint256, _data: bytes[256]=""):
200
assert _to != ZERO_ADDRESS
201
operatorData: bytes[256]=""
202
self._transferFunds(msg.sender, msg.sender, _to, _amount, _data, operatorData)
203
log.Sent(msg.sender, msg.sender, _to, _amount, _data, operatorData)
204
205
206
@public
207
def operatorSend(
208
_from: address,
209
_to: address,
210
_amount: uint256,
211
_data: bytes[256]="",
212
_operatorData: bytes[256]=""
213
):
214
assert _to != ZERO_ADDRESS
215
assert self.isOperatorFor(msg.sender, _from)
216
self._transferFunds(msg.sender, _from, _to, _amount, _data, _operatorData)
217
log.Sent(msg.sender, _from, _to, _amount, _data, _operatorData)
218
219
220
@public
221
def burn(_amount: uint256, _data: bytes[256]=""):
222
operatorData: bytes[256]=""
223
self._transferFunds(msg.sender, msg.sender, ZERO_ADDRESS, _amount, _data, operatorData)
224
self.totalSupply -= _amount
225
log.Burned(msg.sender, msg.sender, _amount, _data, operatorData)
226
227
228
@public
229
def operatorBurn(
230
_from: address,
231
_amount: uint256,
232
_data: bytes[256]="",
233
_operatorData: bytes[256]=""
234
):
235
# _from: Token holder whose tokens will be burned (or 0x0 to set from to msg.sender).
236
fromAddress: address
237
if _from == ZERO_ADDRESS:
238
fromAddress = msg.sender
239
else:
240
fromAddress = _from
241
assert self.isOperatorFor(msg.sender, fromAddress)
242
self._transferFunds(msg.sender, fromAddress, ZERO_ADDRESS, _amount, _data, _operatorData)
243
self.totalSupply -= _amount
244
log.Burned(msg.sender, fromAddress, _amount, _data, _operatorData)
245
246
247
# NOTE: ERC777 intentionally does not define specific functions to mint tokens.
248
@public
249
def mint(
250
_to: address,
251
_amount: uint256,
252
_operatorData: bytes[256]=""
253
):
254
assert _to != ZERO_ADDRESS
255
# any minting, sending or burning of tokens MUST be a multiple of the granularity value.
256
assert _amount % self.granularity == 0
257
# only operators are allowed to mint
258
assert self.defaultOperatorsMap[msg.sender]
259
self.balanceOf[_to] += _amount
260
self.totalSupply += _amount
261
data: bytes[256]=""
262
if _to.is_contract:
263
self._checkForERC777TokensInterface_Recipient(msg.sender, ZERO_ADDRESS, _to, _amount, data, _operatorData)
264
log.Minted(msg.sender, _to, _amount, data, _operatorData)
265
266
Copied!
1
# Author: Sören Steiger, twitter.com/ssteiger_
2
# License: MIT
3
4
# ERC777 Token Sender
5
# https://eips.ethereum.org/EIPS/eip-777
6
7
8
# Interface for ERC1820 registry contract
9
# https://eips.ethereum.org/EIPS/eip-1820
10
contract ERC1820Registry:
11
def setInterfaceImplementer(
12
_addr: address,
13
_interfaceHash: bytes32,
14
_implementer: address,
15
): modifying
16
17
18
TokensSent: event({
19
_operator: indexed(address),
20
_from: indexed(address),
21
_to: indexed(address),
22
_amount: uint256,
23
_data: bytes[256],
24
_operatorData: bytes[256]
25
})
26
27
28
erc1820Registry: ERC1820Registry
29
erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
30
31
32
@public
33
def __init__():
34
self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress)
35
self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777TokensSender"), self)
36
37
38
@public
39
def tokensToSend(
40
_operator: address,
41
_from: address,
42
_to: address,
43
_amount: uint256,
44
_data: bytes[256],
45
_operatorData: bytes[256]
46
):
47
log.TokensSent(_operator, _from, _to, _amount, _data, _operatorData)
48
Copied!
1
# Author: Sören Steiger, twitter.com/ssteiger_
2
# License: MIT
3
4
# ERC777 Token Receiver
5
# https://eips.ethereum.org/EIPS/eip-777
6
7
8
# Interface for ERC1820 registry contract
9
# https://eips.ethereum.org/EIPS/eip-1820
10
contract ERC1820Registry:
11
def setInterfaceImplementer(
12
_addr: address,
13
_interfaceHash: bytes32,
14
_implementer: address,
15
): modifying
16
17
18
TokensReceived: event({
19
_operator: indexed(address),
20
_from: indexed(address),
21
_to: indexed(address),
22
_amount: uint256,
23
_data: bytes[256],
24
_operatorData: bytes[256]
25
})
26
27
28
erc1820Registry: ERC1820Registry
29
erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
30
31
32
@public
33
def __init__():
34
self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress)
35
self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777TokensRecipient"), self)
36
37
38
@public
39
def tokensReceived(
40
_operator: address,
41
_from: address,
42
_to: address,
43
_amount: uint256,
44
_data: bytes[256],
45
_operatorData: bytes[256]
46
):
47
log.TokensReceived(_operator, _from, _to, _amount, _data, _operatorData)
48
Copied!
Last modified 2yr ago