ERC777 代币标准

简要说明:该 EIP 定义了代币合约的标准接口和行为

原文章:https://eips.ethereum.org/EIPS/eip-777

摘要

该标准定义了一种与代币合约交互的新方式,同时保持了与 ERC-20 的向后兼容。

它定义了与 Token 交互的高级功能,也就是说,运营商代表另一个地址(合约或常规账户)发送代币,并发送/接受挂钩(hooks),为 Token 持有者提供对齐代币的更多控制权。

他利用 ERC-1820 来确定当合约和常规地址收到 Token 时是否以及在何处通知它们,并允许与已部署的合约兼容。

动机

该标准尝试改进广泛使用的 ERC-20 代币标准。该标准的主要优点是:

  • 使用和 Ether 相同的原理,即通过send(dest, value, data) 发送 Token
  • 合约和常规地址都可以通过注册 tokensReceived 挂钩(hooks)来控制和拒接接受它们的 Token。(拒绝是通过在钩子函数(hooks function)中revert 来完成的)
  • tokensReceived hooks (钩子) 允许将代币发送到合约并在单个交易中通知它,这与 ERC-20 不同,ERC-20 需要两次调用(approve/transferFrom)才能实现这一点
  • 授权者可以“授权”和“撤销”那些可以代表它们发送代币的运营商。这些运营商旨在成为经过验证的合约,例如交易所、支票管理器或自动收费系统
  • 每个 Token 交易都包含数据和操作员字节字段,可分别自由地用于传递来自持有者和操作员的数据。
  • 通过部署实现钱包 tokensReceived 挂钩的代理合约,他向后不兼容不包含 tokensReceived 挂钩函数的钱包

规范

ERC777Token(Token Contract)

interface ERC777Token {
	function name() external view returns (string memory);
	function symbol() external view returns (string memory);
	function totalSupply() external view returns (uint256);
	function balanceOf(address holder) external view returns (uint256);
	function granularity() external view returns (uint256);
	
	function defaultOperators() external view returns (address[] memory);
	function isOperatorFor(
		address operator,
        address holder
	) external view returns (bool);
	function authorizeOperator(address operator) external;
	function revokeOperator(address operator) external;
	
	function send(address to, uint256 amount, bytes calldata data) external;
	function operatorSend(
		address from,
		address to,
		uint256 amount,
		bytes calldata data.
		bytes calldata operatorData
	) external;
	
	event Sent(
		address indexed operator,
		address indexed from,
		address indexed to,
		uint256 amount,
		bytes data,
		bytes operatorData
	);
	event Minted(
		address indexed operator;
		address indexed to,
		uint256 amount,
		bytes data,
		bytes operatorData
	);
	event Burned(
		address indexed operator,
        address indexed from,
        uint256 amount,
        bytes data,
        bytes operatorData
	);
	event AuthorizedOperator(
		address indexed operator,
		address indexed holder
	);
	event RevokedOperator(address indexed operator, address indexed holder);
}

代币合约必须实现上述接口。是是必须遵循下面描述的规范。

代币合约必须通过 ERC-1820 使用自己的地址注册 ERC777Token 接口

这里通过调用 ERC-1820 注册表上的 setInterfaceImplementer 函数来完成的,其中代币合约地址作为地址和实现者,ERC777Tokenkeccak256哈希 0xac7fbab5f54a3ca8194167523c6753bfeb96a445279294b6125b68cce2177054作为接口哈希。

如果合约有启用或禁止 ERC777 功能的开关,则每次触发该开关时,代币必须通过 ERC1820 相应为自己的地址注册或取消注册 ERC777Token接口。取消注册意味着调用 setInerfaceImplementer,以代币合约为地址,以 ERC777Tokenkeccak256哈希为接口哈希,以0x0为实现者(有关更多详细信息,请参阅为 ERC-1820 中的地址设置接口)

与代币合约交互时,所有金额和余额必须是无符号整数。在内部,所有值都存储为 1E-18 面额的代币。显示面额(向最终用户显示任何金额)必须是 10^18^ 的内部面额。

换一种说法,内部面额类似于”wei”,显示面额类似于 “ether”。它相当于 ERC-20 的小数函数返回18。如果代币合约为用户返回500000000000000000(0.5*1018)的余额,则用户界面必须向用户显示 0.5 个代币。如果用户希望发送 0.3 个代币,则必须以300000000000000000(0.3*1018) 的金额调用合约。

从代币合约的 ABI 以编程方式产生的用户界面可以使用并显示内部面额。但这必须明确,例如通过显示 uint256 类型

View 函数

以下是必须实现的 view 函数:

  • name 函数
function name() external view returns (string memory)

获得代币的名称,例如MyToken

identifier:0fdde03

return: Name of the token

  • symbol 函数
function sumbol() external view returns (string memory)

获得代币的代号,如:MYT

identifier:95d89b41

return: Symbol of the token

  • totalSupply 函数
function totalSupply() external view returns (uint256)

获得已铸造代币的总数。

注意:总供应量必须等于所有地址的余额之和,即由 balanceOf 函数返回的值。

注意:总供应量必须等于在所有 Minted 事件中定义的所有已铸造代币的总和减去在所有 Burned事件中定义的所有销毁代币的总和。

identifier:18160ddd

return: Total supply of tokens currently in cirulation目前流通中的代币总量

  • balanceOf 函数
function balanceOf(address holder) external view returns (uint256)

获得holder地址对应的账户余额。

余额必须大于等于零。

identifier: 70a08231

parameters(参数):holder:要查询的余额地址

return holder 在代币合约中持有的代币数量

  • granularity 函数
function granularity() external view returns(uint256)

获取代币中不可分割的最小单位。

换句话说,granularity 是代币内部单位中可以在任何时候铸造、发送或销毁的最小数量。

granularity 必须遵循以下规则 :

  • granularity 的值必须在创建时设置
  • granularity 的值不能被更改,绝对不能
  • granularity 的值必须大于等于 1
  • 所有余额必须是 granularity 的倍数
  • 铸造、发送或销毁的任何数量的代币(以内部单位计)必须是 granularity 值的倍数
  • 任何导致余额不是 granularity 值的倍数的操作必须被视为无效,并且交易必须回滚

注意:大多数代币应该是完全可分割的。也就是说,除非有充分理由不允许代币的有任何分数,此函数才应该返回 1 。

ERC-20 兼容性问题:

代币的小数位必须始终为 18。对于纯ERC777代币,ERC-20 decimals函数是可选的。并且与代币合约交互时不应该依赖其存在(18 的小数值是隐含的)。对于兼容 ERC-20 的代币,decimals函数是必须的,并且必须返回 18(在 ERC-20 中,decimals 函数是可选的。如果该函数不存在,则小数位的值没有明确定义,可以假设为 0 。因此,出于兼容性的原因,对于兼容 ERC-20 的代币,必须实现decimals函数)

Operators(运营商)

operator是被允许代表某个持有者发送和销毁代表的地址

当一个地址成为持有者的运营商时,必须发出 AuthorizedOperator 事件。AuthorizedOperatoroperator(主题 1)和 holder(主题2)必须分别是运营商(operator)和持有者(holder)的地址。

当一个持有者撤销一个运营商时,必须发出 RevokedOperator 事件。RevokedOperatoroperator(主题 1)和 holder(主题2)必须分别是运营商(operator)和持有者(holder)的地址。

注意:一个持有者可以同时拥有多个运营商

代币可以定义默认运营商。默认运营商时所有持有者隐式授权的运营商。在定义默认运营商时,不得发出AuthorizedPoerator 事件。以下是适用于默认运营商的规则:

  • 代币合约在创建时,必须定义默认运营商
  • 默认运营商必须是不变的。也就是说,代币合约绝对不能添加或删除默认运营商
  • 在定义默认运营商时,不得发出 AuthorizedOperator 事件
  • 除非持有者本身是受影响的默认运营商(除非持有者必须被允许撤销一个默认运营商)
  • 持有者必须被允许重新授权先前被撤销的默认运营商
  • 当特有的持有者明确授权或撤销默认运营商时,必须分别发出AuthorizedOperatorRevokedOperator事件

以下规则使用于任何运营商:

  • 一个地址必须始终是自己的运营商,因此地址作为其自己的运营商绝对不能被撤销
  • 如果一个地址是一个持有者的运营商,isOperatorFor 必须返回 true
  • 如果一个地址不是一个持有者的运营商,isOpreatorFor 必须返回 false
  • 当持有者按照AuthorizedOperator事件定义,授权某个地址为其操作员时,必须以正确的值触发AuthorizedOperator事件。
  • 当持有者按照RevokedOperator事件定义,撤销某个地址为其操作员时,必须以正确的值触发RevokedOperator事件。

注意:

  • 持有者可以授权已经被授权的操作员,每次都必须触发AuthorizedOperator事件
  • 持有者可以撤销已经被撤销的操作员,每次都必须触发RevokedOperator事件

AuthorizedOperator事件

event AuthorizedOperator(address indexed operator, address indexed holder);

表示将 operator 地址授权为 holder 持有者的操作员事件。

注意:此事件在操作员授权过程中,绝不能在外部触发

operator: 成为 holder 操作员的地址

holder:授权 operator 地址为操作员的授权者地址

RevokeOperator事件

event RevokeOperator(address indexed operator, address indexed holder);

表示撤销operator地址作为holder持有者的操作员事件。

注意:此事件在操作员撤销过程中,绝不能在外部触发

operator: 作为holder操作员而被撤销的地址

holder: 撤销operator 地址作为操作员的持有者的地址。

必须实现以下defaultOperatorsauthorizedOperatorrevokeOperatorisOperatorFor函数来管理运营商,代币合约可以通过其他函数来管理运营商

defaultOperators函数

function defaultOperators() external view returns (address[] memory);

获取由代币合约定义的默认操作员列表的函数。

注意:如果代币合约没有任何默认操作员,则此函数必须返回一个空列表。

identifier: 06e48538

return: 返回默认操作员地址的列表

authorizeOperator函数

function authorizeOperator(address operator) external 

将第三方操作员地址设置为 msg.senderoperator,以便代表其发送和销毁代币

注意:持有者(msg.sender)始终是其自身的操作员。此权利不得撤销。因此,如果调用此函数将 msg.sender 授权为其自身的操作员(即 opetaror == msg.sender),则此函数必须回滚。

identifier:959b8c3f

参数:

operator: 要为msg.sender设置的操作员的地址。

revokeOperator 函数

function revokeOperator(address operator) external

撤销operator地址的权力,使其无法代表 msg.sender 发送和销毁代币。

注意:持有者(msg.sender)始终是其自己的操作员。此权力不得撤销。因此,如果调用此函数将msg.sender作为要被撤销的操作员(即operator == msg.sender) ,则此函数必须回滚。

identifier:fad8b32a

参数:

operator: 要为msg.sender撤销的操作员的地址

isOperatorFor 函数

function isOperatorFoe(
	address operator,
	address holder
) external view returns (bool)

判断opetator地址是否是holder的操作员。

identifier:d95b6371

参数:

operator:可能是holder操作员的地址

holder:可能具有operator地址作为操作员的持有者地址

返回值:如果operatorholder的操作员,则返回 true 否则返回false

注意:要了解给定持有者的操作员是哪些地址,必须为每个默认操作员调用isOperatorFor,并解析涉及持有者的AuthorizedOperatorRevokedOperator事件。

发送代币

当运营商将一定数量(amount)的代币以及相关数据(data)和运营商数据(operatorData)从持有者发送到接收者时,代币合约必须应用以下规则:

  • 任何授权的操作员都可以向任何接收者发送代币(除了0x0
  • 持有者的余额必须减少amount
  • 接收者的余额必须增加amount
  • 持有者的余额在发送后必须大于或等于该amount,以确保其最终余额大于或等于零(0)
  • Token 合约必须触发Sent事件发出正确的值,如Sent事件中所定义的
  • 操作员可以在operatorData中包含信息
  • 如果持有者通过 ERC-1820 注册了 ERC777TokenSender实现,代币合约必须调用持有者的 tokensToSend钩子(hook)
  • 如果接收者通过 ERC-1820 注册了 ERC777TokenRecipient 实现,代币合约必须调用接收者的 tokensReceived 钩子(hook)
  • 在整个发送过程中,dataoperatorData 必须是不可变的,因此在调用两个钩子(hook)和发出Sent事件时必须使用相同的 dataoperatorData

代币合约在以下任何情况下都必须回滚revert

  • 操作员地址不是持有者的授权操作员
  • 发送后的持有者余额或接收者余额不是代币合约定义的粒度(granularity)的倍数。
  • 接收者是一个合约,他不通过 ERC-1820 实现 ERC777TokensRecipient
  • 持有者或接收者的地址是0x0
  • 持有者的 tokensToSend 钩子(hook)revert
  • 持有者的 tokensReceived 钩子(hook)revert

代币合约可以从多个持有者,多个接收者或两者发送代笔,在这种情况下:

  • 先前的发送规则必须适用于所有持有者和所有接收者
  • 所有增加的余额的总和必须等于发送的总金额(amount)
  • 所有减少的余额的总和必须等于发送的总金额(amount)
  • 必须为每个持有者和接收者触发对应的Sent事件,并为每对发出相应的金额
  • Sent事件的所有金额的总和必须等于发送的总金额(amount)

注意:对发送收取费用等机制被视为向多个收件人发送:预期收件人和费用收件人

注意:代币的移动可能是连锁的。例如,如果合约在收到代币后将其进一步发送到另一个地址。在这种情况下,先前的发送规则按顺序应用于每个发送

注意:发送零个(0)Token 是有效的,并且必须被视为常规发送。

实施要求:

  • Token 合约必须在更新状态之前调用tokensToSend挂钩(hook)

  • Token 合约必须在更新状态之后调用tokensReceived挂钩(hook)

    即:必须首先调用tokensToSend,然后必须更新余额以反映发送情况,最后必须调用tokenReceived。因此,tokensToSend中的balanceOf调用返回发送前的地址余额,tokensReceived中的balanceOf调用返回发送后地址的余额

注意: 数据(data)字段包含持有提供的信息,类似于常规以太坊发送交易中的数据字段。tokensToSend()挂钩、tokenReceived()或二者都可以使用该信息来决定是否希望拒绝交易。

注意:operatorData字段类似于data字段,只不过它应由操作员提供。

操作员数据operatorData必须仅由操作员提供。它更多地用于日志记录目的和特殊情况。(示例包括付款参考、支票号码、会签等。)在大多数情况下,接收者会忽略操作员数据,或者最多会记录操作员数据。

Sent事件

event Sent (
	address indexed operator,
	address indexed from,
	address indexed to,
	uint256 amount,
	bytes data,
	bytes operatorData
)

指示运营商(operator)地址从 from 地址到to地址发送的代币数量(amount)

注意:此事件不得在发送或 ERC-20 传输过程之外发出。

参数parameters:

operator:触发发送的地址

from:发送代币的持有者

to:Token 的接收者

amount:发送的代币数量

data:持有者提供的信息

operatorData:运营商提供的信息

必须实现下面描述的sendoperatorSend函数来发送 Token,代币合约可以实现其他功能来发送 Token。

send 函数

function send(address to, uint256 amount, bytes calldata data) external

从地址msg.sender向地址to发送一定量的 Token。

操作者和持有者都必须是 msg.sender

identifier: 9bd9bbc6

parameters:

to:Token 的接收者

amount:要发送的 Token 数量

data:持有者提供的信息

operatorSend函数

function operatorSend(
	address from,
	address to,
	uint256 amount,
	bytes calldata data,
	bytes calldata operatorData
) external

代表地址from 发送一定数量(amount)的 Token 到地址to

提醒:如果操作员地址不是发件人地址的授权操作员,则发送过程必须 revert

注意:frommsg.sender 可能是相同的地址,即,一个地址可以为自己调用operatorSend。此调用必须和send等效,此外,操作符可以为operatorData指定显式值(这不能用 send 函数完成)。

identifier:62ad1b83

参数 parameters:

from:正在发送的代币的持有者

to:代币的接收者

amount:要发送的代币数量

data:持有者提供的信息

operatorData:运营商提供的信息

Minting Tokens(铸造代币)

铸造代币是生产新代币的行为。ERC-777 故意没有定义铸造代币的特定功能。这一意图来自于不限制 ERC-777 标准的使用的希望,因为铸造过程通常是针对每个代币的。

尽管如此,为收件人铸造时必须遵守以下规则:

  • 可以为任何收件人(0x0地址除外)铸造代币
  • 总供应量必须增加铸造代币的数量
  • 0x0的余额不得减少
  • 接收者的余额必须增加铸造代币的数量
  • 代币合约必触发Minted事件,并具有Minted事件中定义的正确值
  • 如果接收者通过 ERC-1820 注册了 ERC777TokenReceipent 实现,则代币合约必须调用接收者的 tokensReceived 钩子(hook)
  • 数据dataoperatorData在整个铸造过程中必须时不可变的——因此必须使用相同的数据和operatorData来调用tokensReceived钩子(hook)并触发Minted事件

在以下任何情况中,代币合约在铸造时必须 revert

  • 铸币后产生的接收者余额不是代币合约定义的颗粒度(granularity)的倍数
  • 接收者是一个合约,它不通过 ERC-1820 实现 ERC777TokensReceived 接口
  • 接收者的地址为0x0
  • 接收者的tokensReceived钩子(hook) revert

注意:创建 Token 合约时的初始代币供应量,必须被视为铸造到接收初始供应量的地址的初始供应量。这意味着必须发出一个或多个Minted事件,并且必须调用接收者的tokenReceived钩子(hook)

ERC-20 兼容性要求:

虽然铸造时不得发出 Sent 事件,但如果代币合约向后兼容 ERC-20 ,则应按照 ERC-20 标准中的定义触发from 参数设置为0x0Transfer事件。

代币合约可以同时为多个接收者铸造代币,在这种情况下:

  • 之前的铸币规则必须适用于所有收件人
  • 所有增加的余额的总和必须等于铸造总量
  • 必须为每个接收者触发Minted事件,并为每个接收者发出相应的金额
  • 铸造事件中所有金额的总和必须等于铸造总量

注意:铸造零(0)个代币时有效的,并且必须被视为常规铸造

注意:在发送或销毁期间,数据由持有者提供,但不适用于铸币厂。在这种情况下,数据可以由代币合约或运营商提供,例如,以确保成功铸造给期望特定数据的持有者。

注意:operatorData字段包含运营商提供的信息,类似于常规以太坊发送交易中的数据字段。tokensReceived()挂钩可以使用该信息来决定是否希望拒绝交易。

Minted事件

event Minted(
	address indexed operator,
	address indexed to,
	uint256 amount,
	bytes data,
	bytes operatorData
);

由运营商地址指示to地址铸造的代币数量

注意:此事件不得在铸币过程之外发出。

parameters:

operaotr:触发铸币厂的地址

to:Token 的接收者

amount:铸造的代币数量

data:为接收者提供的信息

OperatorData:运营商提供的信息

Burning Tokens(销毁代币)

销毁代币是销毁现有代币的行为。ERC-777 明确定义了两个销毁代币的函数(burnoperatorBuen)。这些功能有助于将销毁过程集中到钱包和 dapp 中。然而,代币合约可能会阻止部分或所有持有者以任何理由销毁代币。代币合约还可以定义其他函数来销毁代币。

销毁持有者的代币时必须遵循以下规则:

  • 代币可以从任何持有者地址(0x0除外)销毁
  • 总供应量(totalSupply)必须减少代币销毁量
  • 0x0的余额不得增加
  • 持有者的余额必须减去销毁的代币数量
  • 代币合约必须触发一个Burned 事件,并具有Burned事件中定义的正确值
  • 如果持有者通过 ERC-1820 注册了 ERC777TokenSender 实现,则代币合约必须调用持有者的 tokensToSend 钩子(hook)
  • 在整个销毁过程中,operatorData必须时不可变的,因此必须使用相同的operatorData来调用tokensToSend钩子(hook) 并触发Burned事件

在以下任何情况下销毁时,代币合约必须 revert

  • 该运营商地址不是持有人的授权运营商
  • 销毁后产生的持有者余额不是代币合约定义的颗粒度(granularity)的倍数
  • 持有者的余额小于要销毁的代币数量(即导致持有者的余额为负)
  • 持有者的地址是0x0
  • 持有者的tokensToSend钩子(hook) revert

ERC-20 兼容性要求

在销毁的过程中不得触发Sent事件;如果代币合约启用了ERC-20,则应该发出 to 参数设置为 0x0 的 Transfer 事件。ERC-20 标准没有定义销毁代币的概念,但这是一种普遍接受的做法。

代币合约可以同时为多个持有者销毁代币,在这种情况下:

  • 之前的销毁规则必须适用于每个持有者
  • 所有减少的余额之和必须等于销毁总量
  • 必须为每个持有者触发Burn事件,并为每个持有者发出相应的金额
  • Burned事件的所有金额之和必须等于销毁总量(amount)

注意:销毁零(0)个代币是有效的,并且必须被视为正常销毁

注意:数据字段必须包含持有者提供的信息,类似于常规以太发送交易中的数据字段。tokensToSend()钩子(hook),tokensReceived()或者二者都可以使用该信息来决定是否希望拒绝交易。

注意:operatorData字段类似于 data 字段,只不过它应由操作员提供。

Burned 事件

event Burned(
	address indexed operator,
	address indexed from,
	uint256 amount,
	bytes data,
	bytes operatorData
);

表示运营商地址从 from 地址销毁的代币数量

注意:此事件不得在销毁过程之外发出

parameters:

operator:触发销毁的地址

from:被销毁的代币的持有者

amount:销毁的代币数量

data:持有者提供的信息

operatorData:运营商提供的信息

必须实现下面描述的burnoperatorBurn函数来销毁代币。代币合约可以实现其他功能来销毁代币。

burn 函数

function burn(uint256 amount, bytes calldata data) external

从地址msg.sender销毁一定数量的代币。

操作者和持有者都必须是 msg.sender

identifier:fe9f9303

parameters

amount:要销毁的代币数量

data:持有者提供的信息

operatorData函数

function operatorData(
	address from, 
	uint amount, 
	bytes calldata date, 
	bytes calldata operatorData
) external 

销毁地址fromamount数量的代币。

提醒:如果操作员地址不是收件人地址的授权操作员,则销毁过程必须 revert

identifier:fc673c4f

parameters

from:被销毁的代币的持有者

amount:销毁的代币金额

data:持有者提供的信息

operatorData:运营商提供的信息。

注意:操作员可以通过operatorData传递任何信息。operatorData必须仅由操作员提供

注意:frommsg.sender 可能是相同的地址。即一个地址可以为自己调用 operatorBurn。此调用必须与 burn 等效,此外操作符还可以为 operatorData 指定显式值(这不能通过burn函数完成)。

ERC777TokensSendertokenToSend钩子(hook)

tokensToSend挂钩通常会通知任何减少给定持有者余额(发送或销毁)的请求。任何希望从其地址收到代币借机通知的地址(常规地址或账户地址)可以通过注册 ERC-1820 实现下面描述的 ERC777TokensSender 接口的合约地址

这是通过调用 ERC-1820 注册表上的 setInterfaceImplementer 函数来完成的,其中持有者地址为:地址ERC777TokenSender(0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895)keccak256哈希作为接口哈希,地址为以 ERC777TokensSender 作为实施者的合约

interface ERC777TokensSender {
	function tokenToSend(
	address operator,
	address from,
	address to,
	uint256 amount,
	bytes calldata userData,
	bytes calldata operatorData
	) external;
}

注意:常规地址可以注册一个不用的地址(合约的地址),代表其实现接口。合约可以注册其他地址或另一个合约的地址,但所述地址必须代表其实现接口。

tokensToSend

function tokensToSend(
	address operator,
	address from,
	address to,
	uint256 amount,
	bytes calldata userData,
	bytes calldata operatorData
) external

通知操作员地址 从from地址到to地址发送或销毁(如果to0x0)一定量代币的请求。

注意:不得在销毁,发送或 ERC-20 传输过程之外调用此函数

identifier:75ab9782

parameters:

from:发送代币的持有者

to:发送代币的接收者(销毁为0x0)

amount:持有者余额减少的代币数量

data:持有者提供的信息

OperatorData:运营商提供的信息

调用tokensToSend挂钩(hook)时适用以下规则

  • 每个发送和销毁过程都必须调用tokensToSend挂钩
  • tokensToSend挂钩必须在状态更新之前调用,即在余额减少之前
  • operator必须时触发发送或销毁过程的地址
  • from必须时发送或销毁代币的持有者的地址
  • to必须时接收发送 Tokens 的持有者的地址
  • 销毁时to必须为0x0
  • amount必须是持有者发送或销毁的代币数量
  • data必须包含提供给发送或销毁过程的额外信息(如果有)。
  • operatorData必须包含触发余额减少的地址提供的额外信息(如果有)。
  • 持有者可以通过 revert 来阻止转账或者销毁过程(即拒绝从其账户中提取代币)

注意:多个持有者可以使用 ERC777TokensSender 的相同实现

注意:一个地址在任何给定时间最多可以为所有 ERC-777 代币注册一个实现,因此,ERC777TokensSender 必须期望被不同的代币合约调用。tokensToSend调用的msg.sender预计是代币合约的地址。

ERC-20 兼容性要求:

该钩子优先于 ERC-20,并且在调用 ERC-20 的 transfertransferFrom 事件时必须调用(如果已经注册)。当从传输中调用时,操作符必须与 from 的值相同。当从传输中调用时,操作符必须与 from 的值相同。当从transferFrom 调用时,operator必须是发出transferFrom调用的地址

ERC777TokensRecipienttokensReceived钩子

tokensReceived 挂钩通知给定收件人的余额(发送和铸造)的任何增量。任何希望收到代币信用通知的地址(常规地址或合约地址)都可以通过 ERC-1820 注册实现下面描述的 ERC777TokensRecipient 接口合约的地址。

这是通过调用 ERC-1820 注册表上的 setInterfaceImplement 函数来完成的,其中接收者地址为地址,以 ERC777TokensRecipient(0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b)keccak256 哈希作为接口哈希,以及作为实现者实现 ERC777TokensRecipient 的合约的地址

interface ERC777TokensRecipient {
	function tokensReceived(
		address operator,
		address from,
		address to,
		uint256 amount,
		bytes calldata data,
		bytes calldata operatorData
	) external;
}

如果接收者是合约,尚未注册ERC777TokensPecipient 实现,那么代币合约:

  • 如果从 mintsend 调用 tokensReceived 挂钩,则必须 revert
  • 如果从 ERC20 transfertransferFrom 调用中调用 tokensReceived 挂钩,则应继续处理交易

注意: 常规地址可以注册一个不同的地址(合约地址),代表其实现接口。合约必须注册其地址或另一个合约的地址,但所述地址必须代表其实现接口。

tokensReceived

function tokensReceived(
	address operator,
    address from,
    address to,
    uint256 amount,
    bytes calldata data,
    bytes calldata operatorData
) external

通过operator地址通知发送或者铸造(如果from0x0)从from地址到to地址的代币数量amount

注意:不得在铸币、发送或 ERC-20 传输过程之外调用此函数

identifier:0023de29

parameters:

operator:触发余额增加的地址(通过发送或铸造)

from:发送代币的持有者(对铸币厂来说是0x0

to:Token 的接收者

amount:接收者余额增加的代币数量

调用 tokensReceived 挂钩时,适用以下规则:

  • 每个发送和铸造过程都必须调用 tokensReceived挂钩
  • tokensReceived 挂钩必须在状态更新后调用,即余额增加后
  • operator必须是触发发送或铸造过程的地址
  • from必须是其代币被发送的持有者地址
  • 对于铸造来说,from必须是0x0
  • to必须是接收 Token 的接收者地址
  • amount必须是接收者发送或铸造的代币数量
  • data数据必须包含提供发送或铸造进程的额外信息(如果有)
  • operatorData必须包含触发余额增加的地址提供的额外信息(如果有)。
  • 持有者可以通过revert来阻止发送或铸造过程(即拒接接收 Token)

注意:对个持有者可以使用相同的 ERC777TokensRecipient 实现

注意:一个地址在任何给定时间最多可以为所有 ERC-777 代币注册一个实现。因此,ERC777TokensRecipient必须期望被不同的代币合约调用。tokensReceived调用的msg.sender预计是代币合约的地址。

ERC-20 兼容要求:

该钩子优先于 ERC-20,并且在调用 ERC-20 的 transfertransferFrom事件时必须触发(如果已注册)。当从传输中调用时,操作符必须与 from 的值相同。当从transferFrom 调用时,operator必须是发出transferFrom调用的地址。

Gas 消耗注意事项

Dapp 和 钱包应该首先使用 eth_estimateGas 估计发送、铸造或消耗代币时所需的 Gas 量,以避免在交易过程中耗尽 Gas。

image image-20231112171857503 image-20231112172242665 image-20231112172250126 image-20231112172004849 image-20231112172021198
Color beige米色 white白色 light浅灰 dark grey 深灰 dark 黑色
Hex #C99D66 #FFFFFF #EBEFF0 #3C3C3D #000000

这个 Logo 可以使用、修改、挑战,以促进有效的 ERC-777 代币实施和 ERC-777 兼容技术,例如钱包和 dapps。

ERC-777 代币合约作者可以根据此 Logo 为其代币创建特定 Logo

该 Logo 不得用于广告、促销或以任何形式关联不符合 ERC-777 的技术(例如 Token)

该标准的 Logo 可以在 /assets/eip-777/logo 文件夹中找到 SVGPNG 格式。 PNG 版本的徽标提供了几种以像素为单位的尺寸。 如果需要,可以通过从 SVG 转换为 PNG 来创建其他尺寸。

理由

该标准的主要目的是解决 ERC-20 的一些缺点,同时保持与 ERC-20 的向后兼容性,并避免 EIP-223 的问题和漏洞。

下面是有关该标准主要方面的决定的理由。

注意:该标准的作者之一 Jacques Dafflon(0xjac)共同撰写了有关该标准的硕士论文,该论文的详细内容超出了标准的直接合理范围,并且可以提供有关某些方面或决策的进一步说明。

生命周期

ERC-777 不仅仅是发送代币,他还定义了代币的整个生命周期,从铸造过程开始,然后是发送过程,最后是销毁过程。

明确定义生命周期对于一致性和标准性非常重要,尤其是当价值源自稀缺性时。相比之下,在查看某些 ERC-20 代币时,可以观察到 totalSupply返回的值与实际流通供应量之间存在差异,因为该标准没有明确定义创建和销毁代币的过程

数据

铸造、发送和销毁过程都可以利用传递到任何动作(铸造、发送、销毁)的DataoperatorData字段。对于简单的用例,这些字段可能为空,或者他们可能包含与代币转移相关的有价值的信息,类似于发送者或银行本身附加到银行转账的信息。

数据字段(data)的使用同样存在于其他标准天(例如 ERI-223)中,并且是审查该标准的多个社区成员要求的。

挂钩(钩子、Hook)

在大多数情况下,ERC-20 需要两次调用才能安全地将大量代币转移到合约而不锁定它们。发送人使用approve函数进行调用,收件人使用transferFrom进行调用。此外,这需要各方之间进行额外的沟通,而这种沟通并没有明确定义。最后持有者可能会对transferapprove/transferFrom感到困惑。使用前者将代币转移到合约很可能会导致代币被锁定。

挂钩(hooks)可以简化发送过程,并提供单一方式将 Token 发送给任何收件人。由于tokensReceived钩子,合约能够做出反应并防止在接受时锁定代币。

持有者更好的控制

tokensReceived挂钩还允许持有者拒绝接受某些代币。这为持有者提供了更大的控制权,他们可以通过某些参数(例如位于dataoperatorData字段中的参数)接受或拒绝传入代币。

遵循相同的意图并根据社区的建议,添加了tokenToSend挂钩来控制和阻止传出代币的移动。

ERC-1820 注册表

ERC-1820 注册表允许持有者注册它们的钩子,事先检查了连接钩子和持有人的其他替代方案

第一个是在发件人或收件人的地址定义挂钩。这种方法类似于 EIP-223,EIP-223 提出了在接受代币时在接收者合约上调用 tokenFallback 函数,但通过依赖 ERC-165 进行接口检测对齐进行了改进。虽然实施起来很简单,但这种方法有一些限制。特别是,发送者和接收者必须是合约才能提供钩子的实现。EOA 账户无法从中受益。现有合约很可能不兼容,因为他们无疑不知道并且没有定义新的钩子。因此现有的智能合约基础设施(例如可能持有大量以太币和代币的多重签名钱包)需要迁移到新的更新合同。

考虑的第二种方法时使用 ERC-672,它为使用反向 ENS 的地址提供伪内省。然而,这种方法严重依赖 ENS,在此基础上还需要实现反向查找。对这种方法的分析立即揭示了一定程度的复杂性和安全问题,这将超越该方法的好处。

本标准中使用的第三种解决方案是以来一个唯一的注册表,其中任何地址都可以注册代表其实现挂钩合约的地址。这种方法的优点是 EOA 账户和合约都可以从钩子中受益,包括可以依赖于代理合约上部署的钩子的现有合约。

我们决定将该注册表保留在单独的 EIP 中,以免使该标准过于复杂。更重要的是,注册表以灵活的方式设计,以便其他 EIP 和智能合约基础设施可以在 ERC-777 和代币领域之外的自身用例中受益。该注册表的第一个提案是 ERC-20.不幸的是,Solidity 语言升级到 0.5 及更高版本时出现的问题导致注册表的一个单独部分出现错误,需要进行更改。这是在最后一次通话结束后发现的。避免创建单独的 EIP (例如 ERC820a)的尝试遭到拒绝。因此,用于 ERC-777 的注册表标准变成了 ERC-1820。ERC-1820 和 ERC-820 在功能上时等效的。ERC-1820 仅包含对新版本 Solidity 的修复。

运营商(Operator)

该标准Operator的概念定义为移动代币的任何地址。虽然直观上每个地址都会移动自己的代币,但将持有者和运营商的概念分开可以提供更大的灵活性。这主要源于该标准定义了一种机制,让持有者可以让其他地址成为它们的运营者。此外,与 ERC-20 中未明确定义已批准地址的作用的批准调用不同,ERC-777 详细说明了运营商的意图以及与运营商的互动,包括运营商获得批准的义务以及任何持有人的不可撤销的权力撤销操作员

默认运营商(Default Operator)

根据社区对预先批准的运营商的要求添加了默认运营商(Default Operator)。即默认情况下为所有持有者批准的运营商。出于明显的安全原因,Default Operator 列表是在代币合约创建时定义的,并且无法更改。任何持有者仍然有权撤销 Default Operator 。Default Operator 的明显优势之一就是允许代币的无以太移动。默认运营商还提供其他可用性优势,例如允许代币提供商以模块化方式提供功能,并降低持有者使用运营商提供的功能的复杂性,

向后兼容性

该 EIP 不会引入向后不兼容性,并且向后兼容较久的 ERC-20 代币标准。

该 EIP 不使用transfertransferFrom,而是使用sendoperatorSend,以避免在编译所使用的代币标准时出现混乱和错误。

该标准允许同时实现 ERC-20 功能transfertransferFromapproveallowance,使代币与 ERC-20 完全兼容。

Token 可以实现 decimals() 以向后兼容 ERC-20.如果实现,它必须始终返回 18。

因此,代币合约可以并行实施 ERC-20 和 ERC-777.view函数的规范(eg.namesymbolbalanceOftotalSupply)和内部数据(例如余额的映射)重叠,不会出现问题。但请注意,以下函数在 ERC-777 中式强制性的,并且必须实现:namesymnolbalanceOftotalSupplydecimals不是 ERC-777 标准的一部分)

两个标准的状态修改功能是解耦的,并且可以彼此独立运行。 请注意,ERC-20 函数应仅限于从旧合约中调用。

如果代币实现了 ERC-20,他必须通过 ERC-1820 使用自己的地址注册 ERC20Token接口。这是通过调用 ERC1820 注册表上的 setInterfaceImplementer函数来完成的,其中代币合约地址作为地址和实现者,ERC20Tokenkeccak256哈希值(0xaea199e31a596269b42cdafd93407f14436db6e4cad65417994c2eb37381e05a)作为接口哈希值。

如果合约由启用和禁止 ERC20 功能的开关,则每次触发该开关时,代币必须通过 ERC1820 相应地为自己的地址注册或取消注册 ERC20Token 接口。取消注册意味着调用setInterfaceImplementer,以代币合约地址为地址,以ERC20Tokenkeccak256哈希为接口哈希,以0x0为实现者。(有关更多详细信息,请参阅为 ERC-1820 中的地址设置接口。)

实施 ERC-20 的新合约的区别在于 tokensToSendtokensReceived 挂钩优先于 ERC-20。即使使用 ERC-20 transfertransferFrom 调用,代币合约也必须通过 ERC-1820 检查 fromto 地址是否分别实现 tokensToSendtokensReceived 挂钩。如果实现了任何钩子,则必须调用它。请注意,在合约上调用 ERC-20 转账时,如果合约未实现 tokensReceived,则转账调用仍应被接受,即使这意味着代币可能会被锁定。

下表总结了代币合约在通过 ERC-777 和 ERC-20 发送,铸造和转移代币时必须采取的不同操作:

ERC1820 to地址 ERC777发送和铸造 ERC20transfer/transferFrom
ERC777TokensRecipient registered regular
contract
MUST call tokensReceived MUST call tokensReceived
ERC777TokensRecipient not registered regular
contract
continue
MUST revert
continue
SHOULD continue1
1. 为了清晰可见,交易应该继续,因为 ERC20 不知道挂钩

但是,这可能会导致 Token 意外锁定。如何避免意外锁定 Token 至关重要,则交易可能会 revert

如果未实现tokensToSend,则无需采取任何特定操作。该转移必须继续进行,并且只有在不满足其他条件(例如缺乏资金或收到的代币 revert(如果存在))时才能取消。

在发送、铸造、销毁期间,必须触发相应的SendMintedBurned事件。此外,如果代币合约声明它通过 ERC-1820 实现 ERC20Token,则代币合约必须发出用于铸造和销毁的Transfer事件,并且必须发出用于发送的 Transfer 事件(如 ERC-20 标准中指定的)。在 ERC-20 传输或传输功能期间,必须发出有效的发送事件。

因此,对于代币的任何移动,都可能会发出两个事件:ERC-20 Transfer 和 ERC-777 SendMintedBurned(取决于移动类型)。第三方开发人员必须小心,不要将这两个事件视为单独的动作。作为一般规则,如果应用程序将代币视为 ERC20 Token,则仅必须考虑 Transfer 事件。如果应用程序将代币视为 ERC777 Token,则仅必须考虑 SendMintedBurned事件。