ERC721

ERC721

ERC20是标准Token接口,这个规定了Token的基本功能, 是可置换的,Token之间没有区别,所有的token是一样的。ERC721则是针对不可置换Token的智能合约标准接口,(non-fungile tokens)不可置换Token简称NFTs,操作标准API的实现方法。

ERC20是标准Token接口,ERC20的Token可以无限细分为10^18份,而ERC721的Token最小的单位为1,无法再分割。

ERC721Basic.sol

基于ERC165 合约,检测合约实现的接口,并声明了一些方法。
检测:
bytes4(keccak256(“onERC721Received(address, uint256, bytes)”))
合同中的每个功能都会拥有自己的签名,调用合同时,EVM会使用一系列切换案例来查找被调用的功能签名并相应地执行代码。

ERC721Holder.sol

基于ERC721Receiver合约,接收一些地址数字参数

DeprecatedERC721.sol

takeOwnership、transfer、tokensOf

ERC721.sol

基于ERC721Basic 合约,声明了一些totalSupply、tokenOfOwnerByIndex、name、symbol、tokenURI方法

ERC721BasicToken.sol

将给定token ID的所有权转移到另一个地址
,如果目标地址是合约,则必须实现“onERC721Received”,
调用安全的传输,并返回值
“bytes4(keccak256(“onERC721Received(地址、地址、uint256字节)”))”;否则,
转账被退回。

ERC721Receiver.sol

处理非功能性测试

ERC721Token.sol

声明的方法的实现

ERC721 合约声明:

contract ERC721 {
   //与ERC20兼容的接口
   function name() constant returns (string name);
   function symbol() constant returns (string symbol);
   function totalSupply() constant returns (uint256 totalSupply);
   function balanceOf(address _owner) constant returns (uint balance);
   //所有权相关的接口
   function ownerOf(uint256 _tokenId) constant returns (address owner);
   function approve(address _to, uint256 _tokenId);
   function takeOwnership(uint256 _tokenId);
   function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
   function transfer(address _to, uint256 _tokenId);
   function tokenOfOwnerByIndex(address _owner, uint256 _index) constant returns (uint tokenId);
   //通证元数据接口
   function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl);
   //事件
   event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
   event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);
}

方法

name:

contract MyNFT {  
  function name() constant returns (string name){
    return "My Non-Fungible Token";
  }

这个函数作用于告诉外部智能合约以及应用Token的名字

symbol:

该功能还有助于提供与ERC20令牌标准的兼容。 它为外部程序提供token的简称或标识。

contract MyNFT {
  function symbol() constant returns (string symbol){
    return "MNFT";
  }

totalSupply:

该函数返回区块链上可提供货币的总数。

contract MyNFT {
  // 任意数
  uint256 private totalSupply = 1000000000;
  function totalSupply() constant returns (uint256 supply){
    return totalSupply;
  }
}

balanceOf:

该函数是根据给定地址搜索账户中的Token的数目。

contract MyNFT {
  mapping(address => uint) private balances;
  function balanceOf(address _owner) constant returns (uint balance)
      {
        return balances[_owner];
      }
}

所有权函数

  这些函数定义了合约处理代币所有权的方式以及转移所有权的方式。其中最值得注意的函数为takeOwnership和transfer,分别担任提取和发送功能,且对用户之间转移代币来说必不可少。
  

ownerOf:

  该函数返回某代币的所有者的地址。由于每个ERC721代币都是独一无二的,其在区块链上通过一个唯一的ID被查询。所以可以利用其ID判断代币的所有者。
  
  

contract MyNFT {
  mapping(uint256 => address) private tokenOwners;
  mapping(uint256 => bool) private tokenExists;
  function ownerOf(uint256 _tokenId)
  constant returns (address owner) {
    require(tokenExists[_tokenId]);
    return tokenOwners[_tokenId];
  }
}

approve:

该函数批准或授予另一实体代表所有者转移代币的权利。 例如,如果爱丽丝拥有1个NFT,她可以为她的朋友鲍勃调用approve函数。 调用成功后,鲍勃就可以代表爱丽丝以后对该代币的所有权或对该代币进行操作。


contract MyNFT {
  mapping(address => mapping (address => uint256)) allowed;
  function approve(address _to, uint256 _tokenId){
    require(msg.sender == ownerOf(_tokenId));
    require(msg.sender != _to);
    allowed[msg.sender][_to] = _tokenId;
    Approval(msg.sender, _to, _tokenId);
  }
}

takeOwnership:

  该函数担任提现功能,因为某个外部方可以调用它来从另一个用户的账户中提取出代币。因此,在某个用户已经被授权获得一定数额的代币并且希望从另一个用户的余额中提取所述代币的时候,可以使用该函数 。
  

contract MyNFT {
  function takeOwnership(uint256 _tokenId){
    require(tokenExists[_tokenId]);
    address oldOwner = ownerOf(_tokenId);
    address newOwner = msg.sender;
    require(newOwner != oldOwner);
    require(allowed[oldOwner][newOwner] == _tokenId);
    balances[oldOwner] -= 1;
    tokenOwners[_tokenId] = newOwner;
    balances[newOwner] += 1;
    Transfer(oldOwner, newOwner, _tokenId);
  }
}

safeTransferFrom:

转移NFT所有权,一次成功的转移操作必须发起 Transer 事件。函数的实现需要做一下几种检查:
1. 调用者msg.sender应该是当前tokenId的所有者或被授权的地址
1. _from 必须是 _tokenId的所有者
1. _tokenId 应该是当前合约正在监测的NFTs 中的任何一个
1. _to 地址不应该为 0
1. 如果_to 是一个合约应该调用其onERC721Received方法, 并且检查其返回值,如果返回值不为bytes4(keccak256(“onERC721Received(address,uint256,bytes)”))抛出异常。
一个可接收NFT的合约必须实现ERC721TokenReceiver接口:

interface ERC721TokenReceiver {
    /// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
    function onERC721Received(address _from, uint256 _tokenId, bytes data) external returns(bytes4);
}

transferFrom:

用来转移NFTs, 方法成功后需触发Transfer事件。调用者自己确认_to地址能正常接收NFT,否则将丢失此NFT。

transfer

  该方法是用于转移代币。函数transfer让代币所有者将其代币发送给另一个用户,与独立加密货币类似。然而,只有收款账户事先被打款账户授予获得代币的权利,转账才能开始。

contract MyNFT {
  mapping(address => mapping(uint256 => uint256)) private ownerTokens;
  function removeFromTokenList(address owner, uint256 _tokenId) private {
    for(uint256 i = 0;ownerTokens[owner][i] != _tokenId;i++){
      ownerTokens[owner][i] = 0;
    }
  }
  function transfer(address _to, uint256 _tokenId){
    address currentOwner = msg.sender;
    address newOwner = _to;
    require(tokenExists[_tokenId]);
    require(currentOwner == ownerOf(_tokenId));
    require(currentOwner != newOwner);
    require(newOwner != address(0));
    removeFromTokenList(_tokenId);
    balances[oldOwner] -= 1;
    tokenOwners[_tokenId] = newOwner;
    balances[newOwner] += 1;
    Transfer(oldOwner, newOwner, _tokenId);
  }
}

tokenOfOwnerByIndex :

每个人同时可以拥有一个或多个Token,因为每个Token的ID都是唯一的,所以很难跟踪到每个人所拥有的Token的ID。为此,该合约记录了每个人所拥有Token的ID,因此,用户拥有的每个单独的Token都可以通过其索引在用户拥有的Token列表(数组)中检索。tokenOfOwnerByIndex可以帮助我们检索出任意token。


contract MyNFT {
  mapping(address => mapping(uint256 => uint256)) private ownerTokens;
  function tokenOfOwnerByIndex(address _owner, uint256 _index) constant returns (uint tokenId){
    return ownerTokens[_owner][_index];
  }
}

 元数据函数

将表明每个代币最典型特征的数据存储在区块链上的代价十分昂贵,不建议这样做。为了防止这种情况,我们可以将关联到每个代币属性的索引(如IPFS哈希或HTTP链接)存储在链上,这样一来,链外的程序就可以执行逻辑以获取更多有关该代币的信息。这些索引就是“有关数据的数据”,也可以叫做元数据。

tokenMetadata:

这方法可以让我们查看Token的元数据或者指向其数据的链接。

contract MyNFT {
  mapping(uint256 => string) tokenLinks;
  function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl) {
    return tokenLinks[_tokenId];
  }
}

事件

 无论合约在何时调用事件,事件都会被触发。一旦事件被触发,就向任何正在监听的程序广播这些事件。外部程序监听区块链事件,从而可以在事件被触发后利用事件提供的信息执行逻辑。ERC721标准定义了两个事件如下:
 

Transfer:

只要Token被转手,就会触发此事件,当token的所有权从某个用户转移到另一个用户手上时该事件会被广播。 广播里会详细说明哪个帐户发送Token,哪个帐户收到Token,以及哪个Token(通过ID)被转手。

contract MyNFT {
  event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
}

Approval:

该事件在某个用户授权另一个用户获得某个代币的所有权时(即,当授权被执行时)得以触发。其详细说明了哪个账户目前拥有该代币,哪个账户获许在未来获得该代币,以及哪个代币(通过ID定义)被授权转移其所有权。

contract MyNFT {
  event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);
}

安全考虑

1).EVM参数传递机制:在调用函数时,如果目标函数有参数,正常情况下我们需要根据ABI指定的参数类型来构造输入。

例如 transfer(address to, uint256 value) 在调用 transfer() 时,以太坊使用函数签名的哈希值前4字节作为 function selector,计算 sha3(transfer(address,uint256)) 得到 0xA9059CBB,再拼接上to地址,然后拼接value得到完整的calldata,将这段 calldata 附加在交易中发送到目标智能合约地址即可实现函数调用。当以太坊节点收到交易时,将 calldata 与智能合约字节码一同加载到 EVM 中,字节码在编译时生成,也意味着对参数的处理在编译时也已经固定下来了。

在字节码中,出于动态数组的考虑,只会判断 calldata 是否小于某个最小长度,但是不会检查参数是否过长。编译器会生成一系列 CALLDATALOAD 配合数学运算来分离出函数需要的参数。

所以当参数过多的时候,字节码、EVM都不会处理,可以直接忽略。黑客利用这一特性可以很容易地针对 CUSTOM_CALL 构造攻击参数。

2).关于“接收通知调用”正确的代码实现:

正确的代码实现中,对于“接收通知调用”的处理应该将被通知函数的签名(signature)写死为固定值,避免由攻击者来任意指定的任何可能性。

正确的通知调用的写法:

声明Receiver函数,并通过声明的函数进行接收通知调,或者通过 Receiver 函数的签名常量进行接收通知调用。例如Consensys 维护的 Token-Factory 项目:

function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);

        //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this.
        //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
        //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
        if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; }
        return true;
    }

应用场景:

ERC20 是标准的代币协议,它能够用于网站流通价值的积分,虚拟币等等。而ERC721的NFT让数字资产变得更有收藏价值,尤其是在加密货币收藏和网络游戏领域拥有巨大的潜力。ERC20 适用于游戏的流通价值积分,ERC721则在加密货币收藏和网络游戏领域拥有巨大的潜力,非常适合作为加密虚拟资产收藏品。

由于同质代币与非同质代币之间的根本差异,并不能完全兼容ERC-20。

猜你喜欢

转载自blog.csdn.net/qq_26769677/article/details/81517533