Solidity教程之Token协议

Solidity教程之Token协议
Token协议介绍

ERC20

  • EIP: Ethereum Improvement Propose
  • 任何遵从EIP-20协议(ERC20标准)的Contract都属于ERC20 Token

标准接口

// 6 REQUIRED FUNCTIONS
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
// 2 REQUIRED EVENTS
event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
// 3. OPTIONAL FUNCTIONS
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)

分析

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
interface IERC20 {
      // 6 REQUIRED FUNCTIONS
      // 总发行量
    function totalSupply() external view returns (uint);  // -> 总发行量 100000000 * 10**decimals
      // 标准decimal: 18
      // USDT: 6
      // WBTC: 8
    function balanceOf(address account) external view returns (uint); // 指定账户的余额
    // 币的持有人直接调用,进行转账
    function transfer(address recipient, uint amount) external returns (bool);
      // 最常用的!!
      // 1. 我这个owner对合约进行approve,此时approve内部会修改allowance变量
      // 2. 合约内部调用transferFrom来支配owner的token
    function transferFrom( // spender就是这个合约
        address sender,    // owner
        address recipient,    // 转给谁
        uint amount // 金额
    ) external returns (bool);

    // owner: 币的持有人
      // spender: 是指定帮助花费的代理人(被授权的人)
    function allowance(address owner, address spender) external view returns (uint); // 授权的额度
    // decimals view,这是一个public 的变量,自动提供了一个读取的方法 // 返回精度
      // 持有人对spender进行授权,在approve内部,会调用msg.sender来知道owner是谁
    function approve(address spender, uint amount) external returns (bool);
      // 2 REQUIRED EVENTS
      // 事件
    event Transfer(address indexed from, address indexed to, uint value);
    event Approval(address indexed owner, address indexed spender, uint value);
    // 3. OPTIONAL FUNCTIONS
    function name() public view returns (string)
    function symbol() public view returns (string)
    function decimals() public view returns (uint8)
}

以下是ERC20的案例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "./IERC20.sol";
contract ERC20 is IERC20 {
    uint public totalSupply;
    mapping(address => uint) public balanceOf;
    mapping(address => mapping(address => uint)) public allowance;
    string public name = "Solidity by Example";
    string public symbol = "SOLBYEX";
    uint8 public decimals = 18;
    function transfer(address recipient, uint amount) external returns (bool) {
        balanceOf[msg.sender] -= amount;
        balanceOf[recipient] += amount;
        emit Transfer(msg.sender, recipient, amount);
        return true;
    }
    function approve(address spender, uint amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }
    function transferFrom(
        address sender,
        address recipient,
        uint amount
    ) external returns (bool) {
        allowance[sender][msg.sender] -= amount;
        balanceOf[sender] -= amount;
        balanceOf[recipient] += amount;
        emit Transfer(sender, recipient, amount);
        return true;
    }
    function mint(uint amount) external {
        balanceOf[msg.sender] += amount;
        totalSupply += amount;
        emit Transfer(address(0), msg.sender, amount);
    }
    function burn(uint amount) external {
        balanceOf[msg.sender] -= amount;
        totalSupply -= amount;
        emit Transfer(msg.sender, address(0), amount);
    }
}

可以使用openzeppelin库进行创建自己的token:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20 {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        // Mint 100 tokens to msg.sender
        // Similar to how
        // 1 dollar = 100 cents
        // 1 token = 1 * (10 ** decimals)
        _mint(msg.sender, 100 * 10**uint(decimals()));
    }
    // 默认是18,可以进行override
    function decimals() public view override returns (uint8) {
        return 6;
    }
}

ERC721

对于Opeasea而言,测试网目前仅支持Rinkeby,即部署在Rinkeby网络上的nft合约会自动展示在opensea中。

举例教程:https://mp.weixin.qq.com/s/T9GEgaqubHAftpMsdLAMIg

ipfs服务:https://app.pinata.cloud/pinmanager

EIP-721: https://eips.ethereum.org/EIPS/eip-721

标准接口

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
interface ERC165 {
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
interface IERC721 is IERC165 {
      // 3 EVENTS
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
      // 9 REQUIRED FUNCTIONS
    function balanceOf(address owner) external view returns (uint256 balance);
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
      function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
    function approve(address to, uint256 tokenId) external;
    function setApprovalForAll(address operator, bool _approved) external;
    function getApproved(uint256 tokenId) external view returns (address operator);
    function isApprovedForAll(address owner, address operator) external view returns (bool);

      // 3 OPTIONAL FUNCTIONS
    function name() external view returns (string _name);
    function symbol() external view returns (string _symbol);
    function tokenURI(uint256 _tokenId) external view returns (string);
}

部署

// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
/*
1. 一个ERC721合约也是一个集合,可以有N多个token,但是每一个都是不一样的,有唯一的id
2. 每个tokenid可以关联一个URI,一般是.json文件,里面有三个字段,进而变成一个独一无二的展示,所以是NFT,三个字段为:
    - description:nft描述
    - url:nft图片存储在ipfs上的哈希值
    - name:nft名字
*/
contract SimpleCollectible is ERC721URIStorage {
    uint256 public tokenCounter;
    constructor () public ERC721 ("Dogie", "DOG"){
        tokenCounter = 0;
    }
    function createCollectible(string memory tokenURI) public returns (uint256) {
        uint256 newItemId = tokenCounter;
        _safeMint(msg.sender, newItemId);
        _setTokenURI(newItemId, tokenURI);
        tokenCounter = tokenCounter + 1;
        return newItemId;
    }
    function _baseURI() internal view override returns (string memory) {
        return "https://gateway.pinata.cloud/ipfs/";
    }
    //1. createCollectible(QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta)
    //2. tokenURI(0)
    //3. 浏览器请求:https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
}

接受者为合约时

to如果是合约地址,则to合约必须实现onERC721Received接口,因为有回调校验,如果to是EOA则不需要实现该接口。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}
contract ERC721Holder is IERC721Receiver {
    function onERC721Received(
        address,
        address,
        uint256,
        bytes memory
    ) public virtual override returns (bytes4) {
        return this.onERC721Received.selector;
    }
}

完整代码

// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
// 里面继承了IERC721Receiver.sol接口
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
/*
1. 一个ERC721合约也是一个集合,可以有N多个token,但是每一个都是不一样的,有唯一的id
2. 每个tokenid可以关联一个图片(URI),进而变成一个独一无二的展示,所以是NFT
*/
contract SimpleCollectible is ERC721URIStorage {
    uint256 public tokenCounter;
    constructor () public ERC721 ("Dogie", "DOG"){
        tokenCounter = 0;
    }
    function createCollectible(string memory tokenURI) public returns (uint256) {
        uint256 newItemId = tokenCounter;
        _safeMint(msg.sender, newItemId);
        _setTokenURI(newItemId, tokenURI);
        tokenCounter = tokenCounter + 1;
        return newItemId;
    }
  // 注释掉
    // function _baseURI() internal view override returns (string memory) {
    //     return "https://gateway.pinata.cloud/ipfs/";
    // }
}
contract ERC721Holder is IERC721Receiver {
    function onERC721Received(
        address,
        address,
        uint256,
        bytes memory
    ) public virtual override returns (bytes4) {
        return this.onERC721Received.selector;
    }
}

测试

      // QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB是json文件的hash
    //1. createCollectible(QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB)
    //2. tokenURI(0)
    //3. 浏览器请求:https://gateway.pinata.cloud/ipfs/QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB

其中的hash值为这个nft图片的描述文件的哈希:metadata.json,

  1. json文件上传后的哈希为:QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB,这个哈希用于作为tokenuri,在合约内部会和baseUrl组装起来。
  2. 图片的哈希为:QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
{
  "description": "this is a nft1155 metadata json desc",
  "image": "https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
  "name": "duke nft"
}

总结

  1. 在页面展示时,url为:https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
  2. 实际上的tokenuri为:https://gateway.pinata.cloud/ipfs/QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB

授权比较

  • ERC721
// 对单个id进行approve    
function approve(address to, uint256 tokenId) public virtual override;
// 对某个地址授权所有的token
function setApprovalForAll(address operator, bool approved) public virtual override;
  • ERC1155
// 每个对单个id的授权!!
// what -》不支持!
// 对某个地址授权所有的token
function setApprovalForAll(address operator, bool approved) external;

ERC1155

  1. 1155是每个id为一个token组,每个组里面可以包含多个erc20
  2. mint(to, id, amount)
  3. setURI(id, uri)
  4. setURI的时候,填写的是metadata.json,不是图片的url,使用pinata服务存储图片和metadata.json:
   {
       "description": "this is a nft1155 metadata json desc",
       //"image": "ipfs://QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
       "image": "https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
       "name": "duke nft"
   }
  1. opensea测试网是rinkeby,链接为:https://testnets.opensea.io
  2. opensea会自动将当前链接的地址账户的nft扫描出来展示在页面上。
  3. 自己部署(示例):https://testnets.opensea.io/assets/rinkeby/0x54f1a0ad311e00b68fa940bc969c191863ac84fd/1

更好的合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol";
contract MockErc1155Token is ERC1155URIStorage {
    string public name;
    string public symbol;
    uint256 public tokenCounter;
    constructor(string memory _name, string memory _symbol) ERC1155("") {
        name = _name;
        symbol = _symbol;
    }
    function createCollectible(string memory tokenURI, uint256 _amount)
        external
        returns (uint256)
    {
        uint256 newItemId = tokenCounter;
        _mint(_msgSender(), newItemId, _amount, "");
        _setURI(newItemId, tokenURI);
        tokenCounter = tokenCounter + 1;
        return newItemId;
    }
    // function mint(uint256 _id, uint256 _amount) external {
    //     _mint(_msgSender(), _id, _amount, "");
    // }
    // function mintBatch(uint256[] memory _ids, uint256[] memory _amounts)
    //     external
    // {
    //     _mintBatch(_msgSender(), _ids, _amounts, "");
    // }
    function burn(uint256 _id, uint256 _amount) external {
        _burn(msg.sender, _id, _amount);
    }
    function burnBatch(uint256[] memory _ids, uint256[] memory _amounts)
        external
    {
        _burnBatch(msg.sender, _ids, _amounts);
    }
    function burnForMint(
        address _from,
        uint256[] memory _burnIds,
        uint256[] memory _burnAmounts,
        uint256[] memory _mintIds,
        uint256[] memory _mintAmounts
    ) external {
        _burnBatch(_from, _burnIds, _burnAmounts);
        _mintBatch(_from, _mintIds, _mintAmounts, "");
    }
    function setURI(uint256 _id, string memory _uri) external {
        _setURI(_id, _uri);
    }
}

ERC165

  1. 没看到interfaceId在哪里定义了,目前没有找到(这个值哪来的?)
  2. 没见到supportsInterface在哪里被调用(如何使用)
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

ERC165的目的-一种发布和检测智能合约实现的接口的标准。

防止用户传递给接口的地址是无效的(如EOA地址),该协议会在合约内部做地址检查。

本教程仅供学习,如有疑问,请移步Telegram
post-qrcode
gason
版权声明:本文于2022-10-18转载自参考,共计10733字。
转载提示:此文章非本站原创文章,若需转载请联系原作者获得转载授权。
评论(没有评论)
验证码