编写智能合约.docx
《编写智能合约.docx》由会员分享,可在线阅读,更多相关《编写智能合约.docx(24页珍藏版)》请在冰豆网上搜索。
编写智能合约
编写智能合约
1Solidity源文件
Solidity源文件使用的扩展名为.sol。
在源文件中,可以使用pragmaSolidity说明编写代码时用的编译器版本。
例如:
pragmaSolidity八042;
现在,源文件不会低于042的编译器版本,也不会用高于050的编译器版本进行编译(第二个条件使用八添加)。
2智能合约的结构
合约就像一个类(class),其中包含状态变量(statevariable)s函数(function)s函数修改器(functionmod帀e「)、事件(event)x结构(structure)和枚举(enum)o合约还支持继承,通过在编译时备份代码来实现。
最后,合约还支持多态。
下面来看一个智能合约的例子:
contractSample
{
//statevariable
uint256data:
addressowner;
eventlogData(uint256dataToLog);
modifieronlyOwnerO{
if(msg.sender!
=owner)throw;
}
functionSample(uint256initData,addressinitOwne「){
data=initData;
owner=initOwner;
}
functiongetData0returns(uint256returnedDataX
returndata;
}
functionsetData(uint256newData)onlyOwner{
logData(newData);
data=newData:
上述代码的工作原理如下:
1)使用contract关键字声明一个合约。
2)声明两个状态变量data和owner0data包含一些数据,owner包含所有者的以太坊钱包地址,即部署合约者的以太坊地址。
3)定义一个事件(event)。
事件用于通知客户端。
一旦data发生变化,将触发这个事件。
所有事件都保存在区块链中。
4)定义一个函数修改器(functionmodifier)o修改器用于在执行一个函数之前自动检测条件。
这里,修改器检测合约所有者是否在调用函数。
如果不是,就抛出异常。
5)得到合约构造函数(constructor)o在部署合约时,调用构造函数°构造函数用于初始化状态变量。
6)定义两个方法。
第一个方法用于得到data状态变量的值,第二个方法用于改变data的值。
在更深入地学习智能合约的函数之前,我们先来学习一些与Solidity有关的其他知识,然后再回答合约。
3数据位置
截止目前,我们学过的所有编程语言可能都把变量存储在内存中。
但是在Solidity中,根据情况的不同,变量可能不存储在内存和文件系统中。
根据情况的不同,数据总有一个默认位置。
但是对于复杂数据类型,例如字符串(string).数组(array)和结构类型(struct),可以用向类型添加storage或者memory进行重写。
函数参数(包枯返回参数)默认用memory,本地变量默认用storage.显然,对于状态变量来说,位置强制用storageo
数据位置很重要,因为它们会改变分配的行为:
•storage变量和memory变量之间的分配总是创建一个独立的备份。
但如果分配是从memory存储的一种复杂类型到另一种复杂类型,则不创建备份。
•到一个状态变量的分配(即使是来自其他状态变量)总是创建一个独立的备份。
•不能把memory中国存储的复杂类型分配给本地存储变量。
•在分配状态变量和本地存储变量的情况下,本地存储变量指向状态变量,也就是说,本地存储变量变为指针。
4什么是不同的数据类型
Solidity是一种静态类型语言,变量存储的数据类型需要预先定义。
所有变量默认都是0。
在Solidity中,变量是有函数作用范围的,在函数中任何地方声明的变量将对整个函数存在适用范围,无论它是在哪里声明的。
现在让我们看看Solidity提供的不同数据类型:
•最简单的数据类型是布尔值,可以是true或者false。
•uint8,uintl6,uint24,..„uint256分别用于存储无符号的8位,16位,24位,…246位整数。
同理,int8,intl6int256分别用于存储8位,16位,24位,...256位整数。
uint和int是
uint256和int256的别名。
类似于uint和int,ufixed和fixed代码分数。
ufixed0x8,
ufixed0xl6ufixed0x256分别用于存储未签名的8位,16位,24位,…,256位分数。
同理,fixedOx8,fixed0xl6fixed0x256分别用于存储8位,16位,24位,…,256位分
数。
如果一个数字超过256位,则使用256位数据类型存储该数字的近似值。
•address可以用于存储最大20字节的值(十六进制表示)。
它用于存储以太坊地址。
address类型有两个属性:
balance和send。
balance用于检测地址余额,send用于向地址发送以太币。
send方法拿出需要转账的那些数量的wei,并根据转账是否成功返回ture或者false。
wei从调用send方法的合约中扣除。
用户可以在Solidity中使用Ox前缀给变量分配一个十六进制的数值。
4.1数组类型
Solidity支持generic和byte两种数组类型。
它们支持固定长度和动态长度两种数组’也支持多维数组。
bytesl,bytes2,bytes3bytes32是字节数组的类型。
byte是bytesl的别名。
下面给出了generic数组语法的一个示例:
constractsample{
intQmyArray=[0,0];
functionsample(uintindex,intvalue){
myArraypndex]二value;
intQmyArray2=myArray;
uint24[3]memorymyArray3=[1,3,9999];uint8[2]myArray4=[1,2];
}
}
关于数组的重要内容如下:
•数组还有length属性,用于发现数组的长度°用户还可以给length属性分配一个值,以改变数组的大小,但不可以在内存中改变数组的大小,也不同可以改变非动态数组的大小。
•如果想访问动态数组的微设置索引(unsetindex),会抛出异常。
arraysstructs和map都不可以作为函数参数,也不可以用作函数返回值。
4.2字符串类型
在Solidity中,有两种方法创建字符串:
使用bytes和stringobytes用于创建原始字符串,而string用于创建UTF-8字符串。
字符串长度总是动态的。
下面给出了字符串语法的一个示例:
constractsample{
stringmyString=bytesmyRawString;
functionsample(stringinitString,bytesrawStringlnitXmyString=initString;stringmyString2=myString;
stringmemorymyString3二“ABCDE:
myString3二訣YZ:
myRawString二rawStringlnit;
myRawString」ength++;
stringmyString4="Example";//throwsexceptionwhilecompilingstringmyString5=initString;//throwsexceptionwhilecompiling
}
}
4.3结构类型
Solidity还支持结构类型(struct)o下面给出了struct语法的一个示例:
contractsample{
structmyStruct{
boolmyBool;
stringmyString;
}
myStructsi;
myStructs2=myStruct(true,M);
functionsample(boolinitBool,stringinitString){
si=myStruct(initBool,initString);
myStructmemorys3=myStruct(initBool,initString);
}
}
函数参数不可以是结构类型,且函数不可以返回结构类型。
4.4枚举类型
Solidity还支持枚举类型(enum)o下面给出了enum语法的一个示例:
contractsample{
enumOS{Windows,Linux,OSX,UNIX}
OSchoice;
functionsample(OSchosen){
choice=chosen;
}
functionsetUnuxOS0{
choice=OS.Linux;
}
functiongetChoiceOreturns(OSchosenOSX
returnchoice;
4.5mapping类型
mapping数据类型是一个哈希表omapping类型只可以存在与storage中,不存在与memory中,因此它们是作为状态变量声明的。
可以认为mapping类型包含key/value对,不是时间存储key,而是存储key和keccak256哈希,用于查询value。
mapping类型没有长度。
mapping不可以被分配给另一个mappingo下面给出一个创建和使用mapping的示例:
contractsample{
mapping(int=>string)myMap:
functionsample(intkey:
stringvalue){
myMap[key]=value;
mapping(int=>string)myMap2=myMap;
}
}
如果想访问mapping中不存在的key,返回的value均为0o
4.6delete操作符
delete操作符可以用于任何变量,将其设置成默认值。
默认值均为0.
如果对动态数组使用delete操作符,则删除所有元素,其长度变为0。
如果对静态数组使用delete操作符,则重置所有索引。
还可以通过对特定索位置使用delete来重置索引。
如果读map类型使用delete操作符,什么都不会发生。
但是如果对map类型的一个键使用delete操作符,则会删除与该键相关的值。
下面给出了delete操作符的一个示例:
contractsample{
structStruct{
mapping(int=>int)myMap:
intmyNumber;
}
intQmyArray;
StructmyStruct;
functionsample(intkey:
intvalue,intnumber,int[]array){
myStruct=Struct(number);
myStruct.myMap[key]二value;
myArray=array;
}
functionresetO{
deletemyArray:
deletemyStruct;
}
functiondeleteKey(intkey){
deletemyStruct.myMap[key];
4.7基本类型之间的转换
除了数组类型、字符串类型、结构类型、枚举类型和map类型外,其他类型均称为基本类型。
如果把一个操作符应用与不同的类型,编译器将尝试把一个操作数隐式转换微另一种类型。
通常来说,如果没有语义信息丢失,值和类型之间可以进行隐式转换:
uint8可转换微uintl6,uintl28可转换微int256,但int8不可转换微uint256(因为uint256不能存储,例如-l)o此外,无符号整数可以转换成同等大小或更大的字节,但是反之则不然。
任何可以转换成uintl60的类型都可以转换成地址。
Solidity也支持显示转换’所以如果编译器不允许在两种两种数据类型之间隐式转换,则可以进行显示转换。
建议尽量避免显式转换,因为可能返回难以预料的结果。
来看一个例子:
uint32a=0x12345678;
uintl6b二uintl6(a);//bwillbe0x5678now
这里是将uint32类型显示转换微uintl6,也就是说,把较大类型转换为较小类型,因此高位被砍掉了。
4.8使用var
Solidity提供了用于声明变量的va「关键字。
变量类型根据分配给它的第一个值来动态确定。
—旦分配了值,类型剧固定了,所以如果给它知道另一类型,将引起类型转换。
示例如下:
int256x=12;
vary=x;
uint256z=9;
y二乙
在定义数组array和map时不能使用varovar也不能用于定义函数参数和状态变量。
5控制结构
solidity支持if/else/whiIe/for/break/continue/return?
:
等控制结构。
下面给出了控制结构的—个示例:
constractsample{
inta=12;
intob;
functionsampleO{
if(a==12){}esleif(a==34)
}
else
{
}
vartemp=10;
while(temp<20)
{
if(temp二二17)
{
break;
}
else
{
continue;
}
tempi;
}
for(variii=0;iii
6用new操作符创建合约
—个合约可以使用new关键字来创建一个新合约,但前提示必须知道新创建的合约的完整代码。
示例如下:
contractsamplel
{
inta;
functionassign(intb)
{
a二b;
}
}
contractsample2{
functionsample2(){
samplels=newsamplel();
s.assign(12);
7异常
在一些情况下,异常会被自动抛出。
也可以使用throw动抛出异常。
抛出异常会停止回滚目前执行的调用(也就是说,撤销对状态和余额的所有改变)。
捕获异常是不可能的:
constractsample
{
functionmyFunction。
{
throw;
}
}
在Solidity中,有两种函数调用:
内部函数调用和外部函数调用。
内部函数调用是指一个函数在同一个合约中调用另一个函数。
示例如下:
contractsamplel
{
inta;
functionsamplel(intb)payable
{
a二b;
}
functionassign(intc)
{
a二c;
}
functionmakePayment(intd)payable}
a二d;
}
}
contractsample2{
samplels=(newsamplel).value(12)(23);
s.makePayment(22);
s.makePayment.value(45)(12);
s.makePayment.value(4).gas(900)(12);
this.helloO;
samplels2=samplel(addressOfContract);
s2.makePayment(112);
}
使用this关键字进行的调用称为外部调用。
在函数中,this关键字代表当前合约实例。
9合约功能
现在是时候深入学习合约了。
我们将看看一些新的功能,还将深入学习已经见过的一些功能。
9.1可见性
函数或者状态变量的可见性定义了谁可以看到它。
函数和状态变量的四种可见性:
external/public/intemal和private
函数可见性默认为public,状态变量可见性默认为internalo各可见性函数的含义如下:
•externaL外部函数只能有其他合约调用,或者通过交易调用。
外部函数f不能被内部函数调用,也就是说,f()没有用,但this.fO有用。
不能把external可见性应用到状态变量。
•publico公共函数和状态变量可以用所有可行办法访问。
编译器生成的存取器(accessor)函数都是公共状态变量。
用户不能创建自己的存取器。
事实上,它只生成getters,而不生成setterso
•internal内部函数和状态变量只可以内部访问,也就是说,从当前合约内核继承它的合约访问。
不可以使用this访问它。
•privateo私有函数和状态变量类似于内部函数,但是继承合约部可以访问它们。
下面给出了可见性和存取器(accessor)的一个示例:
contractsamplel
{
intpublicb=78;
intinternalc=90;
functionsamplel()
{
this.aQ;
aQ:
b=21;
this.b;this.b();
this.b(8);
this.cO:
c=9;
}
functionaOexternal
{
}
}
contractsample2
{
intinternald=9;intprivatee=90;
}
contractsample3issample2
{
samplels;
functionsample3()
{
s=newsamplelO;
s.aO;
varf=s.b;
s.b二18;
s.cO:
d二8;
e=7;
}
}
9.2函数修改器
我们之前看到了函数修改器(functionmod帀e「)的概念,还编写了一个基本的函数修改器,现在来深入学习修改器。
修改器由子合约(childcontract)继承,且子合约可以对其重写。
可以通过用空格分隔的列表指定修改器将多个修改器应用到一个函数,并将多个修改器按顺序估值;还可以像修改器传送实参。
在修改器中,无论下一个修改器或者函数体二者哪个先到达,会白插入到】”;出现的地方。
让我们来看一个函数修改器的复杂代码例子:
contractsample
{
inta=90;
modifiermyModifierl(intb){
intc=b;
a二8;
}
modifiermyModifier2{intc=a;
}
modifiermyModifier3{a=96;
return;
a=99;
modifiermyModifier4{
intc=a;
}
functionmyFunctionOmyModifierl(a)myModifier2myModifier3return(intd)
{
a=1;
returna;
}
}
myFunctionO的执行代码如下:
intc=b;
intc=a;
a=96;
return;
intc=a;
a=1;
returna;
a=99;
c=a;
a二8;
在上述代码中调用myFunctionO方法时,将返回0•但是之后返回状态变量a时,将得到8.修改器或者函数体中的retum(返回)立即离开整个函数,返回值被分配成它需要成为的任何变量。
就函数来说,return之后的代码在调用者的代码完成运行后再执行,就修改器来说,上述修改器中的之后的代码在调用者的代码完成运行后在执行。
在上面的例子中,第5、6和7行从未执行过。
在第4行之后,执行从第8J0行开始。
修改器中的「etum不可以有相关值,它总是返回全0。
9.3回退函数
—个合约可以有唯一的未命名函数,称为回退函数(fallbackfunction)o该函数不能有实参不能返回任何值。
如果其他函数都不能匹配给定的函数标识符,就在合约上执行回退函数。
当合约不用任何函数调用及接受以太币(即交易发生以太币给合约却不调用任何方法)时,也执行该函数。
在此情况下,用于函数调用的gas通常很少(准确地说是2300gas),所以使回退函数尽可能便宜很重要。
接收以太币但是却不定义回退函数的合约会抛出异常,把以太币发送回去,所以如果你想让你的合约接收以太币,就必须要实现回退函数。
下面给出了回退函数的一个示例:
constructsample
{
functionQpayable
//keepanoteofhowmuchEtherhasbeensentbywhom
9.4继承
Slidity通过代码备份(包括多态)支持多重继承(multipleinheritance)。
即使一个合约继承自其它多个合约,在区块链上值创建一个合约,来自父合约(parentcontract)的代码总是被复制到最终合约里。
示例如下:
contractsamplel
{
functionaOO
functionb(){}
}
contractsample2issamplel
{
functionb(){}
}
contractsample3
{
functionsample3(intb)
{
}
}
contractsample4issamplel,sample2
{
functionaOO
functionc(){
aO;
samplel.a();
bO;
}
}
contractsample5issample3(122)
{
}
1.super关键字
supe「关键字用于引用最终继承链中的下一个合约,示例如下:
constractsamplel
{}
constractsample2{}
constractsample3issample2{}
constractsample4issample2{}
contractsample5issample4{
functionmyFuncQ{}
contractsample6issamplel,sample2,sample3,sample5
{
functionmyFuncO
{
super.myFuncO;
}
}
其中,引用sample6合约的最终继承链式sample6,sample5,sample4,sample2,sannple3和samplelo继承链始于衍生最充分的合约,终于衍生最不充分的合约。
2.抽象合约
仅