66b97fbb82b50de00ffda8f98e71178a
017 | 智能合约开发入门指南

现在我们所说的智能合约说白了其实就是区块链上的脚本程序,不同公链上写智能合约所用的编程语言可能不同,以太坊是 Solidity,EOS 主要采用 C++,NEO 则支持多种语言包括 C#、Java、Kotlin、Python等。不过,目前大部分智能合约还是基于以太坊的,所以本文只讲以太坊的智能合约。

语法基础

以太坊的智能合约编程语言为 Solidity,其语法有点类似 JavaScript,虽然存在很多不足,但目前已经成为智能合约编程的通用语言。那要熟悉该语言,自然要从熟悉其语法基础开始。不过,由于语法内容比较多,所以不可能在一篇文章里全部都详细讲解,我只能挑一些重点内容来讲,其他的,还是需要大家去看其他学习材料进行自学。

先介绍几个非常不错的学习资料,第一个自然是官方文档:https://solidity.readthedocs.io/en/latest/index.html。内容比较多,初期可以只看下核心的第三章《Solidity in Depth》。另外,极客学院有个中文翻译版,觉得看英文实在太累的也可以看这个中文版,但有些地方翻译得非常差且有些内容更新不够快,而且排版也不太好,所以我更推荐看英文原版。还有个小专栏《区块链技术》也有做一些翻译,比极客学院的翻译得好,但小专栏不是免费的,就如我的小专栏《进阶全栈工程师之路》一样是知识付费产品。还有一个叫的在线教程,也是个很好的学习课程,该网站的教程还在不断更新中。

数据类型

Solidity的数据类型也和其他语言一样,可分为值类型和引用类型,也有基本的布尔型、整型、浮点型、枚举型、字符串、数组、结构体、映射表等类型,还有特有的类型,如 address。下面就简单列一下这些数据类型:

  • 布尔型bool,值为 truefalse
  • 整型int/uint,有符号和无符号整型,也可以定义 uint8 - uint256,每 8 bit 为一个步长, uint = uint256int 类型也一样。
  • 浮点型fixed/ufixed,有符合和无符号的浮点数,不过目前只能用来声明变量,但不能赋值,所以其实没什么实际应用,该语言的设计人员这么做,我觉得主要也是怕存在精度问题吧。
  • 枚举型enum,和其他语言的枚举一样,但可操作的相对有限。
  • 字符串string,也和其他语言的字符串一样,但可操作的也是非常有限。
  • 数组: 和其他语言一样,用 type[] 来声明数组,另外,字节数组有点特别,可以用 bytes 来定义动态的字节数组,还可以用 bytes1、bytes2、bytes3、……、bytes32 来定义定长的字节数组。
  • 结构体struct,用来定义结构体,和 C/C++ 的 struct 是一样的用法。
  • 映射表mapping,格式为 mapping(_KeyType => _ValueType),与其他语言的映射表不同的是,不需要初始化就可以访问。
  • 地址address,用来定义以太坊账户地址,包括外部账户和合约账户,以 0x 开头,如 address addr = 0x0 表示 0 地址,0 地址是一个特殊的地址,也称为 黑洞地址,只能转入无法转出,将代币回购销毁基本就是将代币转入黑洞地址。

对于数组、结构体、映射表这些引用类型的数据,由于占用的存储空间通常比较大,所以使用时还需要考虑它们存储的位置。有三种位置可存储:memory、storage、calldatamemory 只保存在本地内存,函数参数和返回参数默认都是 memorystorage 则会永久地存储在区块链上,复杂类型的局部变量和合约的状态变量默认则是 storagecalldata 只限定用在外部函数的参数上。根据官方文档描述,从 0.5.0 版本之后,所有存储位置都将默认指定,而不再需要显式声明。

函数

先来看看一个完整的函数是怎么声明的:

function (<parameter types>) {public|private|internal|external} [pure|view|payable][<modifiers>] [returns (<return types>)]

首先,如果该函数没有返回值,则可以省略 returns

访问权限

public|private|internal|external 表示这个函数的访问权限:

  • public:完全开放,可以任意访问
  • private:只能在当前合约内被调用
  • internal:在当前合约和所继承的合约内可调用,和 Javaprotected 一样
  • external:只能外部访问,合约内其他函数需用 this 方式调用

publicexternal 容易搞混,下面举个例子说明一下就清晰了:

pragma solidity^0.4.24;

contract Test {
    function testPublic() public {
    }

    function testExternal() external {
    }

    function callPublic() {
        testPublic();
    }

    function callExternal() {
        this.testExternal();        
    }
}

如上代码所示,如果合约内部需要调用 external 函数,则需用 this 方式调用,而 public 函数则不用。

一般来说,如果确定一个函数只能外部调用,那应该用external;如果需要被外部调用又需在内部调用,就用 public。不同的访问方式会导致不同的燃料费开销,要知道,在以太坊调用函数都是需要消耗燃料费的,这是有经济成本的。

函数类型

接着,再来了解下这几个关键字:

  • pure:pure 函数是最纯粹的函数,它对状态变量既不读取也不修改。
  • view:view 函数是只读性函数,即只能对状态进行读取但不能修改。
  • payable:payable 函数可以接收以太币,未声明 payable 的函数是无法接收以太币转入的。

当然,如果都不用以上关键字标记函数,那就是:既可以读取状态,也可以修改状态,但无法接收以太币。

其实,还有一种特殊的函数,叫 fallback 函数,该函数没有函数名,也没有参数,没有返回值,且必须是外部函数,即声明为 externalpublic 的函数。当从外部调用某个函数,但又找不到与该函数相匹配的函数时,则会调用这个 fallback 函数。一般来说,fallback 函数主要是用来处理接收以太币后的操作的,所以,一般还会再声明为 payable,而读取接收到的以太币则用 msg.value 读取。

modifier

modifier 主要用来封装一些条件检查,如以下代码,就是检查当前的调用者是否该合约的所有者,msg.sender 表示当前调用者,owner 指定了合约所有者,下横线是占位符,代表函数体。

modifier onlyOwner {
    require(msg.sender == owner, "Only owner can call this function.");
    _;
}

当某个函数需要控制只能由合约所有者才能调用时,就可以在函数声明中加上 onlyOwner 这个 modifier 进行修饰,如下所示:

function close() public onlyOwner {
    selfdestruct(owner);
}
top Created with Sketch.