1
智能合约的定义
1994年,计算机科学家和密码学家 Nick Szabo 首次提出“智能合约”概念。它早于区块链概念的诞生。Szabo 描述了什么是“以数字形式指定的一系列承诺,包括各方履行这些承诺的协议”。虽然有它的好处,但智能合约的想法一直未取得进展——主要是缺乏可以让它发挥出作用的区块链。
直到 2008 年,第一个加密货币比特币才出现,同时引入了现代区块链技术。区块链最初是以比特币的底层技术出现的,各种区块链分叉导致发生很大的变化。智能合约在 2008 年依然无法融入比特币区块链网络,但在五年后,以太坊让它浮出水面。从此,涌现出了各种不同形式的智能合约,其中以太坊智能合约使用最广。
自以太坊开始,区块链是一个运行着智能合约的分布式平台:应用程序可以按照程序运行,不存在故障、审查、欺诈或第三方干预的可能性。智能合约给予了我们使用区块链技术来验证我们运行的代码的执行情况的能力。
智能合约定义
智能合约(英语:Smart contract )是一种旨在以信息化方式传播、验证或执行的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。
智能合约简单定义就是智能合约是可以处理 token 的脚本,围绕它可以发行,转移和销毁资产。这里说的资产是一个泛化的定义,不一定是币,可以是任何一种虚拟物品(比如应收,支付信息甚至加密猫)和现实世界的物品在区块链上的映射(比如舱单,抵押)。
CITA 智能合约
CITA 区块链框架使用的虚拟机 CITA-VM 和 EVM 采取同样的指令集,所以合约所使用的语言也是 solidity。由于 Ethereum 是目前全球最广泛的区块链网络,所以 solidity 也是使用最广泛的智能合约语言,围绕它的生态是非常丰富的,包括了合约调试,部署工具和保护合约安全的一些库。
这里再谈一下合约是由谁来执行的问题,在公链上,比如比特币或者以太坊,这些合约由我们称为“矿工”的参与方强制执行和证明。矿工其实是多台电脑(也可以称为矿机),它们把一项交易(执行智能合约,代币转账等) 以区块的形式添加到一个公开分账本上。使用者给这些矿工支付 “Gas”也就是手续费,它是运行一份合约的成本。
由于 CITA 是针对于企业的开放许可链框架,在 CITA 中矿工是出块节点,使用智能合约所需要的手续费是支付给出块节点的, gas 在这里叫做 quota。当然这里支付比例是可以自定义调整的,具体可以见文档。同时 CITA 可以调节为无币模式,在无币模式下,不存在手续费。
2
智能合约开发
现在,我们开始智能合约的开发部分,Solidity 与 Javascript 很接近,但它们并不相同。而且不能在一段代码上强加 JQuery,智能合约是无法调用区块链体系之外的代码的。同时还有一个特点是,你在开发的时候需要特别注意安全性,因为在区块链上的交易是不可逆的。
智能合约定义
通过一个例子说明基本语法,这里参考了 ethfans 上的一个例子,如果难以理解的话可以换一个,使用当时 PeckShield 讲的一个分饼干的例子。
现在,关于我们的第一个例子,我正在考虑一个由电影《时间规划局》启发的脚本。电影中,人们生活在一个反乌托邦式的未来,改用时间作为货币流通。他们可以通过掰手腕的方式赢取对手的时间(他们的“手臂”上存储着时间,输方的时间将会传送给赢家),我们也可以这么做!用智能合约以角力( Wrestling )的方式赚钱。
首先,solidity 脚本的基础是下面这段代码,pragma 指明正在使用的 Solidity 版本。Wrestling 是合约的名称,是一种与 Javascrip 上的类(class)相似的结构。
我们需要两个参与者,所以我们要添加两个保存他们账户地址的变量(他们的公钥),分别是 wrestler1 和 wrestler2 ,变量声明方式如下。
在我们的小游戏中,每一轮的比赛,参与者都可以投入一笔钱,如果一个人投入的钱是另一个人的两倍(总计),那他就赢了。定义两个玩家是否已经投入的flagwrestler1Played和wrestler2Played以及两位玩家投入的金额wrestler1Deposit和wrestler1Deposit。
还有判断游戏结束与否,赢家和收益的变量。
下面介绍一些关于公钥/私钥的规则,在区块链上每一个账户都是一对公私钥,私钥可以对一个信息进行签名,从而使这条信息可以被他人验证,被验证的时候它的公钥需要被使用到。在整个签名和验证的过程中,没有信息是加密的,实际上任何信息都是公开课查验的。
对于合约里面的变量,本质上来讲,也是可以被公开访问的。在这里要注意是的,即使一个变量是私有的,并不是说其他人不能读取它的内容,而是意味着它只能在合约中被访问。但实际上,由于整个区块链存储在许多计算机上,所以存储在变量中的信息总是可以被其他人看到,这是在区块链中一个很重要额原则。
另一方面,和很多编程语言很像,编译器会自动为公共变量创建 getter 函数。为了使其他的合约和用户能够更改公共变量的值,通知也需要针对不同的变量创建一个 setter 函数。
现在我们将为游戏的每一步添加三个事件。
开始,参与者注册;
游戏期间,登记每一轮赛果;
最后,其中一位参与者获胜。
事件是简单的日志,可以在分布式应用程序(也称为 dapps)的用户界面中调用 JavaScript 回调函数。在开发过程中,事件甚至可以用于调试的目的,因为不同于 JavaScript 有console.log() 函数,solidity 中是没有办法在 console 中打印出信息的。代码如下:
现在我们将添加构造函数,在 Solidity 中,它与我们的合约具有相同的名称,并且在创建合约时只调用一次。在这里,第一位参与者将是创造合约的人。msg.sender是调用该函数的人的地址。
接下来,我们让另一个参与者使用以下函数进行注册:
Require 函数是 Solidity 中一个特殊的错误处理函数,如果条件不满足,它会回滚更改。在我们的示例中,如果变量参与者2等于0x0地址(地址等于0),我们可以继续;如果参与者2的地址与0x0地址不同,这就意味着某个玩家已经注册为对手,所以我们会拒绝新的注册。可以把它认为是 solidity 中的if() {} else{}条件判断。
再次强调,msg.sender是调用该函数的帐户地址,并且当我们触发一个事件,就标志着角力的开始。
现在,每一个参与者都会调用一个函数, wrestle(),并投入资金。如果双方已经玩过这场游戏,我们就能知道其中一方是否获胜(我们的规则是其中一方投入的资金必须是另一方的双倍)。关键字payable意味着函数可以接收资金,如果它不是集合,函数则不会接受币。msg.value是发送到合约中的币的数量。
请注意,我们不是直接把钱交给赢家,在此情况下这并不重要,因为赢家会把该合约所有的钱提取出来;而在其他情况下,当多个用户要把合约中的以太币提取出来,使用withdraw模式会更安全,可以避免重入,在合约安全部分我们会详细讨论这些情况。
简单地说,如果多个用户都可以从合约中提取资金,那么任谁都能一次性多次调用withdraw函数并多次得到报酬。所以我们需要以这样一种方式来编写我们的取款功能:在他继续得到报酬之前,他应得的数额会作废。
它看起来像这样:
https://github.com/devzl/ethereum-walkthrough-1/blob/master/Wrestling.sol 有代码段。
智能合约的IDE
在区块链技术中,不仅转账是一笔交易,对合约中函数的调用和合约的部署都是以发送交易的方式完成。整个过程比较繁琐,正如同其他的变成语言一样,针对于 solidity 智能合约,我们也提供了 IDE (CITA IDE) 来编译和部署合约。
CITA 的IDE
CITA IDE 是基于 Ethereum 的 Solidity 编辑器进行修改并适配了 CITA ,是面向 CITA 的智能合约编辑器,能够编写、编译、debug、部署智能合约。可直接运行官方 CITA IDE 1(https://cita-ide.citahub.com)进行体验。
使用说明
browser 内置常用的模板合约,首先从内置合约模板中选择合适的模板开始开发Compile 本地编译,选择当前 solidity 版本,与合约 pragma 一致进入右侧的 Run 标签, 在 Deploy to CITA 中填入相关信息勾选 Auto ValidUntilBlock 则发送交易前会自动更新 validUntilBlock 字段勾选 store ABI on chain 则会在合约部署成功后将合约 ABI 存储到 CITA 上此处特别注意 Quota 的设置, 一般合约需要较多 Quota, 若 quota 不足, 在交易信息打印的时候可以查看 Error Message 获知点击 Load Contracts 加载当前编译完成的合约, 并选择要部署的合约点击 Deploy to CITA 发起部署合约的交易观察控制台的输出, 交易详细信息会显示在控制台上, 当流程结束时, 会输出交易 hash 和合约地址, 并且以链接形式支持到 Microscope 查看
DApp 及智能合约开发实例
First Forever 是一个DApp demo,展示了在 CITA 上开发一个最小可用的 DApp 的完整流程。
FIrst Forever 地址:
https://github.com/citahub/first-forever-demo/blob/develop/README-CN.md
以下是区块链DApp的开发步骤示意图:
在该项目中使用了一个简单的可以存储用户提交内容的智能合约,源码:SimpleStore
地址:
https://github.com/citahub/first-forever-demo/blob/develop/src/contracts/SimpleStore.sol
更详细的介绍看:如何动手做一个DApp
地址:https://github.com/citahub/first-forever-demo/blob/develop/README-CN.md
3
智能合约安全性
参考视频
[https://www.bilibili.com/video/av58299098]
因为智能合约是不可逆的,所以他的交易一旦形成,是无法回退的。在这种情形下,智能合约的安全性尤为重要。以下先介绍几种合约常见的合约安全性隐患,然后会给出改善他们的方法。
智能合约溢出型漏洞
16bit 整数:0x0000,0x0001,0x0002,…,0xfffd,0xffff
0x8000 + 0x8000 = 0x10000 = 0x0000 = 0
0xffff + 0x0003 = 0x10002 = 0x0002 = 2
0x0000 - 0x0001 = 0xffff = -1 = 65535
这个函数想要做到的是把 msg.sender 在合约中的 token 转给多个人,amount += _value[j];这个操作会存在溢出的风险,如果在加的时候出现状况 amount = 0x8000 + 0x8000 = 0,那么在后面一步的判断 require(balanceOf[msg.sender] >= amount);中会出现的实际判断的是balanceOf[msg.sender] >= 0那么可以从空的账户中把钱转出。
代码注入漏洞
可以把这个合约本身拥有的代币偷走转给别的用户,因为对于extraData 来说,自由度非常高,_spender.call(_extraData)可以是任何一个地址调用任何一个函数。
itchyDAOin MakerDAO 投票系统
这个主要是以一个比较复杂的例子来给学员讲合约中函数调用需要知道的地方,暗示智能合约还是比较难以把控的,需要多学习
以下是一个在 MakerDAO 中的投票系统,在这个投票系统中,一个sender 需要根据自己的权重对一个提案进行投票。
以下是投票函数,在投票以后把票数进行 addWeight 和 subWeight 操作。
最后一步是在 lock 一种币,在 lock 以后可以进行投票操作,在投票完成以后,可以 free 从而退回自己的币。
4
智能合约场景
长远看,遵循标准有很多不应忽视的益处。首先,如果遵照某个标准生成代币,那么每个人都会知道该代币的基础功能,并知道如何与之交互,因此就会有更多信任。去中心化程序(DApps)可以直接辨别出其代币特征,并通过特定的 UI 来与其打交道。另外,一种代币智能合约的标准实现已经被社区开发出来,它采用类似 OpenZeppelin 的架构。这种实现已经被很多大神验证过,可以用来作为代币开发的起点。
本文中会从头开始提供一个不完整的,但是遵循 ERC20 标准的,基础版的代币实现,然后将它转换成遵循 ERC721 标准的实现。这样就能让读者看出两个标准之间的不同。
出发点是希望大家了解代币是如何工作的,其过程并不是一个黑箱;另外,对于 ERC20 这个标准,尽管它至少已经被广泛接受两年以上,如果只是从标准框架简单地生成自己的代币,也还会存在某些不易发现的故障点。
ERC20 标准
ERC20(https://theethereum.wiki/w/index.php/ERC20_Token_Standard)是为同质(Fungible)代币标准设立的标准,可以被其它应用(从钱包到去中心化交易所)重复使用。同质意味着可以用同类的代币互换,换句话说,所有的代币都是等价的(就像钱币,某一美金和其它美金之间没有区别)。而一个非同质代币(Non-fungible Token)代表一种特定价值(例如房屋,财产,艺术品等)。同质代币有其内在价值,而非同质代币只是一种价值智能合约的代表。
要提供符合ERC20标准的代币,需要实现如下功能和事件:
标准不提供功能的实现,这是因为大家可以用自己喜欢的方式写出任何代码,如果不需要提供某些功能只需要按照标准(https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)返回 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位小数,因为遵循了以太坊的惯例,也没有什么特别的目的。
你需要统计一共发行了多少代币,并跟踪每人拥有多少:
当然,你需要从0个代币开始,除非在代币智能合约创建时候就生成了一些,如下例:
totalSupply()函数只是从totalSupply变量中获取数值:
接下来就是ERC20的神奇之处了,transfer()函数是将代币从一个地址发送到另外一个地址的函数:
以上基本就是 ERC20 代币标准的核心内容。
鉴于 ERC20 还存在其他一些问题,更安全容错的transferFrom()实现和其它方案被发布出来(如之前所说,该标准只是一些功能原型和行为定义,具体细节则靠开发者自己实现),并正在讨论中,其中就包括
ERC223(https://github.com/ethereum/EIPs/issues/223)ERC777(https://github.com/ethereum/EIPs/issues/777)
ERC223 方案的动机是避免将代币发送到错误地址或者不支持这种代币的合约上,成千上万的金钱因为上述原因丢失,这一需求作为以太坊后续开发功能的第 223 条记录第 223 条记录在案。ERC777 标准在支持其它功能的同时,对接收地址进行“即将收到代币”的提醒功能,ERC777 方案看起来很有可能替代 ERC20.
ERC721标准
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()函数会为代币设置新的拥有者:
尽管代码比较长,但却是转移代币流程中必不可少的步骤。
还必须注意,ERC721 也支持approve()和transferFrom()函数,因此我们必须在 transfer 函数内部加上其它限制指令,这样一来,当某个代币有了新的拥有者,之前的被授权地址就无法其代币进行转移操作,代码如下:
挖矿基于以上两种标准,可能面对同一种需求,要么产生同质代币,要么产生非同质代币,一般都会用一个叫做Mint()的函数完成。
实现以上功能函数的代码如下:
用任意一个数字产生一个新代币,根据不同应用场景,一般在合约内部只会授权部分地址可以对它进行铸币(mint)操作。
这里需要注意mint()函数并没有出现在协议标准定义中,而是我们添加上去的,也就是说我们可以对标准进行扩充,添加其它对代币的必要操作。例如,可以添加用以太币来买卖代币的系统,或者删除不再需要代币的功能。
来源:溪塔科技
本文来自互联网,如有侵权请与我们联系删除。