Solidity智能合约开发技术与实战-智能合约与函数_第1页
Solidity智能合约开发技术与实战-智能合约与函数_第2页
Solidity智能合约开发技术与实战-智能合约与函数_第3页
Solidity智能合约开发技术与实战-智能合约与函数_第4页
Solidity智能合约开发技术与实战-智能合约与函数_第5页
已阅读5页,还剩114页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

以太坊Solidity智能合约开发教程智能合约与函数学前提示在第3章介绍的实例中,已经涉及到智能合约和函数的概念以及基本应用。智能合约开发是本书的主题,而智能合约的主要功能是通过函数实现的。本章将对智能合约和函数编程的细节做深入的介绍。知识要点5.1智能合约编程基础5.2函数编程基础5.3函数的高级应用5.4外部函数5.5抽象合约和接口5.6异常处理5.1智能合约编程基础5.1.1状态变量的可见性5.1.2智能合约的构造函数5.1.3智能合约之间的继承5.1.1状态变量的可见性

public,指定公有变量,也就是可以在其他智能合约中访问的状态变量。

private,指定私有变量。私有变量只能在定义它的智能合约中访问它。在其他智能合约中都不能访问私有变量,包括派生的智能合约。

internal,指定内部变量,也就是可以在定义它的智能合约、以及派生智能合约中访问的状态变量。派生和继承派生是面向对象编程中的机制,也可以说是继承。假定有一个智能合约A,可以从A派生一个新的智能合约B。反过来讲,也可以说B继承了A。以使用is关键字指定智能合约之间的派生关系pragmasolidity^0.5.1;contractA{uinta=10;}contractBisA{//……}getter访问器对于公有变量,Solidity提供一个getter访问器,用于在智能合约外访问该变量。getter访问器是与变量同名的函数,例如,状态变量a的getter访问器是a()。使用a()可以获取变量a的值,但是不能设置a的值。

【例5-1】pragmasolidity^0.5.1;//声明合约contractA{uintpublicdata=10;}

contractB{functiongetValue()publicreturns(uint){Aa=newA();returna.data();}}手续费在以太坊网络中,对智能合约中的状态变量的修改是要收取Gas的,因为状态变量是记录在区块上的,是需要矿工记账的,所以要支付手续费。而且部署和执行智能合约都是要占用以太坊的资源,因此也是要收取Gas的。选择要部署的智能合约部署智能合约时会扣减当前账号的以太币执行getValue()函数的交易过程和结果单击getValue按钮,注意观察“当前账号”中以太币的变化。同时,在窗口中间底部的控制台,会出现一个表格,记录了执行getValue()函数的交易过程和结果具体说明如下·status:交易的状态为已经被矿工记账,执行成功。当然是在虚拟机环境下。·transactionhash:交易的哈希摘要。·from:支付Gas的以太坊账户。这里显示的是之前选择的“当前账号”。·to:接收gas的以太坊账户。这里显示的是0xbbf289d846208c16edc8474705c748aff07732db,也就是说明是因为执行.getValue()函数而消耗的gas。·gas:显示智能合约的执行者愿意支付的gas上限,因为智能合约可能有bug,比如进入死循环状态,很可能循环写入状态变量。如果不设置上限,就可能会耗尽账户里的以太币。gas上限默认为3000000,可以在窗口右侧的“运行”面板中设置设置gas上限

transactioncost:交易所花费的总的gas,本例中为106094gas。

executioncost:虚拟机(vm)执行计算所花费的gas,本例中为84822gas。executioncost包含在transactioncost中。

hash:交易的哈希摘要,与transactionhash是一样的。

input:经过编码的交易的输入数据。

decodedinput:解码后的交易输入数据,本例没有输入数据。

decodedoutput:解码后的交易输出数据,本例的输出数据为一个uint256类型的数据10。

logs:交易的日志信息,本例并没有记录日志。

value:交易金额。在窗口右侧的“运行”面板中可以设置愿意支付的交易金额。设置交易金额5.1.2智能合约的构造函数一个智能合约中只能有一个构造函数。构造函数在实例化智能合约时会自动被调用。通常可以通过构造函数为状态变量指定初始值。可以使用constructor关键字定义构造函数。构造函数的例子pragmasolidity^0.5.1;//声明合约contractA{uinta;

constructor(uint_a)public{a=_a;}}【例5-2】pragmasolidity^0.5.1;contractPurchase{stringpublicname;uintpublicprice;addresspayablepublicseller;

constructor(stringmemory_name,uint_price)public{name=_name;price=_price;seller=msg.sender;}}5.1.3智能合约之间的继承继承智能合约后,子合约会自然拥有父合约中状态变量和公有、内部函数的访问权。【例5-3】pragmasolidity^0.5.1;contractAnimal{stringpublicname;uintpublicprice;uintinternalage;addressinternalowner;

functionsetName(stringmemory_name)public{name=_name;}

functiongetName()publicviewreturns(stringmemory){returnname;}functionsetPrice(uint_price)public{price=_price;}

functiongetPrice()publicviewreturns(uint){returnprice;}functionbuy()public{owner=msg.sender;}

functiongetOwner()publicviewreturns(address){returnowner;}}contractDogisAnimal{

functionsetAge(uint_age)public{age=_age;}

functiongetAge()publicviewreturns(uint){returnage;}

}子智能合约Dog从Animal继承的状态变量和函数5.2函数编程基础5.2.1定义函数5.2.2函数修饰词5.2.1定义函数function函数名(参数列表)函数修饰符returns(返回值类型列表){

函数体

return返回值;}【例5-4】pragmasolidity^0.5.1;contractDemo{functionadd(uint_a,uint_b)publicpurereturns(uint){return_a+_b;}}部署在开发工具面板中单击“运行”选项卡,单击“部署”按钮,在下部的已部署合约中会出现一个新的记录Demoat0x692...77b3a(memory)。单击此记录,会在下面出现一个文本框和一个add按钮。在文本框中输入1,2,返回结果如下:

0:uint256:3

运行例5-1的结果分别用于输入参数_a和_b多个返回值在Solidity语言中,函数是可以有多个返回值的。只有在returns()中指定多个返回值的类型,用逗号(,)分隔。例如,指定返回2个uint类型的返回值的代码如下:returns(uint,uint)【例5-5】pragmasolidity^0.5.1;//声明合约contractDemo{functionswap(uint_x,uint_y)publicpurereturns(uint,uint){return(_y,_x);}}5.2.2函数修饰词定义函数时,在函数名和参数后面需要指定函数修饰词。函数修饰词包括可见性修饰词、状态修饰词和payable修饰词和自定义修饰词(函数修改器)等类型。1.可见性修饰词除了public、private和internal外,还可以使用external修饰符指定函数可以从智能合约的外部调用该函数。2.状态性修饰词状态性修饰词说

明pure指定函数既不向区块链上写数据,也不从区块链上读取数据。即在函数中只操作存储位置为memory的变量,不访问区块链view指定函数不向区块链上写数据,但可以从区块链上读取数据constant与view的作用相同3.payable修饰词payable修饰词允许函数在被调用的同时接收以太币。接受的以太币会存储在智能合约的账户中,可以在函数的代码中将以太币转账到其他账户。例如下面的代码定义了一个智能合约OnlineStore,其中包含一个payable函数buysometing(),代码如下:

contractOnlineStore{functionbuySomething()externalpayable{require(msg.value==0.001ether);transferThing(msg.sender);}}require()函数指定运行buysometing()函数需要支付0.001个以太币到智能合约的账户。如果调用者的账户中没有足够的以太币,将抛出异常,无法运行下面的代码。msg.value代表调用者向合约发送以太币的数量。external关键字指定buysometing()函数是可以被外部调用的。可以调用transfer()函数完成支付账户地址.transfer(转账金额);【例5-6】pragmasolidity^0.5.1;contractPayDemo{addresspayableaccount1=0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C;functionpay()payablepublic{account1.transfer(msg.value);}functiongetAccount1Balance()viewpublicreturns(uint){returnaccount1.balance;}//读取合约发起方的余额

functiongetOwnerBalance()viewpublicreturns(uint){

addressOwner=msg.sender;returnOwner.balance;}}代码说明如下·pay()函数:用payable修饰的函数,表明调用此函数时会向智能合约PayDemo的账户里面支付以太币。然后程序会调用account1.transfer(msg.value)函数将收到的以太币转账至账户account1。·getAccount1Balance()函数:返回账户account1的余额。·getOwnerBalance()函数:返回合约发起方账户余额。EIP-55格式的账户地址程序中账户account1被设定为Remix当前账户下拉框中的一个测试账户,假定为0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C。这里的地址既包含小写字母也包含大写字母,这就是EIP-55格式的账户地址。EIP-55格式的账户地址自带地址校验机制,将地址中的部分字母大写,与剩余的小写字母来形成校验和,让地址拥有自校验的能力。这样,如果用户不小心填错了地址中的一个字母,就会被及时发现。访问etherscan网站可以将给定的以太网地址转换为EIP-55格式的账户地址(1)在Remix窗口左上部的“运行”面板中,选择“当前账号”下拉框中的一个余额为100ether地址,单击后面的图标,可以将选中的地址复制到剪贴板。得到一个全部都是小写字母和数字组成的账户地址,例如0x14723a09acff6d2a60dcdf7aa4aff308fddc160c。(2)访问如下网址,借助etherscan得到EIP-55格式的账户地址。

https://etherscan.io/address/0x14723a09acff6d2a60dcdf7aa4aff308fddc160c借助etherscan得到EIP-55格式的账户地址例5-3的运行结果4.函数修改器函数修改器是执行函数的前提条件,它可以定义一个条件,只有当此条件满足的情况下,才会执行函数。定义函数修改器的方法如下:modifier修改器名{

条件体.._;}在函数a()上应用函数修改器的方法functiona()修改器名{

函数体..}【例5-7】pragmasolidity^0.5.1;/***权限控制*/contractModifierDemo{addresspublicowner=msg.sender;//检查必须是合约的所有者

modifieronlyOwner{require(msg.sender==owner,"必须是合约的所有者");_;}

//改变合约的拥有者身份

//newOwner是新所有者的地址

functionchangeOwner(address_newOwner)onlyOwner{if(_newOwner==0x0)throw;owner=_newOwner;}}【例5-8】pragmasolidity^0.5.1;contractmodifierDemo{addressseller;

modifierOnlySeller(){require(msg.sender==seller,"必须是卖家");_;}

functionOnSell()public{seller=msg.sender;}//取消交易

functionAbort()viewpublicOnlySeller{

//......}代码说明如下·seller:用于保存卖家的账户地址。·OnSell()函数:模拟商品上架。将调用此函数的账户地址赋值到seller中。·OnlySeller()函数修改器:指定调用者必须是seller,否则抛出异常。·Abort()函数:模拟商品下架,应用OnlySeller()函数修改器,指定调用者必须是seller,否则抛出异常。5.2.3函数的参数1.形参和实参2.值类型和引用类型1.形参和实参在定义函数时指定的参数被称为形参;在调用函数时指定的参数被称为实参。例如,在例5-4中函数add()的2个参数_a和_b都是形参,如果使用add(1,2)调用函数,则1和2为实参。也可以使用变量作为实参。2.值类型和引用类型函数的参数可以分为值类型和引用类型2种。在调用函数时需要给值类型参数传递一个值,也就是将指定的值复制到值类型参数变量的地址中,值类型参数可以是整型、地址、定长字节数组和枚举类型。在调用函数时需要给引用类型参数传递一个地址也就是说在函数中对参数进行操作,相当于直接操作传递进来的变量,引用类型参数可以是不定长字节数组、字符串、数组和结构体,这些数据类型的参数还需要使用memory或storage修饰符指定它是值类型还是引用类型。memory指定参数为值类型,storage指定参数为引用类型。例如,如果一个字符串参数不指定memory或storage修饰符,会无法通过编译。【例5-9】pragmasolidity^0.5.1;contractParameterDemo{strings="test";

functionSetString(stringstorage_str)internal{bytes(_str)[0]="T";}functionSetStrTest()publicreturns(stringmemory){

SetString(s);returns;}functionGetS()publicviewreturns(stringmemory){returns;}5.3函数的高级应用5.3.1自毁函数5.3.2函数重载5.3.3内置函数5.3.1自毁函数自毁函数是一个特殊的函数。它有着固定的名称和固定的参数。用于销毁当前智能合约,并将当前合约账户中的余额发送到指定的地址。自毁函数的名称为selfdestruct,它有且只有一个address参数,用于指定接收当前合约账户余额的账户地址。自毁函数默认存在,不需要显式定义。【例5-10】演示自毁函数的使用方法pragmasolidity^0.5.1;contractselfdestructDemo{constructor()payablepublic{}functionkill(addresspayable_add)public{selfdestruct(_add);}}5.3.2函数重载函数重载是指函数的命名相同,但是函数的参数定义不同。参数定义不同有如下2层含义:(1)参数的数量不同。(2)参数的类型不同。下面代码定义的2个fun1()函数并不是函数重载contractOverload{//error,重名方法

functionfun1()public{}functionfun1()public{}}下面代码定义的2个fun2()函数也并不是函数重载,因为它们只有返回值不同contractOverload{//error,重名方法

functionfun2()publicreturns(uint){}functionfun2()publicreturns(string){}}下面代码定义的2个fun3()函数在编译时被认为是函数重载,不会报错。但是在传入参数小于256时,程序会报错,因为无法匹配合适的函数(2个函数都可以匹配)。contractOverload{//error,重名方法

functionfun3(uint_a)public{}functionfun3(uint8_a)public{}}【例5-11】演示函数重载的情况pragmasolidity^0.5.1;contractOverload{functionfunc1()publicpurereturns(uint){return0;}functionfunc1(uint_a)publicpurereturns(uint){return_a;}}例5-11的部署情况5.3.3内置函数1.获取区块和交易属性的函数2.数学和密码学相关函数1.获取区块和交易属性的函数函数说

明blockhash(uintblockNumber)returns(bytes32)返回指定区块的哈希block.coinbase(address)returns(address)返回币基地址,也就是挖出当前区块的矿工地址block.difficulty(uint)returns(uint)返回当前区块难度Block.getGasLimit()returns(uint)返回当前区块Gas上限block.number()returns(uint)返回当前区块号block.timestampreturns(uint)返回当前区块的时间戳gasleft()returns(uint256)返回剩余的Gasmsg.sender()返回消息的发送者,调用函数的账户地址msg.value()返回随消息发送的wei的数量now()block.timestamp的别名tx.gasprice()返回交易的Gas价格tx.origin()返回交易的发起者2.数学和密码学相关函数函数说

明addmod(uintx,uinty,uintk)returns(uint)计算

(x+y)%kmulmod(uintx,uinty,uintk)returns(uint)计算(x*y)%kkeccak256((bytesmemory)returns(bytes32)计算Keccak-256哈希sha256(bytesmemory)returns(bytes32)计算参数的SHA-256哈希ripemd160(bytesmemory)returns(bytes20)计算参数的RIPEMD-160哈希ecrecover(bytes32hash,uint8v,bytes32r,bytes32s)returns(address)利用椭圆曲线签名恢复与公钥相关的地址,如果出现错误则返回05.4外部函数在其他Solidity文件中定义的函数被称为外部函数。5.4.1导入外部函数5.4.2函数库5.4.3Fallback函数5.4.1导入外部函数假定在一个智能合约Test中定义一个测试函数add(),代码如下:pragmasolidity^0.5.1;contractTest{constructor()payablepublic{

}

functionadd(uint_a,uint_b)publicpurereturns(uint){return_a+_b;}}在另一个智能合约中ImportTest中导入外部函数add()的方法如下import"./test.sol";//导入外部的sol文件。(Test合约)

pragmasolidity^0.5.1;import"./test.sol";//导入外部的sol文件。(Test合约)

contractImportTest{

functiontest()publicreturns(uint){Testt=newTest();returnt.add(1,2);//调用Test合约中的add()函数

}}调用外部函数add()的执行结果5.4.2函数库在Solidity中,函数库(Libraries)是函数的集合。它类似于智能合约,也对应一个地址,但是函数库只能部署一次。在智能合约中可以通过DELEGATECALL方式调用库中的函数。此时函数是在智能合约的上下文环境中运行的。在函数库中,可以使用this指向调用它的智能合约,但是只能访问调用合约明确提供的状态变量。使用library关键字定义函数库library<函数库名>{

函数1定义函数2定义

………

函数n定义}【例5-12】pragmasolidity^0.5.1;libraryMath{functionadd(uint_x,uint_y)publicpurereturns(uint){return_x+_y;}functionminus(uint_x,uint_y)publicpurereturns(uint){return_x-_y;}functionmultiple(uint_x,uint_y)publicpurereturns(uint){return_x*_y;}functiondivide(uint_x,uint_y)publicpurereturns(uint){return_x/_y;}}函数库Math保存为math.sol。创建智能合约TestMath,其中调用函数库Mathpragmasolidity^0.5.1;import"./math.sol";contractTestMath{usingMathforuint;functionTestAdd()publicpurereturns(uint){uinta=1;returna.add(2);}functionTestMinus()publicpurereturns(uint){uinta=10;returna.minus(2);}functionTestMultiple()publicpurereturns(uint){uinta=10;returna.multiple(2);}functionTestDivide()publicpurereturns(uint){uinta=10;returna.divide(2);}}例5-6的运行结果例5-6的计算过程函数调用函数库Math中的函数计算过程结果TestAdd()add(1,2)1+23TestMinus()minus(10,2)10-28TestMultiple()multiple(10,2)10*220TestAdd()divide(10,2)10/255.4.3Fallback函数所谓Fallback函数也叫做回退函数。Fallback函数没有函数名,也没有参数和返回值。一个智能合约只能包含一个Fallback函数。以下两种情况下会调用Fallback函数(1)调用合约时,没有匹配上任何一个函数。在编译的过程中,Solidity会对程序的语法进行检查。因此,不可能显式地调用一个不存在的函数。但是可以使用Solidity提供的底层函数address.call()来模拟这种情况。(2)智能合约在接收以太币时。1.调用不存在的函数使用底层函数address.call()可以模拟调用一个不存在的函数的情形。使用address.call()函数的方法如下:address.call(<函数选择器,参数列表>)函数选择器函数选择器可以看做是函数的标识,具体情况将在第6章中介绍。这里只需要了解通过下面的方法可以的得到函数选择器:函数选择器=abi.encodeWithSignature("<函数名>(参数类型列表)"));实际上就是对函数名及其参数类型列表进行哈希运算后取前4个字节。【例5-13】pragmasolidity^0.5.1;contractExecuteFallback{functioncallNonExistFunc()publicreturns(bool){bytesmemoryfuncIdentifier=abi.encodeWithSignature("functionNotExist()");(boolsuccess,bytesmemoryreturnData)=address(this).call(funcIdentifier);returnsuccess;例5-13的日志2.智能合约接受以太币时需要定义Fallback函数智能合约在接受以太币时,会自动调用Fallback函数。一个没有定义回退函数的合约。如果接收以太币,会触发异常,并返还以太币。所以如果智能合约要接收以太币,就需要定义Fallback函数。而且此时Fallback函数必须要有payable修饰符。【例5-14】

//使用send()发送ether,观察会触发fallback函数

functionsendEther()public{boolresult=address(this).send(1);//从合约地址的余额中发送1wei给它自己,所以其balance不会变,只是会消耗msg.sender账户gasemitSendEvent(address(this),1,result);}}SendEvent事件eventSendEvent(addressto,uintvalue,boolresult);智能合约SendFallback中定义一个Fallback函数

//fallback函数及其事件

eventFallbackTrigged(bytesdata);function()publicpayable{//一定要声明为payable,否则send()执行结果将会始终为falseemitFallbackTrigged(msg.data);}getBalance()函数functiongetBalance()publicviewreturns(uint){returnaddress(this).balance;}初始化时调用sendEther()函数的日志deposit()函数functiondeposit()publicpayable{}智能合约的账户中有足够余额时调用sendEther()函数的日志5.5抽象合约和接口5.5.1抽象合约5.5.2接口5.5.3继承5.5.1抽象合约抽象合约指包含抽象函数的智能合约。抽象函数没有函数体,函数声明头以;结束。例如,下面就是一个抽象合约的定义,其中包含一个抽象函数cry()。contractPet{functioncry()publicreturns(stringmemory);}抽象合约也可以包含非抽象函数例如,抽象合约Pet中可以定义一个run()函数,代码如下:pragmasolidity^0.5.1;contractPet{functioncry()publicreturns(stringmemory);functionrun()publicreturns(stringmemory){return"run";}}抽象合约不能被部署5.5.2接口接口类似于抽象合约,也包含抽象函数。但是接口中不包含非抽象函数。例如5.5.1节中介绍的run函数就不能包含在接口中。接口具有如下特性·接口不能继承其他智能合约和接口。·接口中不能定义变量。·接口不能定义构造函数。·接口不能定义结构体。·接口中不能定义枚举类型。使用interface关键字定义接口interfacePet{functioncry(stringmemory)public;}5.5.3继承可以使用is关键字定义智能

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论