智能合约编写之Solidity的基础特性
The following article is from FISCO BCOS开源社区 Author 储雨知
来源 | FISCO BCOS
作者 | 储雨知
目前大部分的联盟链平台,包括FISCO BCOS,都采用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);
}
Solidity编程风格应采用一定的规范。关于编程风格,建议参考 https://learnblockchain.cn/docs/solidity/style-guide.html#id16
修饰符
//Modifer
modifier onlyAdmin(){
require(msg.sender == _admin, "You are not admin");
_;
}
...
//Functions
function setState(uint value) public onlyAdmin{
...
}
智能合约的运行
方法一:可以使用FISCO BCOS控制台的方式来部署合约,具体请参考 https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/installation.html#id7
方法二:使用FISCO BCOS开源项目WeBASE提供的在线ide WEBASE-front运行
方法三:通过在线ide remix来进行合约的部署与运行, remix的地址为 http://remix.ethereum.org/
编译
部署
setState
getState
Solidity数据类型
整型系列
定长bytes系列
function bytesSample() public{
bytes32 barray;
//Initialize baarray
//read brray[0]
byte b = barray[0];
}
uint256 s = 1;
bytes32 b = bytes32(s);
function bytesSample() public pure returns(byte, byte){
uint256 value = 1;
bytes32 b = bytes32(value);
//Should be (0, 1)
return (b[0], b[31]);
}
变长bytes
string
function stringSample() public view returns(bytes){
string memory str = "abc";
bytes memory b = bytes(str);
//0x616263
return b;
}
address
function addressSample() public view returns(bytes20){
address me = msg.sender;
bytes20 b = bytes20(me);
return b;
}
它无法迭代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];
}
}
数组
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[] memory p = new uint[](2);
p[3] = 1;//THIS WILL THROW EXCEPTION
return p.length;
}
struct
struct Person{
uint age;
string name;
}
Person private _person;
function structExample() {
Person memory p = Person(1, "alice");
_person = p;
}
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表示。
结语
推荐阅读
闪电网络的 5 个优点和4 个缺点、本质、来源与工作原理……一文带你读懂闪电网络! 开发项目时如何选择区块链平台?我们分析了以太坊、Bitcoin via RSK、Ardor三个有趣的平台来给你回答! 小米回应"暴力裁员";报告称安卓手机贬值速度是 iPhone 两倍;Ant Design 4.0.1 发布| 极客头条 时间复杂度的表示、分析、计算方法……一文带你看懂时间复杂度! Javascript函数之深入浅出递归思想,附案例与代码! 华为、阿里员工在听的英语资源,即将过期,请自取