代币集大成者 - 手搓一个ERC1155合约并上线 holesky

ERC20和ERC721都是单代币标准,一个合约中只能管理一种代币。而ERC1155的设计目标是统一管理同质化代币(FungibleToken)和非同质化代币(Non-FungibleToken),提高批量操作的效率,减少Gas成本:https://learnblockchain.cn/shawn_

什么是 ERC1155

ERC20ERC721都是单代币标准,一个合约中只能管理一种代币。而 ERC1155 的设计目标是统一管理 同质化代币(Fungible Token) 和 非同质化代币(Non-Fungible Token),提高批量操作的效率,减少 Gas 成本。

ERC1155 和 ERC721 的区别

ERC721 中,每个代币都有唯一的 tokenId,但一个合约中只能支持这一种代币。而在 ERC1155 中,一个合约可以有多种代币,甚至可以同质化代币和非同质化代币共同存在。在诸如区块链游戏的场景中,管理多种物品(装备、金币)等变得更为高效和节省 gas。并且,ERC1155 支持批量转账的功能。

ERC1155 是怎么实现的?

要知道 ERC1155 是怎么实现管理多种代币,通过对比 ERC721ERC1155 的核心存储结构就可以很容易得知。

  • ERC721ERC721 中,核心存储 NFT 的结构为这一个 mapping,通过记录某个 tokenId 被哪个地址所拥有,便可标识出这个 NFT 是归属谁的。
    mapping(uint256 tokenId => address) private _owners;
  • ERC1155 而在 ERC1155 中,核心数据结构也是一个 mapping,但这是一个嵌套的 mapping。其结构为 tokenId --> 地址 --> 数量。 通过对某个 tokenId 归属进行标记,如果是同质化代币,数量可以大于 1,如果是非同质化代币(NFT),则数量固定为 1。这样就起到了同时管理同质化代币和非同质化代币的作用。
  • 同质化代币: 例如一个同质化代币,其 id0。被地址 0xbb0xcc 持有。数量分别为 23。则这个 mapping 中,一个 tokenId 则对应了两个子 mapping,存储了两个地址的持仓数量。
  • 非同质化代币: 例如一个非同质化代币,其 id1。被 0xdd 持有。因为其数量固定为 1,所以在这个 mapping 中,只能同时存在一个子 mapping,记录了 0xdd 持有一个的记录。

    mapping(uint256 id => mapping(address account => uint256)) private _balances;

    核心变量

    ERC1155 的核心状态变量非常简单,就是一个上面提到的核心存储结构加上一个额外的 mapping 作为授权机制,来保存代币授权给谁的信息。其中 _uri 保存了所有代币的元数据,可以通过 {id} 插值来进行区分。

    mapping(uint256 id => mapping(address account => uint256)) private _balances;
    
    mapping(address account => mapping(address operator => bool)) private _operatorApprovals;
    
    // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
    string private _uri;

    接口

    ERC1155 的接口及解释如下。

  • ERC1155 继承了 ERC165,提供接口检测的能力。
  • ERC721 类似,safeTransferFrom 的时候也要求对方实现 ERC1155Receiver 接口来避免代币锁定的情况出现。(ERC1155Receiver 可以看做 ERC165 的抽离处理,单独拎出来让别的合约调用时判定,并同时完成发送代币的过程)
    /**
     * @dev 返回某个 tokenId 被某个地址拥有的数量
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

     /**
     批量返回 ids 和 accounts 对应的余额
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
    给 operator 授权调用者地址的所有代币操作权限
    本质上是操作 mapping(address account => mapping(address operator => bool)) 来记录
    */
    function setApprovalForAll(address operator, bool approved) external;

    /**
    查询 account 的所有代币授权情况
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
    安全转账,代币 id,从 from 地址转账到 to 地址 value 的数量
    要求对方实现了 IERC1155Receiver 的接口,否则会失败回滚(防止发生代币锁定的危险)
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;

    /**
    安全批量转账,代币 ids[],从 from 地址转账到 to 地址 values[] 的数量
    要求对方实现了 IERC1155Receiver 的接口,否则会失败回滚(防止发生代币锁定的危险)
    */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external;

事件

ERC1155 定义的事件非常简单,只有三个。分别对应转账、批量转账、授权全部代币的场景。

    /**
    单个转账事件
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
    转账事件
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
    批量授权事件
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

代码实现

下面,我们就来进行手搓一个 ERC1155 合约并上线到 holesky 测试网。

  1. Shawn1155.sol代码编写
contract Shawn1155 is ERC1155 {
    uint256 constant MAX_ID = 10;
    constructor() ERC1155("ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/ "){
//        mint 出来给自己
        mintBatch();
    }

    // 批量 mint 出来
    function mintBatch() internal {
        uint256[] memory ids = new uint256[](MAX_ID);
        uint256[] memory amounts = new uint256[](MAX_ID);
        for(uint256 i=0;i<MAX_ID;i++){
            ids[i] = i;
            amounts[i] = 1000;
        }
        _mintBatch(msg.sender,ids,amounts,"");
    }

}
  1. Shawn1155.s.sol部署脚本
contract DeployAndTransfer is Script {
    function run() external {
        vm.startBroadcast();

        // 1. 部署合约
        Shawn1155 token = new Shawn1155();
        console2.log("Deployed Shawn1155 at:", address(token));

        // 2. 生成一个随机地址(模拟 EOA 接收者)
        address randomEOA = address(uint160(uint256(keccak256(abi.encodePacked(block.timestamp, blockhash(block.number - 1))))));
        console2.log("Random receiver:", randomEOA);

        // 3. 发送 tokenId 1,数量 100 给 randomEOA
        token.safeTransferFrom(msg.sender, randomEOA, 1, 100, "");

        vm.stopBroadcast();
    }
}
  1. 部署测试
    • 加载环境变量
source .env
  • 部署命令
    forge script script/ERC1155/Shawn1155.s.sol \
    --rpc-url $HOLESKY_RPC_URL \
    --private-key $PRIVATE_KEY \
    --broadcast \
    -vvvv

image.png 到holesky上查看 https://holesky.etherscan.io/address/0x6E6bC0328814Ed87396ACea53EB4363A5eA236DC#events

image.png

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

0 条评论

请先 登录 后评论
shawn_shaw
shawn_shaw
web3潜水员、技术爱好者、web3钱包开发工程师、欢迎交流工作机会。欢迎骚扰:vx:cola_ocean