写点什么

智能合约编写之 Solidity 的基础特性

  • 2020-03-13
  • 本文字数:4780 字

    阅读完需:约 16 分钟

智能合约编写之Solidity的基础特性

目前大部分的联盟链平台,包括 FISCO BCOS,都采用 Solidity 作为智能合约开发语言,因此熟悉并上手 Solidity 十分必要。


作为一门面向区块链平台设计的图灵完备的编程语言,Solidity 支持函数调用、修饰符、重载、事件、继承等多种特性,在区块链社区中,拥有广泛的影响力和踊跃的社区支持。但对于刚接触区块链的人而言,Solidity 是一门陌生的语言。


智能合约编写阶段将从 Solidity 基础特性、高级特性、设计模式以及编程攻略分别展开,带读者认识 Solidity 并掌握其运用,更好地进行智能合约开发。


本篇将围绕 Solidity 的基础特性,带大家上手开发一个最基本的智能合约。

智能合约代码结构

任何编程语言都有其规范的代码结构,用于表达在一个代码文件中如何组织和编写代码,Solidity 也一样。


本节,我们将通过一个简单的合约示例,来了解智能合约的代码结构。


pragma solidity ^0.4.25;contract Sample{
//State variables address private _admin; uint private _state;
//Modifier modifier onlyAdmin(){ require(msg.sender == _admin, "You are not admin"); _; }
//Events event SetState(uint value);
//Constructor constructor() public{ _admin = msg.sender; }
//Functions function setState(uint value) public onlyAdmin{ _state = value; emit SetState(value); }
function getValue() public view returns (uint){ return _state; }
}
复制代码


上面这段程序包括了以下功能:


  • 通过构造函数来部署合约

  • 通过 setValue 函数设置合约状态

  • 通过 getValue 函数查询合约状态


整个合约主要分为以下几个构成部分:


  • 状态变量 - _admin, _state,这些变量会被永久保存,也可以被函数修改

  • 构造函数 - 用于部署并初始化合约

  • 事件 - SetState, 功能类似日志,记录了一个事件的发生

  • 修饰符 - onlyAdmin, 用于给函数加一层"外衣"

  • 函数 - setState, getState,用于读写状态变量


下面将逐一介绍上述构成部分。

状态变量

状态变量是合约的骨髓,它记录了合约的业务信息。用户可以通过函数来修改这些状态变量,这些修改也会被包含到交易中;交易经过区块链网络确认后,修改即为生效。


uint private _state;
复制代码


状态变量的声明方式为:[类型] [访问修饰符-可选] [字段名]

构造函数

构造函数用于初始化合约,它允许用户传入一些基本的数据,写入到状态变量中。


在上述例子中,设置了_admin 字段,作为后面演示其他功能的前提。


constructor() public{    _admin = msg.sender;} 
复制代码


和 java 不同的是,构造函数不支持重载,只能指定一个构造函数。

函数

函数被用来读写状态变量。对变量的修改将会被包含在交易中,经区块链网络确认后才生效。生效后,修改会被永久的保存在区块链账本中。


函数签名定义了函数名、输入输出参数、访问修饰符、自定义修饰符。


function setState(uint value) public onlyAdmin;
复制代码


函数还可以返回多个返回值:


function functionSample() public view returns(uint, uint){    return (1,2);}
复制代码


在本合约中,还有一个配备了 view 修饰符的函数。这个 view 表示了该函数不会修改任何状态变量。


与 view 类似的还有修饰符 pure,其表明该函数是纯函数,连状态变量都不用读,函数的运行仅仅依赖于参数。


function add(uint a, uint b) public pure returns(uint){    return a+b;}
复制代码


如果在 view 函数中尝试修改状态变量,或者在 pure 函数中访问状态变量,编译器均会报错。

事件

事件类似于日志,会被记录到区块链中,客户端可以通过 web3 订阅这些事件。


定义事件


event SetState(uint value);
复制代码


构造事件


emit SetState(value);
复制代码


这里有几点需要注意:


  • 事件的名称可以任意指定,不一定要和函数名挂钩,但推荐两者挂钩,以便清晰地表达发生的事情.

  • 构造事件时,也可不写 emit,但因为事件和函数无论是名称还是参数都高度相关,这样操作很容易笔误将事件写成函数调用,因此不推荐。


function setState(uint value) public onlyAdmin{    _state = value;    //emit SetState(value);    //这样写也可以,但不推荐,因为很容易笔误写成setState       SetState(value); }
复制代码


修饰符

修饰符是合约中非常重要的一环。它挂在函数声明上,为函数提供一些额外的功能,例如检查、清理等工作。


在本例中,修饰符 onlyAdmin 要求函数调用前,需要先检测函数的调用者是否为函数部署时设定的那个管理员(即合约的部署人)。


//Modifermodifier onlyAdmin(){    require(msg.sender == _admin, "You are not admin");          _;}
...//Functionsfunction setState(uint value) public onlyAdmin{ ...}
复制代码


值得注意的是,定义在修饰符中的下划线“_”,表示函数的调用,指代的是开发者用修饰符修饰的函数。在本例中,表达的是 setState 函数调用的意思。

智能合约的运行

了解了上述的智能合约示例的结构,就可以直接上手运行,运行合约的方式有多种,大家可以任意采取其中一种:



本例中使用 remix 作为运行示例。

编译

首先,在 remix 的文件 ide 中键入代码后,通过编译按钮来编译。成功后会在按钮上出现一个绿色对勾:


部署

编译成功后就可进行部署环节,部署成功后会出现合约实例。


setState

合约部署后,我们来调用 setState(4)。在执行成功后,会产生一条交易收据,里面包含了交易的执行信息。



在这里,用户可以看到交易执行状态(status)、交易执行人(from)、交易输入输出(decoded input, decoded output)、交易开销(execution cost)以及交易日志(logs)。


在 logs 中,我们看到 SetState 事件被抛出,里面的参数也记录了事件传入的值 4。


如果我们换一个账户来执行,那么调用会失败,因为 onlyAdmin 修饰符会阻止用户调用。


getState

调用 getState 后,可以直接看到所得到的值为 4,正好是我们先前 setState 所传入的值:


Solidity 数据类型

在前文的示例中,我们用到了 uint 等数据类型。由于 Solidity 类型设计比较特殊,这里也会简单介绍一下 Solidity 的数据类型。

整型系列

Solidity 提供了一组数据类型来表示整数, 包含无符号整数与有符号整数。每类整数还可根据长度细分,具体细分类型如下。


定长 bytes 系列

Solidity 提供了 bytes1 到 bytes32 的类型,它们是固定长度的字节数组。


用户可以读取定长 bytes 的内容。


    function bytesSample() public{
bytes32 barray; //Initialize baarray //read brray[0] byte b = barray[0]; }
复制代码


并且,可以将整数类型转换为 bytes。


uint256 s = 1;        bytes32 b = bytes32(s);
复制代码


这里有一个关键细节,Solidity 采取大端序编码,高地址存的是整数的小端。例如,b[0]是低地址端,它存整数的高端,所以值为 0;取 b[31]才是 1。


function bytesSample() public pure returns(byte, byte){
uint256 value = 1; bytes32 b = bytes32(value); //Should be (0, 1) return (b[0], b[31]); }
复制代码

变长 bytes

从上文中,读者可了解定长 byte 数组。此外,Solidity 还提供了一个变长 byte 数组:bytes。使用方式类似数组,后文会有介绍。

string

Solidity 提供的 string,本质是一串经 UTF-8 编码的字节数组,它兼容于变长 bytes 类型。


目前 Solidity 对 string 的支持不佳,也没有字符的概念。用户可以将 string 转成 bytes。


function stringSample() public view returns(bytes){        string memory str = "abc";        bytes memory b = bytes(str);        //0x616263        return b;    }
复制代码


要注意的是,当将 string 转换成 bytes 时,数据内容本身不会被拷贝,如上文中,str 和 b 变量指向的都是同一个字符串 abc。

address

address 表示账户地址,它由私钥间接生成,是一个 20 字节的数据。同样,它也可以被转换为 bytes20。


function addressSample() public view returns(bytes20){
address me = msg.sender; bytes20 b = bytes20(me); return b; }
复制代码

mapping

mapping 表示映射, 是极其重要的数据结构。它与 java 中的映射存在如下几点差别:


  • 它无法迭代 keys,因为它只保存键的哈希,而不保存键值,如果想迭代,可以用开源的可迭代哈希类库

  • 如果一个 key 未被保存在 mapping 中,一样可以正常读取到对应 value,只是 value 是空值(字节全为 0)。所以它也不需要 put、get 等操作,用户直接去操作它即可。


contract Sample{
mapping(uint=>string) private values;
function mappingSample() public view returns(bytes20){ //put a key value pair values[10] = "hello";
//read value string value = values[10];
}
}
复制代码

数组

如果数组是状态变量,那么支持 push 等操作:


contract Sample{
string[] private arr;
function arraySample() public view { arr.push("Hello"); uint len = arr.length;//should be 1 string value = arr[0];//should be Hello }
}
复制代码


数组也可以以局部变量的方式使用,但稍有不同:


function arraySample() public view returns(uint){        //create an empty array of length 2        uint[](2);        p[3] = 1;//THIS WILL THROW EXCEPTION         return p.length;}
复制代码

struct

Solidity 允许开发者自定义结构对象。结构体既可以作为状态变量存储,也可以在函数中作为局部变量存在。


struct Person{        uint age;        string name;    }
Person private _person;
function structExample() { Person memory 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 开发智能合约的基本知识。读者可以尝试运行该合约,感受智能合约的开发。


若想更深入学习智能合约示例,推荐官方网站示例供读者学习,也可关注本专题后续系列文章:


https://solidity.readthedocs.io/en/v0.6.2/solidity-by-example.html


在官网的示例中,提供了投票、竞拍、微支付通道等多个案例,这些案例贴近实际生活,是很好的学习资料。

关于作者

储雨知,FISCO BCOS 核心开发者。


2020-03-13 11:002336

评论 1 条评论

发布
用户头像
写的不错,大佬开课吗
2021-04-13 23:57
回复
没有更多了
发现更多内容

蚁剑的分析和利用

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 安全漏洞

🍃【Spring专题】「实战系列」重新回顾一下异常重试框架Spring Retry的功能指南

洛神灬殇

spring 11月日更 Spring retry Guava retry

共建全栈国产化解决方案 | OceanBase持续与合作伙伴推进测试互认

OceanBase 数据库

数据库 分布式 双十一 oceanbase

阿里大规模业务混部下的全链路资源隔离技术演进

阿里巴巴云原生

阿里云 云原生 资源隔离 ACK 混部

家庭太阳能发电,何时能告别“两极分化”?

脑极体

FFmpeg[5] - 将视频文件转码成MP4格式(FFmpeg转封装2)

liuzhen007

11月日更

太顶了!华为高工用一份423页的网络协议笔记把计算机网络讲清了

热爱java的分享家

Java 架构 面试 编程语言 网络协议

MySQL事务的实现原理之Undo Log的分析

卢卡多多

Undo Log 11月日更

如何巧妙使用Camtasia制作PPT讲解视频?

淋雨

Camtasia

到底什么才是真正的区块链游戏

CECBC

🎅Less快速学习(下)🎅

空城机

CSS 大前端 less 11月日更

【Three.js】随着元宇宙开启WEB3D之路

devpoint

JavaScript WebGL 3D three.js 11月日更

第三代分布式数据库来了,真香!

OceanBase 数据库

数据库 开源 分布式 oceanbase

双11“剁手”后,你的包裹收到了吗?

OceanBase 数据库

数据库 开源 分布式 oceanbase

度小满启动“小微加油站”,让低息服务可持续

脑极体

【死磕Java并发】-----Java内存模型之总结

chenssy

11月日更 死磕 Java 死磕 Java 并发

推动数字化转型,OceanBase 助力保险企业创造新价值

OceanBase 数据库

数据库 开源 技术宅 oceanbase 分布式,

给面试官上一课:HTTPS是先进行TCP三次握手,再进行TLS四次握手

热爱java的分享家

Java 架构 程序人生 编程语言 经验分享

.NET6新东西--Lambda优化

喵叔

11月日更

Go 语言学习查缺补漏ing Day1

Regan Yue

Go 语言 11月日更

“敏捷版”全链路压测

阿里巴巴云原生

阿里云 云原生 全链路压测 PTS

金链银链不如区块链 如何利用区块链技术探索“太空世界”?

CECBC

clickhouse sql之Array函数

WindFlying

千万级学生管理系统的考试试卷存储方案设计

Beyond Ryan

我理解的CPI和PPI

石云升

学习笔记 11月日更

黑客常备20个工具,你知道几个?

喀拉峻

深入浅出,一文吃透mysql索引

微客鸟窝

MySQL 11月日更

区块链技术如何在涉诉信访中显身手

CECBC

运维体系建设思考-稳定性篇

ning

运维 云原生 监控 稳定性

惨遭GitHub直接封杀的阿里P8手敲出来这份565页凤凰架构分布式手册,有何神奇之处

热爱java的分享家

Java 面试 程序人生 编程语言 凤凰架构

两个排序数组的中位数,“最”有技术含量的解法!

老表

Python 算法 LeetCode 11月日更

智能合约编写之Solidity的基础特性_区块链_储雨知_InfoQ精选文章