Solidity

solidity语法接近于JavaScript,是一种面向对象的语言。但作为一种真正意义上运行在网络上的去中心化合约,它又有许多不同:
以太坊底层基于账户,而不是UTXO。所以增加了一个特殊的address的数据类型用于定位用户和合约账户。
语言内嵌框架支持支付。提供了payable等关键字,可以在语言层面直接支持支付。
使用区块链进行数据存储。数据的每一个状态都可以永久存储,所以在使用时需要确定变量使用内存,还是区块链存储。
运行环境是在去中心化的网络上,所以需要强调合约或函数执行的调用的方式。
不同的异常机制。一旦出现异常,所有的执行都将会被回撤,这主要是为了保证合约执行的原子性,以避免中间状态出现的数据不一致。

solidity源码和智能合约

Solidity源代码要成为可以运行在以太坊上的智能合约需要经历以下步骤:
1.用Solidity编写的智能合约源代码需要先使用编译器编译为字节码,编译过程中会同时产生智能合约的二进制接口规范
2.通过交易的方式将字节码部署到以太坊网络,每次成功部署都会产生一个新的智能合约账户
3.使用javaScript编写的DApp通常通过web3.js+ABI去调用智能合约中的函数来实现数据的读取和修改。

Solidity编译器
1.Remix
2.solcjs : npm install -g solc

源文件:
pragma (版本杂注)
pragma solidity ^0.4.0 源文件将既不允许低于0.4.0版本的编译器编译,也不允许高于0.5.0版本的编译器编译。

import(导入其它源文件)

Solidity支持的导入语句import,语法同javaScript非常相似
import “filename”; //从filename中导入所有的全局符号到当前的全局作用域中。
import * as symbolName from “filename”; //创建一个新的全局符号symbolName,其成员均来自“filename”中全局符号
import {symbol1 as alias,symbol2} from “filename”; //创建新的全局符号alias和symbol2,分别从“filename”引用symbol1和symbol2
import “filename” as symbolName; //等同于import * as symbolName from “filename”;

solidity值的类型

bool: true or false;
int/uint:分别表示有符号和无符号的不同位数的整型变量;支持关键字uint8到uint256以及int8到int256,以8位为步长递增
定长浮点型(fixed/ufixed):表示各种大小的有符号和无符号的定长浮点型;在关键字ufixedMxN和fixedMxN中,M表示该类型占用的位数,N表示可用的小数位数。
地址(address):存储一个20字节的值
定长字节数组:关键字有bytes1,bytes2,bytes3,…bytes32
枚举(enum):一种用户可以定义类型的方法,与C语言类似,默认从0开始递增,一般用来模拟合约的状态。
函数(function):一种表示函数的类型

solidity引用类型

数组(Array)
定长数组/动态数组
storage存储型数组元素类型可以是任意的,memory内存类型数组元素类型不能是映射(mapping)类型。
结构(struct)
Solidity支持通过构造结构体的形式定义新的类型
映射(Mapping)
映射可以视为哈希表,在实际的初始化过程中创建每个可能的key,并将其映射到字节形式全是0的值。

solidity地址类型

address
地址类型存储一个20字节的值;地址类型也有成员变量,并作为所有合约的基础
address payable
多出了transfer和send两个成员变量
两者区别和转换:
payable地址可以发送ether地址,而普通不行。
允许从payable address 到address的隐式转换,而反过来不行。
从0.5.0版本起,合约不再是从地址类型派生而来,但如果它有payable的回退函数,那同样可以显式转换为address或者address payable类型。

地址类型成员变量

.balance(uint256):该地址的以太余额,以wei为单位
.transfer(uint256 amount):向指定地址发送数量为amount的ether,失败时抛出异常,发送2300gas的矿工费,不可调节。
.send(uint256 amount) returns(bool):向指定地址发送数量为amount的ether,失败时返回false,发送2300gas的矿工费,不可调节。
.call(bytes memory) returns (bool,bytes memory):发出底层函数CALL,失败时返回false,发送所有可用gas,可调节。
.delegatecall(bytes memory) returns (bool,bytes memory):发出底层函数DELEGATECALL,失败时返回false,发送所有可用gas,可调节。

字符数组

定长字符数组,属于值类型,bytes1,bytes2,…bytes32代表长度1到32的字节序列。
有一个.length属性,返回数组长度
变长字符数组
属于引用类型,包括bytes和string,不同的是bytes是Hex字符串,而string是UTF-8编码的字符串。

数组

固定大小k和元素类型T的数组被写为T[k],动态大小的数组为T[]。例如,一个由5个uint动态数组组成的数组是uint[] [5]
要访问第三个动态数组中的第二个uint,可以使用x[2][1]
越界访问数组,会导致调用失败回退
如果要添加新元素,则必须使用.push()或将.length增大
变长的storage数组和bytes有一个push()方法。可以将一个新元素附加到数组末端,返回值为当前长度。

结构

结构类型可以在映射和数组中使用,它们本身可以包含映射和数组。
结构不能包含自己类型的成员,但可以作为自己数组成员的类型,也可以作为自己映射成员的值类型。

映射

声明一个映射:mapping
_KeyType可以是任何基本类型,这意味着它可以是任何内置值类型加上字符数组和字符串。不允许使用用户定义的或复杂的类型,如枚举,映射,结构以及除bytes和string之外的任何数组类型。

solidity数据位置

所有复杂类型,即数组、结构和映射类型,都有一个额外属性,“数据位置”,用来说明数据是保存在内存memory中还是存储在storage中。
大多数时候有默认位置,也可以在类型名后添加关键字storage或者memory进行修改。
函数参数的数据位置默认是memory,局部变量的数据默认位置是storage,状态变量的数据位置强制是storage。
另外还有第3种数据位置,calldata,只读且不会永久存储的位置,用来存储函数参数。外部函数的参数的数据位置被强制指定为calldata,效果跟memory差不多。

强制指定的数据位置:
外部函数的参数:calldata;状态变量:storage
默认数据位置:
函数参数:memory
引用类型的局部变量:storage
值类型局部变量:栈(stack)
特别要求:
公开可见的函数参数一定是memory类型,如果要求是storage类型,则必须是private或者internal函数,这是为了防止随意的公开调用占有资源。

示例错误程序:

运行f,b的值会随之改变。

contract F{
    uint public b;
    uint public a;
    uint[] public data;
    function f()public{
        uint[] x;
        x.push(2);
        data = x;
    }
}

x是没有初始化的storage指针,会指向合约最开始,即b的位置,x中会存储其长度,所以每运行一次f,b会增加1。

同样的错误例子:

蜜罐合约:

contract H{
    uint luckyNumber = 52;
    uint public last;
    struct Guess{
        address player;
        uint number;
    }
    Guess[] public guessHistory;
    function guess(uint _num) public{
        Guess newGuess;
        newGuess.player = msg.sender;
        newGuess.number = _num;
        guessHistory.push(newGuess);
        if(_num == luckyNumber){
            msg.sender.transfer(msg.value*2);
        }
    }
}

Solidity函数声明和类型

函数的值类型有两类:内部(internal)函数和外部(external)函数
内部函数只能在当前合约内被调用,因为它们不能在当前合约上下文的外部被执行。调用一个内部函数是通过跳转到它的入口标签来实现的,就像在当前合约的内部调用一个函数。
外部函数由一个地址和一个函数签名组成,可以通过外部函数调用传递或者返回。
调用内部函数:直接使用名字f
调用外部函数:this.f(当前合约),a.f(外部合约)

Solidity函数可见性

函数的可见性可以指定external,public,internal或者private:对于状态变量,不能设置为external,默认是internal。
external:外部函数作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。一个外部函数f不能从内部调用。当收到大量数据的时候,外部函数有时候会更有效率。
public:public函数是合约接口的一部分,可以在内部或通过消息调用。对于public状态变量,会自动生成一个getter函数。
internal:这些函数和状态变量只能是内部访问,不使用this调用。即不能用.func的方式调用,直接调用即可。
private:private函数和状态变量仅在当前定义它们的合约中使用,并且不能被派生合约使用。

函数状态可变性

pure:纯函数,不允许修改或者访问
view:不允许修改状态
payable:允许从消息调用中接受以太币
constant:与view相同,一般只修饰状态变量,不允许赋值。
修改状态:
修改状态变量,产生事件,创建其他合约,使用selfdestruct,通过调用发送以太币,调用任何没有标记为view或者pure的函数,使用低级调用,使用包含特定操作码的内联汇编。
读取状态:
读取状态变量,访问this.balance或者

.balance,访问block,tx,msg中任意成员,调用任何未标记为pure的函数,使用包含某些操作码的内联汇编。

函数修饰器

使用修饰器modifier可以轻松改变函数的行为。例如:它们可以在函数执行前自动检查某个条件。修饰器modifier是合约的可继承属性,并可能被派生合约覆盖。
如果一个函数有多个修饰器modifier,可以以空格隔开,修饰器modifier会依次检查执行。
_表示函数体的位置。

回退函数(fallback)

回退函数(fallback) 是合约中的特殊函数;没有名字,不能有参数也不能有返回值。
如果在一个合约的调用中,没有其它函数与给定的函数标识符匹配,那么这个函数会被执行
每当合约收到以太币,回退函数就会执行。此外,为了接收以太币,fallback函数必须标记为payable。如果不存在这样的函数,则合约不能通过常规交易接收以太币。
上下文中通常只有很少的gas可以用来完成回退函数的调用,所以使fallback函数调用尽量廉价。

事件(event)

事件是EVM提供的一种日志基础设施。事件可以用来做操作记录,存储为日志。也可以用来实现一些交互功能,比如通知UI,返回函数调用结果等。
当定义的事件触发时,我们可以将事件存储到EVM的交易日志中,日志是区块链的一种特殊数据结构;日志与合约关联,与合约的存储合并存入区块链中;只要某个区块可以访问,其相关的日志就可以访问,但在合约中,我们不能直接访问日志和事件数据。
可以通过日志实现简单支付验证SPV,如果一个外部实体提供了一个带有这种证明的合约,它可以检查日志是否真实存在于区块链中。

Solidity异常处理

Solidity使用“状态恢复异常”来处理异常。这样的异常将撤销对当前调用中状态的所有更改,并且向调用者返回错误。
函数assert和require可用于判断条件,并在不满足条件时抛出异常
assert()一般只应用于测试内部错误,并检查常量
require()应用于确保满足有效条件,或验证调用外部合约的返回值。
revert()用于抛出异常,它可以标记一个错误并将当前调用回退。

单位

1 ether = 10^18 wei
1 finney = 10^15 wei
1 szabo = 10^12 wei
时间:
hours,seconds,weeks,days,years,minutes

上一篇:Solidity进阶之路:搭建僵尸工厂 - 第8章: 使用结构体和数组


下一篇:Solidity和智能合约