智能合约开发之ERC

Posted BBinChina

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了智能合约开发之ERC相关的知识,希望对你有一定的参考价值。

概要

本章主要科普什么是ERC,在智能合约开发过程中,开发者们开始规范一些标准,ERC(ETHereum Request for Comment)便是以太坊上智能合约的一些标准定义,比较ERC20、ERC721等。

ERC-20

ERC20是V神在15年的标准,用于开发者在区块链上发行token令牌(也叫代币)的标准,而按这个标准编写的令牌可以与众多交易所、钱包等进行交互。
ERC20的文档

以下进行ERC20合约的实现及分析:

pragma solidity ^0.4.0

contract MyToken 
    address public creator;                     //合约创建者
    uint256 public totalSupply;                 //Token 总供应量
    mapping (address=>uint256) public balances;  //账户余额

    //合约构造函数
    function MyToken() public 
        creator = msg.sender;                   //合约的创建者;
        totalSupply = 10000;                    //初始化10000个总量的token
        balances[creator] = totalSupply;         //合约创建者发行并拥有所有的token
    

    //合约调用者查询余额,标准化接口
    function balanceOf(address owner) public constant returns(uint256) 
        return balances[owner];
    

    //转账,标准化接口
    function sendToken(address receiver, uint256 amount) public returns(bool) 
        address owner = msg.sender;
        require(amount > 0);
        require(balances[owner] >= amount);
        balances[owner] -= amount;
        balances[receiver] += amount;
        return true;
    

主要介绍下载balanceOf函数,我们使用了constant关键字,solidity函数有两种:constant、non-constant。
constant函数是只读,意味着不能执行任何状态改变,等同于view关键字。
还有一类函数采用pure关键字声明,表示不会读也不会改变状态。

transfer函数用于转账,转账时,采用require关键字来检测合约调用者是否拥有足够的token,当转账失败时返回false。

遵循ERC20通证标准可以编写智能合约。它需要实现的通证方法包括:可选的 name、symbol、decimals,必须有的 balanceOf、transfer、transferFrom、approve、allowance。它需要实现的事件响应包括 Transfer、Approve。

授权

在ERC20标准里,采用了全局变量allowed来表示一个所有者的地址被映射到一个已授权的地址并且同时被映射到token的数量。

mapping(address=>mapping(address=>uint256)) internal allowed;

function approve(address _spender, uint256 _value) public returns(bool) 
	allowed[msg.sender][_spender] = _value;
	//Approval是一个事件
	Approvall(msg.sender,_spender,_value);
	return true; 

被授权的地址使用transFrom()函数来消费

function transferFrom(address _from, address _to,uint256 _value) public returns(bool) 
	require(_to != address(0));
	require(_value <= balances[_from]);
	//检测被授权的数量是否足够
	require(_value <= allowed[_from][msg.sender];
	balances[_from]=balances[_from].sub(_value);
	balances[_to]=balances[_to].add(_value);
	allowed[_from][msg.sender]=allowed[_from][msg.sender].sub(_value);
	//事件
	Transfer(_from,_to,_value);
	return true;

销毁

ERC20合理里记录的是地址的余额数量,顾销毁实际上只是减少特定地址的余额以及总供应量

function burn(uint256 _value) public 
	require(_value <= balances[msg.sender]);
	address burner = msg.sender;
	balances[burner] = balances[burner].sub(_value);
	totalSupply_ = totalSupply_.sub(_value);
	//Burn 和 Transfer均为事件
	Burn(burner, _value);
	Transfer(burner,address(0),_value); 

ERC721

与 ERC20 不同,ERC721 是一种不可互换的令牌标准(Non-fungible Token Standard,NFT)。
通过ERC20知道,在每个地址上记录的是持有的数量,而ERC721要解决的是地址上持有的哪些Token,这些token都是独一无二不可再细分的,即不可等同进行置换。

ERC721必须实现ERC721和ERC165接口,而其标准必须满足以下条件:
1、Token所有权
2、创建Token
3、转账与授权
4、销毁Token

ERC721的EIP文档

Token所有权

在ERC721标准中,所有权是由映射到一个地址的一个token索引/ID的数组来决定,主合约需要保存所有合约创建的token列表,采用数组存储:

uint256[] internal allTokens

主合约还需要把token的索引和数量映射到一个地址上:

mapping(address=>uint256[])internal ownedTokens;

需要进行判断某个token所属地址时,如果通过以上数组遍历的话,性能很差,因此采用空间换时间的方式再定义token与address的映射关系

mapping(uint256=>address)internal tokenOwner

在ERC721开发过程中还需要注意在传输或者销毁token时,需要对地址持有token的数组进行重排操作,因为solidity的数组进行删除操作时,并不是把值真正删除,而是设置为0,因此我们需要知道token在所有者持有数组的索引位置。

//维持一个映射关系表示token在持有者数组的索引位置
mapping(uint256=>uint256) internal ownedTokensIndex;
//同时记录token在合约所有token的索引位置
mapping(uint256=>uint256) internal allTokensIndex;

合约标准中还采用了一个变量来跟踪持有者拥有的token数量

mapping(address=>uint256) internal ownedTokensCount;

创建Token

ERC721合约里有关于总供应量的两个函数addToken()和_mint(),调用合约拉力的addTokenTo()函数,然后通过super.addTokenTo()先调用基类ERC721合约里的addTokenTo()函数,通过这两个函数,就可以更新所有全局的所有权变量。


    //基本的ERC721实现
    function _burn(address _owner, uint256 _tokenId) internal
        clearApproval(_owner, _tokenId);
        removeTokenFrom(_owner, _tokenId);
        Transfer(_owner,address(0),_tokenId);
    

    function addTokenTo(address _to, uint256 _tokenId) internal 
        //调用基本实现
        super.addTokenTo(_to,_tokenId);
        //token在所有者的索引位置即当前的拥有数组大小
        uint256 length=ownedTokens[_to].length;
        ownedTokens[_to].push(_tokenId);
        ownedTokensIndex[_tokenId]=length;
    

授权

RC721标准提供机会批准通过id传递令牌的地址,或者我们可以批准地址来传输所有的令牌。 要批准通过ID传输,我们使用approve()函数如下。

	mapping(uint256=>address)internal tokenApprovals;
    mapping(address=>mapping(address=>bool)) internal operatorApprovals;
    function approve(address _to, uint256 _tokenId) public 
        address owner=ownerOf(_tokenId);
        require(_to != owner);
        require(msg.sender==owner||isApprovedForAll(owner,msg.sender));
        if(getApproved(_tokenId) != address(0) || _to != address(0)) 
            tokenApprovals[_tokenId] = _to;
            Approval(owner, _to, _tokenId);
        
    
    function isApprovedForAll(address _owner,address _operator) public view returns(bool) 
        return operatorApprovals[_owner][_operator];
    

    function getApproved(uint256 _tokenId) public view returns(address) 
        return tokenApprovals[_tokenId];
    

    function setApprovalForAll(address _to, bool _approved) public 
        require(_to != msg.sender);
        operatorApprovals[msg.sender][_to] = _approved;
        ApprovalForAll(msg.sender, _to, _approved);
    

在这里,全局变量tokenApprovals将令牌索引或标识映射到已批准传输的地址。 在approve()函数中,我们首先检查所有权或msg.sender isApprovedForAll() 。 使用setApprovalForAll()函数来批准一个地址来传输和处理由特定地址拥有的所有令牌,因为我们有一个全局变量operatorApprovals ,其中所有者的地址映射到批准的地址,然后映射到布尔。 默认设置为0或false,但通过使用**setApprovalForAll()我们可以将此映射设置为true,并允许地址处理所有ERC721的拥有。 接下来,我们使用getApproved()**来检查我们没有设置address(0)许可。 最后,我们的tokenApprovals映射完成到所需的地址。 和ERC20一样, Approval是事件。

下面解释如何传送ERC721 token,我们自定义函数修饰符canTransfer来限定msg.sender是获得授权的或者是token的所有者

	   modifier canTransfer(uint256 _tokenId) 
        require(isApprovedOrOwner(msg.sender, _tokenId));
        _;
    

    function isApprovedOrOwner(address _spender, uint256 _tokenId) internal view returns (bool) 
        address owner=ownerOf(_tokenId);
        return _spender==owner || getApproved(_tokenId)==_spender || isApprovedForAll(owner, _spender);
    

   function transferFrom(address _from, address _to, uint256 _tokenId) public canTransfer(_tokenId) 
        require(_from != address(0));
        require(_to != address(0));
        //清除拥有者不再有授权的权限
        clearApproval(_from, _tokenId);
        removeTokenFrom(_from, _tokenId);
        addTokenTo(_to, _tokenId);
        //把token的索引/id加到新的所有者名下
        Transfer(_from, _to, _tokenId);
    

    function clearApproval(address _owner, uint256 _tokenId) internal 
        require(ownerOf(_tokenId) == _owner);
        if(tokenApprovals[_tokenId]!=address(0)) 
            tokenApprovals[_tokenId]=address(0);
            Approval(_owner, address(0), _tokenId);
        
    

    function removeTokenFrom(address _from, uint256 _tokenId) internal 
        //调用基本合约实现
        super.removeTokenFrom(_from, _tokenId);
        //把所有者的ownedTokens数组里的最后一个token移到被传输的token的索引位置,同时将数组长度减一
        uint256 tokenIndex=ownedTokensIndex[_tokenId];
        uint256 lastTokenIndex=ownedTokens[_from].length.sub(1);
        uint256 lastToken=ownedTokens[_from][lastTokenIndex];
        ownedTokens[_from][tokenIndex]=lastToken;
        ownedTokens[_from][lastTokenIndex]=0;
        ownedTokens[_from].length--;
        ownedTokensIndex[_tokenId]=0;
        ownedTokensIndex[lastToken]=tokenIndex;
    

	//基本的ERC721实现
    function removeTokenFrom(address _from, uint256 _tokenId) internal 
        require(ownerOf(_tokenId)==_from);
        ownedTokensCount[_from]=ownedTokensCount[_from].sub(1);
        tokenOwner[_tokenId]=address(0);
    

销毁


    function _burn(address _owner, uint256 _tokenId) internal 
	    super._burn(_owner,_tokenId);
	    //清除metadata (if any)
	    //将在下章讲解元数据扩展内容
	    if(bytes(tokenURIs[_tokenId]).length != 0) 
		    delete tokenURIs[_tokenId];
	    

	    //step:重排所有Token的数组
	    uint256 tokenIndex=allTokensindex[_tokenId];

        //将最后的token放置到被删除token索引位置
        uint256 lastTokenIndex=allTokens.length.sub(1);
        uint256 lastToken=allTokens[lastTokenIndex];
        allTokens[tokenIndex]=lastToken;
        allTokens[lastTokenIndex]=0;
        allTokens.length--;
        //替换数组内容后,需要更新lastToken的索引
        allTokensindex[_tokenId]=0;
        allTokensindex[lastToken]=tokenIndex
    

	    //基本的ERC721实现
    function _burn(address _owner, uint256 _tokenId) internal
        clearApproval(_owner, _tokenId);
        removeTokenFrom(_owner, _tokenId);
        Transfer(_owner,address(0),_tokenId);
    

以上是关于智能合约开发之ERC的主要内容,如果未能解决你的问题,请参考以下文章

以太坊开发入门-ERC20合约

ERC-721 智能合约一次铸造 2 个 NFT

ERC20 代币转移到智能合约

我正在尝试编写恶意 ERC20 智能合约批准功能(用于学习目的),但这不起作用

如何将 ERC20 代币发送到智能合约余额?

使用智能合约存取erc20代币