一文说清楚ERC1363规范为什么是ERC20的理想替代

  • Louis
  • 发布于 2024-07-13 12:03
  • 阅读 1723

为什么会有ERC1363标准?我们知道,ERC20是用于在以太坊区块链上创建和管理代币的流行标准。它定义了一组函数和事件,用于转账、批准和查询代币余额。虽然ERC20标准已经成功用于创建各种代币,但它也存在一些已知的缺点和漏洞,这些漏洞被黑客利用,盗取了很多代币,让项目损失惨重。

相关背景:为什么会有ERC1363标准?

我们知道,ERC20是用于在以太坊区块链上创建和管理代币的流行标准。它定义了一组函数和事件,用于转账、批准和查询代币余额。虽然 ERC20 标准已经成功用于创建各种代币,但它也存在一些已知的缺点和漏洞,这些漏洞被黑客利用,盗取了很多代币,让项目损失惨重。

ERC1363 是对 ERC20 标准的扩展,它引入了在转账或批准后执行回调函数的功能。这意味着符合 ERC1363 的代币除了具有 ERC20 代币的所有功能之外,还具有以下一些特点和优势

ERC1363的一些优势:

提高可组合性: ERC1363 代币可以与其他智能合约无缝交互,并在转账或批准发生时触发操作。这使得它们适用于需要代币与其他去中心化应用程序 (dApp) 集成的各种用例。

增强安全性: ERC1363 代币可以用来防止代币丢失或锁定在合约中。例如,在将代币转账给接收者合约之前,可以调用回调函数来检查接收者合约是否有效。这可以帮助防止代币意外发送到无法处理它们的合约中。

改善用户体验: ERC1363 代币可以用于创建更原子性的交易,并减少用户需要确认的交易数量。例如,ERC1363 代币可以用于将代币转账和批准操作组合成单个交易。这可以提高交易速度并改善用户体验。

TokenBank合约存在问题:

手把手教你实现TokenBank智能合约中,假设用户向合约转账 ERC20 代币。由于没有机制可以查看是谁进行了转账,智能合约无法为转账用户记账。

一个比较典型的解决方案是:接收者使用 transferFrom 将代币转账给自己,但是前提条件是:代币发送方批准接收智能合约代表发送方转账代币。

contract ReceivingContract {
    function deposit(uint256 amount) external {
    // 如果未经批准或用户余额不足,将会回滚
    ERC20(token).transferFrom(msg.sender, address.this, amount);
        // 为账户记账
    deposits[msg.sender] += amount;
    }
}

存款人调用接收智能合约的函数(在上面的示例代码中为 deposit)来从发送方转账代币到合约。由于合约知道它从用户那里转账了代币,因此能够正确记账。

然而,为了批准合约转账代币,需要增加额外的交易费用。并且前置的授权操作,会让用户感觉有些繁琐。

此外,用户在批准合约后应将批准设置为零,否则存在合约被利用的风险,可能导致合约从用户那里提取更多 ERC20 代币。

预定义一个转账Hook (钩子)

转账Hook是接收智能合约中的预定义函数,当它接收到代币时将被调用。也就是说,代币合约在接收到转账指令后,先执行转账操作,执行完毕之后,会在接收地址上调用预定义函数。

如果这个预定义函数不存在、回滚或未返回预期的成功值,则这笔转账交易会回滚。

ERC1363扩展了ERC20标准,添加了转账Hook

要实现ERC1363这个标准,ERC20需要额外的函数(稍后会解释)来转账代币并同时触发接收方的转账Hook,并且接收方必须根据标准实现转账Hook。

IERC1363Receiver

对于接收ERC1363代币的合约,我们要想通知到它,它就必须实现 IERC1363Receiver,(请查看OpenZeppelin实现),其中包含一个名为 onTransferReceived 的函数:

这个函数成功的时候会返回:

bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))

pragma solidity ^0.8.20;

interface IERC1363Receiver {
    function onTransferReceived(
            address operator,
            address from,
            uint256 value,
            bytes calldata data
    ) external returns (bytes4);
}
  • operator 是发起转账的地址
  • from 是从中扣除 ERC1363 代币的账户
  • value 是转账的代币数量
  • data 由operator指定以转发给接收方

在接收合约实现此函数时,请始终检查msg.sender是否是你希望接收的ERC1363合约的代币,因为任何人都可以使用任意值调用 onTransferReceived()。

下面是一个代码实例,这个合约可以接收指定的ERC1363代币:

pragma solidity ^0.8.20;

import "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol";
import "@openzeppelin/contracts/interfaces/IERC1363.sol";

contract TokenReceiver is IERC1363Receiver {
    address internal erc1363Token;

        // 部署此合约的时候,erc1363Token合约地址作为入参传入
    constructor(address erc1363Token_) {
            erc1363Token = erc1363Token_;
    }

    mapping(address user => uint256 balance) public balances;

    function onTransferReceived(
        address operator,
        address from,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4) {
        require(msg.sender == erc1363Token, "not the expected token");
        balances[from] += value;
        return this.onTransferReceived.selector;
    }

    function withdraw(uint256 value) external {
        require(balances[msg.sender] >= value, "balance too low");
        balances[msg.sender] -= value;
        IERC1363(erc1363Token).transfer(msg.sender, value);
    }

}

接收代币的合约知道自己收到ERC20代币的传统方式是使用transferFrom函数,该函数需要首先进行代币额度批准,但是使用 ERC1363 后,合约能够知道自己已收到代币,并且还能够跳过批准步骤,因为 transferAndCall 将EOA账户通过 transfer 将代币转账给接收者合约(无需批准)并调用 onTransferReceived 函数。

ERC1363做到了通过ERC20最大化向后兼容性

这个新代币标准的问题在于现有协议无法使用它们,为了最大化向后兼容性,ERC1363 是一种 ERC20 代币,它添加了旧协议不需要使用的额外函数,并不会影响原有功能。

所有现有的 ERC20 函数:name、symbol、decimals、totalSupply、balanceOf、transfer、transferFrom、approve 和 allowance 的行为都与 ERC20 标准规定的完全一致。

ERC1363 标准添加了新函数到 ERC20,以便旧协议仍然可以与 ERC1363 代币交互,就像与 ERC20 代币一样。但是,如果需要,新协议可以利用 ERC1363 上的转账Hook。

要成为符合 ERC1363 标准的代币,代码还必须实现六个额外的函数:

  • 两个版本的 transferAndCall
  • 两个版本的 transferFromAndCall
  • 两个版本的 approveAndCall

顾名思义,这些函数将执行 ERC20 操作,然后调用接收方的Hook函数。

每个函数都有两个版本,一个带有数据参数,一个不带。数据参数是为了发送方能够将数据转发给接收合约。


// 有两个 transferAndCall 函数,
// 一个带有数据参数,一个不带

function transferAndCall(
    address to,
    uint256 value
) external returns (bool);

function transferAndCall(
    address to,
    uint256 value,
    bytes calldata data
) external returns (bool);

// 有两个 transferFromAndCall 函数,
// 一个带有数据参数,一个不带

function transferFromAndCall(
    address from,
    address to,
    uint256 value
) external returns (bool);

function transferFromAndCall(
    address from,
    address to,
    uint256 value,
    bytes calldata data
) external returns (bool);

// 有两个 approveAndCall 函数,// 一个带有数据参数,一个不带```
function approveAndCall(
    address spender,
    uint256 value
) external returns (bool);

function approveAndCall(
    address spender,
    uint256 value,
    bytes calldata da...

剩余50%的内容订阅专栏后可查看

点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Louis
Louis
web3 developer,技术交流或者有工作机会可加VX: magicalLouis