区块链安全第 3 课——智能合约编程:Hello Solidity
@snowming
0x01 本课导言
在本文中,将从使用 Solidity 输出 Hello World 开始,逐步深入到对链上数据的读写操作,最终系统地学习一遍 Solidity 的基本语法。
你会发现在 Solidity 中输出 Hello World 并无需像 C 或者 Python 一样调用 printf/print,编译器已经帮我们实现了对于状态变量的 getter() 方法……你还会见到一些熟悉的老朋友比如 enum、struct、循环……语法基本类似于 C 但是比 C 更简单,也会有一些新朋友比如 function modifier、event、function selector……话不多说,快来一起领略 Solidity 的魅力!
0x02 Hello World
每一种语言的学习都是从 hello world 开始的。来看看如何用 Solidity 写 Hello World 吧!
pragma solidity ^0.4.9;
contract HelloWorld{
string public greet = "Hello World!";
}
编译、部署&&运行之后,使用 Remix 获取这个「状态变量」。状态变量是永久地存储在合约存储中的值。定义方式是:<类型> <属性> <变量名> = <值>。
状态变量有两个特征:
- 在函数外声明
- 存储在区块链上,也就是会上链

如上,点击 greet 按钮的提示语是:CALL[call]from: 0x9746391f7B9DD17cf887D434F1aE44fb9Bd6D3e4to: HelloWorld.greet()data: 0xcfa...e3217Debug
这意味着刚刚我们调用了 HelloWorld.greet(),且返回值是 string: Hello World!
为什么明明是访问了状态变量,但是显示的是调用了同名方法呢?
这是因为 public 关键字的作用。
关键字 public
自动生成一个函数,允许在这个合约之外访问这个状态变量的当前值。如果没有这个关键字,其他的合约没有办法访问这个变量。所以这句代码:string public greet = "Hello World!";
实际由编译器生成的函数的代码大致如下所示:function greet() returns (string) { return greet; }
当然,加一个和上面完全一样的函数是行不通的,因为我们会有同名的一个函数和一个变量。这里,只是要解释为什么明明是变量但是是通过函数访问的 —— 因为编译器已经帮你实现了 get() 方法。
0x03 get() 和 set() 函数的上链问题
来看这个简单的合约:
pragma solidity ^0.4.19;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
代码非常简单,相信通过前两课的铺垫,可以很容易的看出:
- set 方法是修改 storedData 的值的
- get 方法是获取 storedData 的值的
部署此合约之后来尝试调用这两个方法:


我在操作的时候,先填写了 788 的参数,然后调用了 set 方法;然后在交易 pending 的期间调用了 get 方法。结果返回的值是 0。
但其实链上的交易顺序是基于 nonce 的。set 是第 n 币交易,则 get 是第 n+1 笔交易。理论上在区块链上只有第 n 笔交易完成后,才会执行第 n+1 笔交易。但是很显然在这里,get 函数被直接先于 set 函数调用了。
而且查看合约交易历史,只能看到 set 方法的调用,而看不到 get 方法的调用:

但是当 set 方法执行完毕后,再次执行 get 方法,就能得到被改变的 storedData 值,这说明 get 方法也是执行成功了的。但是区块链上却看不到交易历史。

这是为什么呢?
通俗一点来说:读不上链写上链。也就是对于合约数据的读取不会作为区块链中的交易,而写或者修改这些操作才会作为区块链中的交易。
因为声明了 storedData 变量之后默认初始化值为 0,所以 get 调用返回 0。而且不需要参与链上交易的排队。
另外可以说一下 get() 方法中的这个 view 关键字:function get() public view returns (uint)
可以将函数声明为 view
类型,这种情况下要保证不修改状态。
下面的语句被认为是修改状态:
- 修改状态变量。
- 产生事件。
- 创建其它合约。
- 使用
selfdestruct
(合约自毁)。 - 通过调用发送以太币。
- 调用任何没有标记为
view
或者pure
的函数。 - 使用低级调用。
- 使用包含特定操作码的内联汇编。
注:constant
是 view
的别名。
0x04 What’s more
现在你已经零零散散的了解了一些关于智能合约编程的基本知识了,但是可能在上面的内容中有一些模糊不清的困惑。所以我仍认为你应该系统学习一下 Solidity 编程的基本语法。
这篇博客之所以跟第二篇之间隔了好几天的时间,是因为我把这部分语法刷完了才发出来。简单的说就是要阅读代码、理解概念,个人比较喜欢这种 learn by example 的形式。
下面是我刷过的知识点的列表:
- Hello World
- First App
- Primitive Data Types
- Variables
- Reading and Writing to a State Variable
- Ether and Wei
- Gas and Gas Price
- If / Else
- For and While Loop
- Mapping
- Array
- Enum
- Structs
- Data Locations – Storage, Memory and Calldata
- Function
- View and Pure Functions
- Error
- Function Modifier
- Events
- Constructor
- Inheritance
- Shadowing Inherited State Variables
- Calling Parent Contracts
- Visibility
- Interface
- Payable
- Sending Ether – Transfer, Send, and Call
- Fallback
- Call
- Delegatecall
- Function Selector
- Calling Other Contract
- Creating Contracts from a Contract
- Try / Catch
- Import
- Library
- Hashing with Keccak256
- Verifying Signature
希望你也刷一遍,然后再和我一起进行下一步的学习,有任何问题可以在评论中留言。
下次课我们将换换口味,暂时从大段代码中松口气。我将介绍以太坊客户端,进行以太坊客户端的下载、安装、区块链同步以及通过 JSON-RPC 接口向客户端发起请求、获取数据。
写的很赞,学习啦