草庐IT

代币标准--ERC1155协议源码解析

宁N分析 2023-10-31 原文

ERC1155多代币标准

ERC1155结合了ERC20和ERC721的能力,这是一个标准接口,支持开发同质化的、半同质化的、非同质化的代币和其他配置的通用智能合约。

IERC1155接口

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

// IERC1155接口同样继承了IERC165接口
interface IERC1155 is IERC165 {

TransferSingle事件

// 转移代币后触发事件,记录转移信息
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

TransferBatch事件

// 批量转移代币后触发事件,记录转移信息
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

ApprovalForAll事件

// 授权approve后触发事件,记录授权信息
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

URI事件

// URI的值改变时触发该事件,记录信息
    event URI(string value, uint256 indexed id);

balanceOf函数

// 获取account账户对应代币id拥有的数量
    function balanceOf(address account, uint256 id) external view returns (uint256);

balanceOfBatch函数

// 获取账户列表对用的token余额,相当于多次balanceOf
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external
        view
        returns (uint256[] memory);

setApprovalForAll函数

// 设置给operate的授权
    function setApprovalForAll(address operator, bool approved) external;

isApprovedForAll函数

// 判断operate是否有account账号的授权
    function isApprovedForAll(address account, address operator) external view returns (bool);

safeTransferFrom函数

// 从from账户转移amount数量的代币id到to地址
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

safeBatchTransferFrom函数

// 批量转移token,相当于调用多次safeTransfer
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}

ERC1155

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/ERC1155.sol)

pragma solidity ^0.8.0;

import "./IERC1155.sol";
import "./IERC1155Receiver.sol";
import "./extensions/IERC1155MetadataURI.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/introspection/ERC165.sol";


contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
    using Address for address;
    // 代币id与拥有者地址及其该代币余额的映射
    mapping(uint256 => mapping(address => uint256)) private _balances;
    // 地址之间的授权状态的映射
    mapping(address => mapping(address => bool)) private _operatorApprovals;
    // 相同类型的代币 _uri是一致的
    string private _uri;
   // 构造函数 初始化uri
   constructor(string memory uri_) {
       _setURI(uri_);
   }

supportsInterface函数

// 检查合约是否实现该接口
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC1155).interfaceId ||
            interfaceId == type(IERC1155MetadataURI).interfaceId ||
            super.supportsInterface(interfaceId);
    }

uri函数

// 获取代币的uri,uri指向是代币的元数据,例如图片信息
    function uri(uint256) public view virtual override returns (string memory) {
        return _uri;
    }

balanceOf函数

// 获取account地址的id代币的数量
    function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
    // 检查account不为空地址
        require(account != address(0), "ERC1155: address zero is not a valid owner");
        return _balances[id][account];
    }

balanceOfBatch函数

// 返回account账号列表对应的id代币的数量
    function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
        public
        view
        virtual
        override
        returns (uint256[] memory)
    {
        // 检查账号列表长度与代币id列表长度一致
        require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");
        
        // 创建一个列表长度与account相同
        uint256[] memory batchBalances = new uint256[](accounts.length);
       
       // 获取账号对应的代币id的数量
        for (uint256 i = 0; i < accounts.length; ++i) {
            batchBalances[i] = balanceOf(accounts[i], ids[i]);
        }

        return batchBalances;
    }

setApprovalForAll函数

// 设置对operate的授权approve状态
    function setApprovalForAll(address operator, bool approved) public virtual override {
    // 调用_setApprovalForAll函数
        _setApprovalForAll(_msgSender(), operator, approved);
    }

isApprovedForAll函数

    // 获取account地址对operate地址的授权状态
    function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[account][operator];
    }

safeTransferFrom函数

// 转移mount数量的id代币 从from地址到to地址
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public virtual override {
    // 检查from是否是合约调用者地址,或者from账号有approve授权给合约调用者
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: caller is not token owner or approved"
        );
        // 调用_safeTransferFrom函数,转移代币
        _safeTransferFrom(from, to, id, amount, data);
    }

safeBatchTransferFrom函数

// 批量转移代币
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual override {
        // 检查from是否是合约调用者地址,或者from账号有approve授权给合约调用者
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: caller is not token owner or approved"
        );
        // 调用_safeBatchTransferFrom函数转移代币
        _safeBatchTransferFrom(from, to, ids, amounts, data);
    }

_safeTransferFrom函数

// 转移mount数量的id代币 从from地址到to地址
    function _safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
    // 检查to地址是否为空地址
        require(to != address(0), "ERC1155: transfer to the zero address");
// 获取当前合约调用者地址
        address operator = _msgSender();
        // 获取id列表,该列表只有一个元素 ids[0] = id
        uint256[] memory ids = _asSingletonArray(id);
        // 获取amount列表该列表只有一个元素 amounts[0] = amount
        uint256[] memory amounts = _asSingletonArray(amount);
        // 转移代币前执行的函数
        _beforeTokenTransfer(operator, from, to, ids, amounts, data);
        // 获取from地址的id代币数量
        uint256 fromBalance = _balances[id][from];
        // 检查 from地址的id代币数量fromBalance 是否大于等于要转移的数量amount
        require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
        // 减去转移走的数量 重新写入代币数量
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }
        // 加上得到的代币数量,重新写入值
        _balances[id][to] += amount;
        // 触发转移单个代币时间,记录信息
        emit TransferSingle(operator, from, to, id, amount);
        // 代币转移后执行函数
        _afterTokenTransfer(operator, from, to, ids, amounts, data);
        // 检查接收的合约地址是否实现IERC1155Receiver接口
        _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
    }

_safeBatchTransferFrom函数

    function _safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
    // 检查账号列表长度与代币id列表长度一致
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
    // 检查to地址是否为空地址
        require(to != address(0), "ERC1155: transfer to the zero address");
   // 获取当前合约调用者地址
        address operator = _msgSender();
  // 代币转移前执行函数
        _beforeTokenTransfer(operator, from, to, ids, amounts, data);
 
        for (uint256 i = 0; i < ids.length; ++i) {
        // 获取代币id
            uint256 id = ids[i];
        // 获取转移数量值
            uint256 amount = amounts[i];
        // 获取当前账号与代币对应数量
            uint256 fromBalance = _balances[id][from];
        // 检查余额是否大于转移数量
            require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
            // 减去转移数量,重新写入值
            unchecked {
                _balances[id][from] = fromBalance - amount;
            }
            // 加上转移数量,重新写入值
            _balances[id][to] += amount;
        }
    // 触发转移单个代币事件,记录信息
        emit TransferBatch(operator, from, to, ids, amounts);
    // 代币转移后执行函数
        _afterTokenTransfer(operator, from, to, ids, amounts, data);
   // 检查接收的合约地址是否实现IERC1155Receiver接口
        _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
    }

_setURI函数

    // 设置新uri地址
    function _setURI(string memory newuri) internal virtual {
        _uri = newuri;
    }

_mint函数

// 铸造amount数量id代币给to地址
    function _mint(
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
    // 检查to是否是空地址
        require(to != address(0), "ERC1155: mint to the zero address");
   // 获取合约调用者地址
        address operator = _msgSender();
        // 获取id列表,该列表只有一个元素 ids[0] = id
        uint256[] memory ids = _asSingletonArray(id);
        // 获取amount列表该列表只有一个元素 amounts[0] = amount
        uint256[] memory amounts = _asSingletonArray(amount);
        // 代币转移前执行函数
        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
        // 增加代币余额
        _balances[id][to] += amount;
        // 触发转移单个代币事件,记录信息
        emit TransferSingle(operator, address(0), to, id, amount);
        // 代币转移后执行函数
        _afterTokenTransfer(operator, address(0), to, ids, amounts, data);
        // 检查接收的合约地址是否实现IERC1155Receiver接口
        _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
    }

_mintBatch函数

// 铸造amounts列表对应ids列表代币给to地址
    function _mintBatch(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
         // 检查to是否是空地址
        require(to != address(0), "ERC1155: mint to the zero address");
        // 检查账号列表长度与代币id列表长度一致
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
        // 获取当前合约调用者地址
        address operator = _msgSender();
        // 代币转移前执行函数
        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
        // 增加代币余额
        for (uint256 i = 0; i < ids.length; i++) {
            _balances[ids[i]][to] += amounts[i];
        }
        // 触发转移单个代币事件,记录信息
        emit TransferBatch(operator, address(0), to, ids, amounts);
        // 代币转移后执行函数
        _afterTokenTransfer(operator, address(0), to, ids, amounts, data);
        // 检查接收的合约地址是否实现IERC1155Receiver接口
        _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
    }

_burn函数

//销毁from地址的id代币
    function _burn(
        address from,
        uint256 id,
        uint256 amount
    ) internal virtual {
        // 检查from地址 不为空地址
        require(from != address(0), "ERC1155: burn from the zero address");
        // 获取当前合约调用者地址
        address operator = _msgSender();

        // 获取id列表,该列表只有一个元素 ids[0] = id
        uint256[] memory ids = _asSingletonArray(id);

         // 获取amount列表该列表只有一个元素 amounts[0] = amount
        uint256[] memory amounts = _asSingletonArray(amount);

        // 代币转移前执行函数
        _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");

        // 获取from账号id代币余额
        uint256 fromBalance = _balances[id][from];

        // 检查余额是否大于等于销毁数量
        require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");

        // 减去销毁数量,重新写入值
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }
        // 触发转移单个代币事件,记录信息
        emit TransferSingle(operator, from, address(0), id, amount);
        // 代币转移后执行函数
        _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
    }

_burnBatch函数

// 批量销毁地址拥有的各种id代币
    function _burnBatch(
        address from,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual {
        // 检查from地址 不为空地址
        require(from != address(0), "ERC1155: burn from the zero address");
        // 检查账号列表长度与代币id列表长度一致
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
         // 获取当前合约调用者地址
        address operator = _msgSender();
        // 代币转移前执行函数
        _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
        // 销毁代币
        for (uint256 i = 0; i < ids.length; i++) {
            // 获取代币id
            uint256 id = ids[i];
            // 获取销毁数量
            uint256 amount = amounts[i];
            // 获取代币余额
            uint256 fromBalance = _balances[id][from];
            // 检查余额是否大于等于销毁数量
            require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
            // 减去转销毁量,重新写入值
            unchecked {
                _balances[id][from] = fromBalance - amount;
            }
        }
        // 触发批量转移代币事件,记录信息
        emit TransferBatch(operator, from, address(0), ids, amounts);
        // 代币转移后执行函数
        _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
    }

_setApprovalForAll函数

// 设置owner对operate的授权状态
    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal virtual {
        // 检查 owner地址和operate地址是否相同
        require(owner != operator, "ERC1155: setting approval status for self");
        // 设置对operate的授权approve状态
        _operatorApprovals[owner][operator] = approved;
        // 触发授权事件
        emit ApprovalForAll(owner, operator, approved);
    }

_beforeTokenTransfer函数

// 代币转移前函数
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {}

_afterTokenTransfer函数

// 代币转移后执行函数
    function _afterTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {}

_doSafeTransferAcceptanceCheck函数

// 如果to是普通地址则返回ture,如果to是合约地址则检查该合约是否实现onERC1155Received接口
    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
                if (response != IERC1155Receiver.onERC1155Received.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non-ERC1155Receiver implementer");
            }
        }
    }

_doSafeBatchTransferAcceptanceCheck函数

// 如果to是普通地址则返回ture,如果to是合约地址则检查该合约是否实现onERC1155Received接口
    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
                bytes4 response
            ) {
                if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non-ERC1155Receiver implementer");
            }
        }
    }

_asSingletonArray函数

// 返回包含单个元素的列表
    function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
        uint256[] memory array = new uint256[](1);
        array[0] = element;

        return array;
    }
}

有关代币标准--ERC1155协议源码解析的更多相关文章

  1. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

  4. ruby-on-rails - 我更新了 ruby​​ gems,现在到处都收到解析树错误和弃用警告! - 2

    简而言之错误:NOTE:Gem::SourceIndex#add_specisdeprecated,useSpecification.add_spec.Itwillberemovedonorafter2011-11-01.Gem::SourceIndex#add_speccalledfrom/opt/local/lib/ruby/site_ruby/1.8/rubygems/source_index.rb:91./opt/local/lib/ruby/gems/1.8/gems/rails-2.3.8/lib/rails/gem_dependency.rb:275:in`==':und

  5. ruby - 将 spawn() 的标准输出/标准错误重定向到 Ruby 中的字符串 - 2

    我想使用spawn(针对多个并发子进程)在Ruby中执行一个外部进程,并将标准输出或标准错误收集到一个字符串中,其方式类似于使用Python的子进程Popen.communicate()可以完成的操作。我尝试将:out/:err重定向到一个新的StringIO对象,但这会生成一个ArgumentError,并且临时重新定义$stdxxx会混淆子进程的输出。 最佳答案 如果你不喜欢popen,这是我的方法:r,w=IO.pipepid=Process.spawn(command,:out=>w,:err=>[:child,:out])

  6. ruby-on-rails - 标准化文件名的字符串,删除重音和特殊字符 - 2

    我正在尝试找到一种方法来规范化字符串以将其作为文件名传递。到目前为止我有这个:my_string.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.gsub(/[^a-z]/,'_')但第一个问题:-字符。我猜这个方法还有更多问题。我不控制名称,名称字符串可以有重音符、空格和特殊字符。我想删除所有这些,用相应的字母('é'=>'e')替换重音符号,并将其余的替换为'_'字符。名字是这样的:“Prélèvements-常规”“健康证”...我希望它们像一个没有空格/特殊字符的文件名:“prelevements_routin

  7. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  8. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  9. ruby - 用 YAML.load 解析 json 安全吗? - 2

    我正在使用ruby2.1.0我有一个json文件。例如:test.json{"item":[{"apple":1},{"banana":2}]}用YAML.load加载这个文件安全吗?YAML.load(File.read('test.json'))我正在尝试加载一个json或yaml格式的文件。 最佳答案 YAML可以加载JSONYAML.load('{"something":"test","other":4}')=>{"something"=>"test","other"=>4}JSON将无法加载YAML。JSON.load("

  10. ruby - 如何使用 Nokogiri 解析纯 HTML 表格? - 2

    我想用Nokogiri解析HTML页面。页面的一部分有一个表,它没有使用任何特定的ID。是否可以提取如下内容:Today,3,455,34Today,1,1300,3664Today,10,100000,3444,Yesterday,3454,5656,3Yesterday,3545,1000,10Yesterday,3411,36223,15来自这个HTML:TodayYesterdayQntySizeLengthLengthSizeQnty345534345456563113003664354510001010100000344434113622315

随机推荐