当前位置: 首页 > 科技观察

Solidity用于智能合约编写的基本特性

时间:2023-03-15 00:31:20 科技观察

上一篇介绍过,目前包括FISCOBCOS在内的大多数联盟链平台都使用Solidity作为智能合约开发语言,因此有必要熟悉并掌握从Solidity开始。作为专为区块链平台设计的图灵完备编程语言,Solidity支持函数调用、修饰符、重载、事件、继承等多种特性,在区块链社区具有广泛的影响力和热度。社区支持。但对于区块链新手来说,Solidity是一门陌生的语言。智能合约编写阶段将从Solidity的基本特性、高级特性、设计模式和编程策略开始。将引导读者了解Solidity并掌握其使用,从而更好地开发智能合约。本文将重点介绍Solidity的基本特性,带你开发一个基本的智能合约。智能合约代码结构任何编程语言都有其标准的代码结构来表达如何在代码文件中组织和编写代码,Solidity也是如此。在本节中,我们将通过一个简单的合约示例来了解智能合约的代码结构。pragmasolidity^0.4.25;contractSample{//状态变量地址private_admin;uint私人状态;//ModifiermodifieronlyAdmin(){require(msg.sender==_admin,"你不是admin");_;}//事件eventSetState(uintvalue);//构造函数constructor()public{_admin=msg.sender;}//函数functionsetState(uintvalue)publiconlyAdmin{_state=value;发射SetState(值);}functiongetValue()publicviewreturns(uint){return_state;}}上述程序包括以下功能:通过构造函数部署合约通过setValue函数设置合约状态通过getValue函数查询合约状态整个合约主要分为以下几个部分:状态变量-_admin,_state,这些变量会被永久保存,也可以被函数修改给函数加一层“外衣”函数——setState、getState,用于读写状态变量下面将对以上组件一一介绍。状态变量状态变量是合约的骨髓,记录了合约的业务信息。用户可以通过函数修改这些状态??变量,这些修改也会包含在交易中;交易经区块链网络确认后,修改生效。uint私人状态;状态变量的声明方式为:[类型][访问修饰符-可选][字段名]。构造函数构造函数用于初始化合约,允许用户传入一些基础数据,写入状态变量。上面的例子中设置了_admin字段作为后面演示其他功能的先决条件。constructor()public{_admin=msg.sender;}与Java不同,构造函数不支持重载,只能指定一个构造函数。函数函数用于读取和写入状态变量。对变量的修改将包含在交易中,并在得到区块链网络确认后才会生效。生效后,修改将永久保存在区块链账本中。函数签名定义函数名称、输入和输出参数、访问修饰符和自定义修饰符。functionsetState(uintvalue)publiconlyAdmin;函数也可以返回多个返回值:functionfunctionSample()publicviewreturns(uint,uint){return(1,2);}在这个合约中,还有一个带有view修饰符的函数。此视图表明该函数不修改任何状态变量。和view类似,还有修饰符pure,表示该函数是一个纯函数,甚至连状态变量都不需要读取,函数的运行只依赖于参数。functionadd(uinta,uintb)publicpurereturns(uint){returna+b;}如果试图修改视图函数中的状态变量,或者访问纯函数中的状态变量,编译器会报错错误。事件事件类似于日志,会记录在区块链中,客户端可以通过web3订阅这些事件。定义一个事件:eventSetState(uintvalue);构造一个事件:emitSetState(value);这里有几点需要注意:事件的名称可以任意指定,不一定要和函数名联系起来,但是建议两者都联系起来,清楚的表达发生了什么。构造事件时,也可以不写emit,但是由于事件和函数在名称和参数上的关联度很高,容易把事件写成函数调用,所以不建议不写。functionsetState(uintvalue)publiconlyAdmin{_state=value;发射SetState(值);//下面也是可以的,但是不推荐,因为容易写错setState//SetState(value);}Solidity编程风格应该采用一定的规范。编程风格请参考:https://learnblockchain.cn/docs/solidity/style-guide.html#id16ModifierModifier是合约中非常重要的一部分。它挂在函数声明上,为函数提供一些额外的功能,比如检查、清理等。在这个例子中,修饰符onlyAdmin要求在调用函数之前,需要检查函数的调用者是否是部署函数时设置的管理员(即合约的部署者)。//ModifermodifieronlyAdmin(){require(msg.sender==_admin,"你不是admin");_;}...//FunctionsfunctionsetState(uintvalue)publiconlyAdmin{...}值得注意的是,修饰符中定义的下划线“_”表示函数调用,指的是开发者修改的函数与修饰符。在这种情况下,它表达了setState函数调用的意思。智能合约的运行了解了上面智能合约示例的结构后,就可以直接运行了。合约的运行方式有多种,您可以选择其中的一种:方式一:您可以使用FISCOBCOS控制台部署合约详情请参考:https://fisco-bcos-documentation.readthedocs。io/zh_CN/latest/docs/installation.html#id7方法二:使用FISCOBCOS开源项目WeBASE提供的在线ideWEBASE-front操作方法三:通过在线ideremix部署并运行合约。remix的地址为:http://remix.ethereum.org/本例以remix作为运行示例。编译首先,在remix的在线ide中输入代码后,通过编译按钮进行编译。成功后,按钮上会出现一个绿色的勾:部署编译成功后,可以进行部署链接,部署成功后会出现合约实例。部署setState合约后,让我们调用setState(4)。执行成功后,会生成交易收据,其中包含交易的执行信息。在这里,用户可以看到交易执行状态(status)、交易执行者(from)、交易输入输出(decodedinput、decodedoutput)、交易成本(executioncost)和交易日志(logs)。在事务日志中,我们看到抛出了SetState事件,里面的参数也记录了事件传入的值4。如果我们使用另一个帐户执行它,调用将失败,因为onlyAdmin修饰符将阻止用户调用。getState调用getState后,直接可以看到得到的值为4,正是我们之前setState传入的值:Solidity数据类型前面的例子中,我们使用了uint等数据类型。由于Solidity类型设计比较特殊,这里也简单介绍一下Solidity数据类型。IntegerSeriesSolidity提供了一组数据类型来表示整数,包括无符号整数和有符号整数。每一类整数还可以根据长度进行细分,具体的细分类型如下。类型长度(位)signeduint256nouint88nouint1616no......nouint256256noint256yesint88isint1616is......isint256256isfixed-lengthbyteseriesSolidity提供了从bytes1到bytes32的类型,都是定长字节数组,用户可以读取定长字节的内容。函数bytesSample()public{bytes32数组;//初始化baarray//读取bray[0]byteb=barray[0];此外,整数类型可以转换为字节。uint256s=1;bytes32b=bytes32(s);这里有一个关键的细节,Solidity采用big-endian编码,高地址存放little-endian整数。例如b[0]为低地址端,存放整数的高位,所以值为0;取b[31]为1。函数bytesSample()publicpurereturns(byte,byte){uint256value=1;bytes32b=bytes32(值);//应该是(0,1)return(b[0],b[31]);}changeLongBytes通过以上,读者可以理解定长字节数组。此外,Solidity还提供了一个变长字节数组:bytes。用法类似于数组,后面会介绍。StringSolidity提供的字符串本质上是UTF-8编码字节数组的字符串,兼容变长字节类型。目前Solidity对字符串的支持不是很好,没有字符的概念。用户可以将字符串转换为字节。函数stringSample()publicviewreturns(bytes){stringmemorystr="abc";字节内存b=bytes(str);//0x616263返回b;}需要注意的是,在将string转为bytes时,并不会复制数据内容本身,如上,str和b变量都指向同一个字符串“abc”。地址类型address代表账户地址,由私钥间接生成,是一个20字节的数据。同样,也可以转换成bytes20。functionaddressSample()publicviewreturns(bytes20){addressme=msg.sender;bytes20b=bytes20(我);返回b;}Map映射的意思是映射,是一种极其重要的数据结构。它与Java中的映射有如下不同:不能迭代键名,因为它只保存键的hash,不保存键值。如果要迭代,可以使用开源的可迭代哈希类库。如果一个key的名字没有保存在map中,可以正常读取对应的key值,但是值为空(字节全为0)。所以不需要put、get等操作,用户直接操作即可。合同示例{映射(uint=>string)私有值;functionmappingSample()publicviewreturns(bytes20){//放一个键值对values[10]="hello";//读取值字符串value=values[10];}}array如果数组是状态变量,支持push等操作:contractSample{string[]privatearr;函数arraySample()公共视图{arr.push("Hello");uintlen=arr.length;//应该是1stringvalue=arr[0];//应该是Hello}}数组也可以作为局部变量,但略有不同:functionarraySample()publicviewreturns(uint){//创建一个长度为2的空数组uint[]memoryp=newuint[](2);p[3]=1;//THISWILLTHROWEXCEPTIONreturnp.length;}StructureSolidity允许开发人员自定义结构对象。结构可以存储为状态变量或函数中的局部变量。结构人{uint年龄;字符串名称;}个人私有_person;functionstructExample(){人物记忆p=Person(1,"alice");_person=p;完整列表请参考Solidity官网:https://solidity.readthedocs.io/en/v0.6.3/types.html全局变量示例合约代码的构造函数包括msg.sender。它是一个全局变量。在智能合约中,可以通过全局变量或者全局方法来获取当前区块和交易相关的一些基本信息,比如区块高度、区块时间、合约调用方等。比较常用的全局变量是msg变量,它代表调用上下文。常用的全局变量如下:msg.sender:合约的直接调用者。由于是直接调用者,在“用户A->合约1->合约2”的调用链下,如果在合约2中使用msg.sender,会获取到合约1的地址。如果想获取用户A,可以使用tx.origin。tx.origin:交易的“发起者”,整个调用链的起点。msg.calldata:包含完整的调用信息,包括函数标识符、参数等。calldata的前4个字节为函数标识符,与msg.sig相同。msg.sig:msg.calldata的前4个字节,用于标识函数。block.number:表示当前区块高度。now:表示当前时间戳。也可以用block.timestamp来表示。这里只列出一些常用的全局变量。完整版请参考:https://solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html。结束语本文以一个简单的示例合约作为介绍,介绍使用Solidity开发智能合约的基础知识。读者可以尝试运行合约,体验智能合约的开发。