教程 | 以太坊开发演练,Part-4:代币与 ERC

ETHapple1232018-04-14 23:36:34  阅读 -评论 1  阅读原文

从一个开发者角度来看,以太坊代币仅仅就是智能合约。如果拿饮料来对比,就如同任何人都可以用自己的方式调配出适合自己口味的咖啡。

大家也可能听说过 ERC20,ERC721,或者其它标准,这些都是开发者社区共同遵守的基础功能,在此基础上,大家都可以使用自己开发功能,以自己的方式创建脚本来管理虚拟货币

加勒比海盗中有一句台词很好地再现了这个情况:

-代码更像是"指南",而非实际规则-

长远看,遵循标准有很多不应忽视的益处。首先,如果遵照某个标准生成代币,那么每个人都会知道该代币的基础功能,并知道如何与之交互,因此就会有更多信任。去中心化程序(DApps)比如 Mist,可以直接辨别出其代币特征,并通过特定的 UI 来与其打交道。另外,一种代币智能合约的标准实现已经被社区开发出来,它采用类似 OpenZeppelin 的架构。这种实现已经被很多大神验证过,可以用来作为代币开发的起点。

本文中会从头开始提供一个不完整的,但是遵循 ERC20 标准的,基础版的代币实现,然后将它转换成遵循 ERC721 标准的实现。这样就能让读者看出两个标准之间的不同。

写本文的出发点是希望大家了解代币是如何工作的,其过程并不是一个黑箱;另外,对于 ERC20 这个标准,尽管它至少已经被广泛接受两年以上,如果只是从标准框架简单地生成自己的代币,也还会存在某些不易发现的故障点。

生成自己代币

ERC20 是为同质(Fungible)代币标准设立的标准,可以被其它应用(从钱包到去中心化交易所)重复使用。同质意味着可以用同类的代币互换,换句话说,所有的代币都是等价的(就像钱币,某一美金和其它美金之间没有区别)。而一个非同质代币(Non-fungible Token)代表一种特定价值(例如房屋,财产,艺术品等)。同质代币有其内在价值,而非同质代币只是一种价值智能合约的代表。

要提供符合ERC20标准的代币,需要实现如下功能和事件:

contract ERC20Interface { function totalSupply() public constant returns (uint); function balanceOf(address tokenOwner) public constant returns (uint balance); function allowance(address tokenOwner, address spender) public constant returns (uint remaining); function transfer(address to, uint tokens) public returns (bool success); function approve(address spender, uint tokens) public returns (bool success); function transferFrom(address from, address to, uint tokens) public returns (bool success); event Transfer(address indexed from, address indexed to, uint tokens); event Approval(address indexed tokenOwner, address indexed spender, uint tokens); }

标准不提供功能的实现,这是因为大家可以用自己喜欢的方式写出任何代码,如果不需要提供某些功能只需要按照标准返回 null/false 的值就可以了。

注意:本文并不很强调代码,大家只需了解内部机理,全部代码将会在文末附上链接。

实现

首先,需要给代币起一个名字,因此会采用一个公有变量(Public Variable)':

string public name = "Our Tutorial Coin";

然后给代币起一个代号:

string public symbol = "OTC";

当然还要有具体小数位数:

uint8 public decimals = 2;

因为 Solidity 并不完全支持浮点数,因此必须把所有数表示成整数。例如,对于一个数字 "123456",如果使用 2 位小数,则代表 "1234.56";如果采用4位小数,则代表 "12.3456"。0 位小数代表代币不可分。而以太坊的加密币以太币则使用18位小数。

一般地,代币不需要使用18位小数,因为它被神圣的以太币加持过了(除非你愿意被其它专家指责为什么采用这个神圣的数字)。

你需要统计一共发行了多少代币,并跟踪每人拥有多少:

uint256 public totalSupply; mapping(address => uint256) balances;

当然,你需要从0个代币开始,除非在代币智能合约创建时候就生成了一些,如下例:

// The constructor function of our Token smart contract function TutoCoin() public { // We create 100 tokens (With 2 decimals, in reality it's 1.00 token) totalSupply = 100; // We give all the token to the msg.sender (in this case, it's the creator of the contract) balances[msg.sender] = 100; // With coins, don't forget to keep track of who has how much in the smart contract, or they'll be "lost". }

totalsupply() 函数只是从 totalSupply 变量中获取数值:

function totalSupply() public constant returns (uint256 _totalSupply) { return totalSupply; }

balanceOf() 也类似:

// Gets the balance of the specified address. function balanceOf(address tokenOwner) public view returns (uint256 balance) { return balances[tokenOwner]; }

接下来就是ERC20的神奇之处了, transfer() 函数是将代币从一个地址发送到另外一个地址的函数:

function transfer(address _to, uint256 _value) public returns (bool) { // avoid sending tokens to the 0x0 address require(_to != address(0)); // make sure the sender has enough tokens require(_value <= balances[msg.sender]); // we substract the tokens from the sender's balance balances[msg.sender] = balances[msg.sender] - _value; // then add them to the receiver balances[_to] = balances[_to] + _value; // We trigger an event, note that Transfer have a capital "T", it's not the function itself with a lowercase "t" Transfer(msg.sender, _to, _value); // the transfer was successfull, we return a true return true; }

以上基本就是 ERC20 代币标准的核心内容。

approve(),transferFrom(), 和 allowance() 是使得代币符合 ERC20 标准的函数,但是它们容易受到攻击。

当源地址用 approve() 函数授权另外一个地址,被授权地址就可以使用 transferFrom() 函数花费源地址里的代币。allowance() 只是一个从其它地址获得可用额度的函数。

这些函数都有安全隐患,因为当源地址向被授权地址授信可以花费 X 个代币后,有突发原因需要将授信改成 Y 个代币(提高或降低额度),被授权地址很有可能在重新授信执行之前将 X 个代币转移走;当 Y 个代币的新授信完成后,又可以转移Y个代币。在之前的本系列文章中我提到过,当某个交易正处在挖矿过程中时状态不能确定,矿工可以在交易挖矿过程中控制执行时间。

鉴于 ERC20 还存在其他一些问题,更安全容错的 transferFrom() 实现和其它方案被发布出来(如之前所说,该标准只是一些功能原型和行为定义,具体细节则靠开发者自己实现),并正在讨论中,其中就包括 ERC223 和 ERC777.

ERC223 方案的动机是避免将代币发送到错误地址或者不支持这种代币的合约上,成千上万的金钱因为上述原因丢失,这一需求作为以太坊后续开发功能的第 223 条记录在案。ERC777 标准在支持其它功能的同时,对接收地址进行"即将收到代币"的提醒功能,ERC777 方案看起来很有可能替代 ERC20.

ERC721

目前看,ERC721 跟 ERC20 及其近亲系列有本质上的不同。

ERC721 中,代币都是唯一的。ERC721 是几个月前提出来的方案,CryptoKitties,这款使用ERC721标准实现的收集虚拟猫游戏使得它备受瞩目。

以太猫游戏实际就是智能合约中的非同质代币 (non-fungible token),并在游戏中用猫的形象来表现出来。

如果想将一个 ERC20 合约转变成 ERC721 合约,我们需要知道 ERC721 是如何跟踪代币的。

在 ERC20 中,每个地址都有一个账目表,而在 ERC721 合约中,每个地址都有一个代币列表:

mapping(address => uint[]) internal listOfOwnerTokens;

由于 Solidity 自身限制,不支持对队列进行 indexOF() 的操作,我们不得不手动进行队列代币跟踪:

mapping(uint => uint) internal tokenIndexInOwnerArray;

当然可以用自己实现的代码库来发现元素的索引,考虑到索引时间有可能很长,最佳实践还是采用映射方式。

为了更容易跟踪代币,还可以为代币的拥有者设置一个映射表:

mapping(uint => address) internal tokenIdToOwner;

以上就是两个标准之间最大的不同,ERC721 中的 transfer() 函数会为代币设置新的拥有者:

function transfer(address _to, uint _tokenId) public (_tokenId) { // we make sure the token exists require(tokenIdToOwner[_tokenId] != address(0)); // the sender owns the token require(tokenIdToOwner[_tokenId] == msg.sender); // avoid sending it to a 0x0 require(_to != address(0)); // we remove the token from last owner list uint length = listOfOwnerTokens[msg.sender].length; // length of owner tokens uint index = tokenIndexInOwnerArray[_tokenId]; // index of token in owner array uint swapToken = listOfOwnerTokens[msg.sender][length - 1]; // last token in array listOfOwnerTokens[msg.sender][index] = swapToken; // last token pushed to the place of the one that was transferred tokenIndexInOwnerArray[swapToken] = index; // update the index of the token we moved delete listOfOwnerTokens[msg.sender][length - 1]; // remove the case we emptied listOfOwnerTokens[msg.sender].length--; // shorten the array's length // We set the new owner of the token tokenIdToOwner[_tokenId] = _to; // we add the token to the list of the new owner listOfOwnerTokens[_to].push(_tokenId); tokenIndexInOwnerArray[_tokenId] = listOfOwnerTokens[_to].length - 1; Transfer(msg.sender, _to, _tokenId); }

尽管代码比较长,但却是转移代币流程中必不可少的步骤。

还必须注意,ERC721 也支持 approve() 和 transferFrom() 函数,因此我们必须在 transfer 函数内部加上其它限制指令,这样一来,当某个代币有了新的拥有者,之前的被授权地址就无法其代币进行转移操作,代码如下:

function transfer(address _to, uint _tokenId) public (_tokenId) { // ... approvedAddressToTransferTokenId[_tokenId] = address(0); }



挖矿


基于以上两种标准,可能面对同一种需求,要么产生同质代币,要么产生非同质代币,一般都会用一个叫做 Mint() 的函数完成。

实现以上功能函数的代码如下:

function mint(address _owner, uint256 _tokenId) public (_tokenId) { // We make sure that the token doesn't already exist require(tokenIdToOwner[_tokenId] == address(0)); // We assign the token to someone tokenIdToOwner[_tokenId] = _owner; listOfOwnerTokens[_owner].push(_tokenId); tokenIndexInOwnerArray[_tokenId] = listOfOwnerTokens[_owner].length - 1; // We update the total supply of managed tokens by this contract totalSupply = totalSupply + 1; // We emit an event Minted(_owner, _tokenId); }

用任意一个数字产生一个新代币,根据不同应用场景,一般在合约内部只会授权部分地址可以对它进行挖矿(mint)操作。

这里需要注意,mint() 函数并没有出现在协议标准定义中,而是我们添加上去的,也就是说我们可以对标准进行扩充,添加其它对代币的必要操作。例如,可以添加用以太币来买卖代币的系统,或者删除不再需要代币的功能。

元数据


如前所述,非同质代币是价值的代表,大量情况下,需要描述这种价值。可以用如下字符串实现:

mapping(uint => string) internal referencedMetadata;

由此可见,智能合约与其说内含某种对象不如说是一种权益的证明。例如,不能将一辆车存放在智能合约中,但是可以存放车牌或者其它法律票证。

目前虚拟资产广泛使用的技术都用 IPFS 哈希作为元数据,IPFS 哈希是存放在 IPFS 系统中文件的地址。简单说,IPFS 是一个 HTTP 的 torrent 版本。当一个新文件添加到 IPFS 中,就会在 IPFS 网络中的至少一个计算节点上表现出来。

当文件通过 IPFS 或者 HTTP 对每个人都可见时,"代币所有权证明"就在智能合约中注册。这个操作不是程序,而应该是不可替代代币的一种新应用。它被称为"Crypto-collectibles",现在变得很热门。

回到我们的代码,ERC721 的讨论目前不太活跃了,原始建议贴很久都没有更新过,因此基于此又有新的讨论方案,被称为 ERC841。在 ERC841 中"不可替代代币(non-fungible token)"被"契据(deed)"的称呼替代。

另外一个方案,ERC821,也被提出来,期望基于 ERC223 和 ERC777 提供更好的方案设计。

ERC821 和 ERC841 有同样的目标,但是实现方法上有些许不同,但都有待改进,如果大家有建议,可以参与讨论。

可以在 Github 上找到 ERC20 和 ERC721 的实现(不建议用于生产),链接为:devzl/ethereum-walkthrough-4

另外,花点儿时间了解 OpenZepplin 框架也是值得的。他们有非常棒的基本上通过了审计的模块化智能合约(当然,在你决定使用哪个模块之前最好通读其内容)

以上就是第四部分的内容,下一篇中我们将介绍如何创建 DApp。

如果喜欢本文,可以通过如下方式联系我:@dev_zl

彩蛋


Initial coin offerings 有点儿偏离以太坊项目开发的议题,但是本质上,它就是一种众筹。

如果一个初创公司需要资金,就可以创建自己的代币,过一段时间卖一部分,被称做 crowdsale 或者 Initial coin offering。

在智能合约和区块链技术出现之前,初创公司会使用众筹网站集资,但是这种网站会抽走很大一部分服务费。有了 Initial coin offering 之后,没有了中间商,筹集的钱都归初创公司自己用了。

目前,集资项目更多的是骗局,从投资者角度看,应该看好自己的钱袋。从开发者角度看,crowdsale 就是一种智能合约,它卖的是未来兑换以太币的代币。没有一个标准方式,但是可以从OpenZepplin代码库中找到一些好的实现方式。另外,在以太坊上也有一个简易教程。

原文链接: https://hackernoon.com/ethereum-development-walkth...
作者: dev_zl
翻译&校对: 小野于林 & Elisa


译者:小野于林

高性能运算和分布式存储从业者。探索其在区块链中的定位和价值。

本文由作者授权 EthFans 翻译及再出版。

声明:链世界登载此文仅出于分享区块链知识,并不意味着赞同其观点或证实其描述。文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。此文如侵犯到您的合法权益,请联系我们kefu@lianshijie.com

参与讨论 (1 人参与讨论)

相关推荐

为什么「代币化所有权」是自股权以来最好的协调工具?

「代币」这种所有权的数字表现形式,由于其无摩擦的分配和可适应的特性,是自股权出现以来最强大的激励和协调工具。

ETH/USDT 深度最佳的以太坊 DEX - Tokenlon

由于 DeFi 的爆炸式增长,imToken 内置的去中心化交易所 Tokenlon 的单日交易量总额也突破了 1000 万美金。仅 2020 年这半年多的时间,Tokenlon 累计交易额已达 4 亿美金。

比特币减半和以太坊2.0将为加密矿工带来巨大变化

比特币减半和以太坊2.0将为加密矿工带来巨大变化

在比特币减半和冠状病毒大流行来临之后,私人矿工们受到了冲击,但大型厂商也受到了影响。

以太坊2.0技术浅析

以太坊 2.0网络启动协调员Danny Ryan也在论坛中初步宣布,以太坊2.0最终测试网预计将于8月初正式启动测试。我们应该在以太坊2.0正式上线前对它的功能特性进行了解,更好的迎接区块链的新浪潮。

比特币有什么缺点?

1.交易平台的脆弱性。比特币网络很健壮,但比特币交易平台很脆弱。交易平台通常是一个网站,而网站会遭到黑客攻击,或者遭到主管部门的关闭。2.交易确认时间长。比特币钱包初次安装时,会消耗大量时间下载历史交易数据块。而比特币交易时,为了确认数据准确性,会消耗一些时间,与p2p网络进行交互,得到全网确认后,交易才算完成。3.价格波动极大。由于大量炒家介入,导致比特币兑换现金的价格如过山车一般起伏。使得比

业务中使用区块链的四种方式

业务中使用区块链的四种方式

暴走时评:区块链是一种支持像比特币这样的数字货币的公共分类帐本,并且正改变着我们的业务方式。一旦那些对匿名交易,甚至是秘密交易感兴趣的人接纳了这样一种鲜为人知的工具,加密货币就会日趋成为主流。 区块链是一种支持像比特币这样的数字货币的公共分类帐本,并且正改变着我们的业务方式。一旦那些对匿名交易,甚至是秘密交易感兴趣的人接纳了这样一种鲜为人知的工具,加密货币就会日趋成为主流。越来越多的个人和企

区块链:法定数字货币技术路线的必然选择

区块链:法定数字货币技术路线的必然选择

在人类发展史上,货币的进化从未停止。从物物交换,到金属铸币,再到纸质货币,以及当前正在发展的数字货币正在向着越来越便捷的方向进化。 比特币的出世起初并未带来轰动,但是最近几年其价格惊人的爬高创造出了一个个造富神话,引起各国政府及监管机构的关注。虽然金融专家普遍认为它只是一种资产,而非货币,但是,其背后的区块链(Blockchain)技术引起了包括各大金融机构、政府、企业及学术界的浓厚兴趣,未

用区块链记录证书,证明真伪,墨尔本大学迈出了第一步

用区块链记录证书,证明真伪,墨尔本大学迈出了第一步

墨尔本大学宣布发起区块链认证和审核计划,允许通过一种隐私、安全且持久的方式验证学生的证书。 墨尔本大学正在试验一个区块链记录维护项目,允许接收者(即学生)存储他们的证书,出于核验目的,第三方也能访问这个系统。Learning Machine是这个发布系统的开发者,他们采用的是麻省理工媒体实验室(MIT Media Lab)在2016年提交的Blockcerts开源代码。 墨尔本大学副校长格雷

麦妖榜
更新日期 2019-09-03
排名用户贡献值
1牛市来了30910
2BitettFan24187
3等待的宿命23810
4区块大康20369
5六叶树20310
6linjm122719429
7天下无双16192
8lizhen00215280
9让时间淡忘14586
10yelanyi050511349
返回顶部 ↑