ERC-7144: 具有交易验证步骤的 ERC-20
为转账和授权调用添加新的验证步骤,以在钱包被盗时实现安全措施。
Authors | Eduard López i Fina (@eduardfina) |
---|---|
Created | 2023-05-07 |
Requires | EIP-20 |
摘要
本标准是 ERC-20 的扩展。它定义了新的验证功能以避免钱包被盗:每个 transfer
或 approve
都将被锁定,等待验证。
动机
区块链的力量同时也是它的弱点:让用户对其数据负全部责任。
目前存在许多 Token 被盗的情况,而当前的 Token 防盗方案(例如将 Token 转移到冷钱包)使得 Token 使用起来不方便。
在每次 transfer
和 approve
之前都有一个验证步骤,这将使智能合约开发者有机会创建安全的 Token 防盗方案。
一个实现示例是一个系统,其中验证器地址负责验证所有智能合约交易。
该地址将连接到一个 dApp,用户可以在其中查看其 Token 的验证请求并接受正确的请求。
仅授予该地址验证交易的权限将使系统更加安全,窃取 Token 的小偷必须同时拥有用户的地址和验证器地址。
规范
本文档中的关键词“必须”,“禁止”,“必需”,“应”,“不应”,“应该”,“不应该”,“推荐”,“可以”和“可选”应按照 RFC 2119 中的描述进行解释。
符合 ERC-20 规范的合约可以实现此 EIP。
所有更改 Token 所有权的操作,如 transfer
/ transferFrom
,应创建一个等待验证的 TransferValidation
并发出 ValidateTransfer
,并且不应转移 Token。
所有启用批准以管理 Token 的操作,如 approve
,应创建一个等待验证的 ApprovalValidation
并发出 ValidateApproval
,并且不应启用批准。
当转账由已批准的帐户而不是所有者调用时,必须直接执行,无需验证。这是为了适应所有需要批准才能直接移动你的 Token 的当前项目。
验证 TransferValidation
或 ApprovalValidation
时,有效字段必须设置为 true,并且不得再次验证。
验证 TransferValidation
的操作应更改 Token 的所有权。
验证 ApprovalValidation
的操作应启用批准。
合约接口
interface IERC7144 {
struct TransferValidation {
// 所有者的地址。
address from;
// 接收者的地址。
address to;
// Token 数量。
uint256 amount;
// 是否是有效的转移。
bool valid;
}
struct ApprovalValidation {
// 所有者的地址。
address owner;
// 消费者的地址。
address spender;
// 批准的 Token 数量。
uint256 amount;
// 是否是有效的批准。
bool valid;
}
/**
* @dev 当请求新的转移验证时发出。
*/
event ValidateTransfer(address indexed from, address indexed to, uint256 amount, uint256 indexed transferValidationId);
/**
* @dev 当请求新的批准验证时发出。
*/
event ValidateApproval(address indexed owner, address indexed spender, uint256 amount, uint256 indexed approvalValidationId);
/**
* @dev 如果此合约是验证器 ERC20,则返回 true。
*/
function isValidatorContract() external view returns (bool);
/**
* @dev 使用转移 ID 返回转移验证结构体。
*
*/
function transferValidation(uint256 transferId) external view returns (TransferValidation memory);
/**
* @dev 使用批准 ID 返回批准验证结构体。
*
*/
function approvalValidation(uint256 approvalId) external view returns (ApprovalValidation memory);
/**
* @dev 返回创建的转移验证的总量。
*
*/
function totalTransferValidations() external view returns (uint256);
/**
* @dev 返回创建的转移验证的总量。
*
*/
function totalApprovalValidations() external view returns (uint256);
}
isValidatorContract()
函数必须实现为 public
。
transferValidation(uint256 transferId)
函数可以实现为 public
或 external
。
approvalValidation(uint256 approveId)
函数可以实现为 public
或 external
。
totalTransferValidations()
函数可以实现为 pure
或 view
。
totalApprovalValidations()
函数可以实现为 pure
或 view
。
原理
普遍性
该标准仅定义了验证函数,但未定义应如何使用它们。它将验证定义为内部验证,并让用户决定如何管理它们。
一个例子是可以有一个连接到 dApp 的地址验证器,以便用户可以管理他们的验证。
该验证器可以用于所有 Token,也可以仅用于某些用户。
它也可以用作现有 ERC-20 的包装智能合约,允许与现有 Token 进行 1/1 转换。
扩展性
该标准仅定义了验证函数,但未定义必须使用哪种系统对其进行验证。第三方协议可以根据需要定义如何调用这些函数。
向后兼容性
本标准是 ERC-20 的扩展,与除 transfer
/ transferFrom
/ approve
之外的所有操作兼容。
这些操作将被覆盖以创建验证请求,而不是转移 Token 或启用批准。
参考实现
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./IERC7144.sol";
/**
* @dev ERC7144 的实现
*/
contract ERC7144 is IERC7144, ERC20 {
// 从转移 ID 到转移验证的映射
mapping(uint256 => TransferValidation) private _transferValidations;
// 从批准 ID 到批准验证的映射
mapping(uint256 => ApprovalValidation) private _approvalValidations;
// 转移验证的总数
uint256 private _totalTransferValidations;
// 批准验证的总数
uint256 private _totalApprovalValidations;
/**
* @dev 通过设置 Token 集合的 `name` 和 `symbol` 来初始化合约。
*/
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){
}
/**
* @dev 如果此合约是验证器 ERC721,则返回 true。
*/
function isValidatorContract() public pure returns (bool) {
return true;
}
/**
* @dev 使用转移 ID 返回转移验证结构体。
*
*/
function transferValidation(uint256 transferId) public view override returns (TransferValidation memory) {
require(transferId < _totalTransferValidations, "ERC7144: 无效的转移 ID");
TransferValidation memory v = _transferValidation(transferId);
return v;
}
/**
* @dev 使用批准 ID 返回批准验证结构体。
*
*/
function approvalValidation(uint256 approvalId) public view override returns (ApprovalValidation memory) {
require(approvalId < _totalApprovalValidations, "ERC7144: 无效的批准 ID");
ApprovalValidation memory v = _approvalValidation(approvalId);
return v;
}
/**
* @dev 返回创建的转移验证的总量。
*
*/
function totalTransferValidations() public view override returns (uint256) {
return _totalTransferValidations;
}
/**
* @dev 返回创建的批准验证的总量。
*
*/
function totalApprovalValidations() public view override returns (uint256) {
return _totalApprovalValidations;
}
/**
* @dev 返回 `transferId` 的转移验证。如果转移不存在,则不会恢复
*/
function _transferValidation(uint256 transferId) internal view virtual returns (TransferValidation memory) {
return _transferValidations[transferId];
}
/**
* @dev 返回 `approvalId` 的批准验证。如果转移不存在,则不会恢复
*/
function _approvalValidation(uint256 approvalId) internal view virtual returns (ApprovalValidation memory) {
return _approvalValidations[approvalId];
}
/**
* @dev 使用转移 ID 验证转移。
*
*/
function _validateTransfer(uint256 transferId) internal virtual {
TransferValidation memory v = transferValidation(transferId);
require(!v.valid, "ERC721V: 转移已验证");
super._transfer(v.from, v.to, v.amount);
_transferValidations[transferId].valid = true;
}
/**
* @dev 使用批准 ID 验证批准。
*
*/
function _validateApproval(uint256 approvalId) internal virtual {
ApprovalValidation memory v = approvalValidation(approvalId);
require(!v.valid, "ERC7144: 批准已验证");
super._approve(v.owner, v.spender, v.amount);
_approvalValidations[approvalId].valid = true;
}
/**
* @dev 创建从 `from` 到 `to` 的 `tokenId` 的转移请求。
*
* 要求:
*
* - `from` 不能为零地址。
* - `to` 不能为零地址。
*
* 发出 {ValidateTransfer} 事件。
*/
function _transfer(
address from,
address to,
uint256 amount
) internal virtual override {
require(from != address(0), "ERC7144: 从零地址转移");
require(to != address(0), "ERC7144: 转移到零地址");
if(_msgSender() == from) {
TransferValidation memory v;
v.from = from;
v.to = to;
v.amount = amount;
_transferValidations[_totalTransferValidations] = v;
emit ValidateTransfer(from, to, amount, _totalTransferValidations);
_totalTransferValidations++;
} else {
super._transfer(from, to, amount);
}
}
/**
* @dev 创建从 `owner` 操作 `amount` 的批准请求
*
* 发出 {ValidateApproval} 事件。
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual override {
require(owner != address(0), "ERC7144: 从零地址批准");
require(spender != address(0), "ERC7144: 批准到零地址");
ApprovalValidation memory v;
v.owner = owner;
v.spender = spender;
v.amount = amount;
_approvalValidations[_totalApprovalValidations] = v;
emit ValidateApproval(v.owner, spender, amount, _totalApprovalValidations);
_totalApprovalValidations++;
}
}
安全注意事项
正如规范中定义的那样,更改 Token 所有权或启用批准以管理 Token 的操作应创建一个等待验证的 TransferValidation
或 ApprovalValidation
,并且不应转移 Token 或启用批准。
考虑到这一前提,负责验证 TransferValidation
或 ApprovalValidation
的操作必须受到应用系统所需的最大安全性的保护。
例如,一个有效的系统是存在一个负责验证交易的验证器地址。
再举一个例子,每个用户都可以选择他的验证器地址的系统也是正确的。
在任何情况下,安全性的重要性在于,未经选定系统的许可,任何地址都无法验证 TransferValidation
或 ApprovalValidation
。
版权
版权和相关权利已通过 CC0 放弃。
Citation
Please cite this document as:
Eduard López i Fina (@eduardfina), "ERC-7144: 具有交易验证步骤的 ERC-20 [DRAFT]," Ethereum Improvement Proposals, no. 7144, May 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7144.