基本类型
int
- 用intx和uintx来申明,其中x是一个8-256之间的8的倍数,表示有多少个bit。如int8 ,uint32。
- 比较:<=,<,==,!=,>=,>
- 位运算:&,|,^,~,<<,>>
- 数值运算:加减乘除,%取余,指数。0的0次方等于1
- type(x).min和type(x).max给出该类型的上下界
溢出会被截断
address
- address与address payable:储存160个bit长的信息,也就是一个地址。
- address payable 相对于address多了可以向此地址转账的功能
- .balance给出该地址的金额
- .transfer()和.send()对于一个address payable代表向其转账
address payable x = address(0x123);
address myAddress = address(this);
if(myAddress.balance >= 10){
x.transfer(10);// or x.send(10)//合约向x转账
}
Bool
- 用bool x来申明
- 操作符 !,&&,||,==,!=
- &&操作和||操作是短路运算
数组
- 定长数组:bytes1,bytes3, … bytes32表示储存了1个,2个…32个byte的数组
- bytes32 x:可以用下标访问
- 具有位运算
- 常规数组和C++类似:int a[10]
- 也可以申明动长数组:int a[]。动长数组有a.length返回长度, a.push(x) 在最后添加一个新元素x并返回新长度
Char 和 String
- 用char来申明字符变量
- string相当于一个char的数组,但是目前的版本不支持使用.length以及下标- 访问,需要转化为bytes访问字节形式的内容
string s;
bytes(s).length
bytes(s)[7] = 'x'
Mapping
- 用mapping(_keytype => _valuetype),基本上就是个哈希表
mapping(address => uint) public balance;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
申明一个balance从地址映射的uint的值
uint没有具体申明,默认为uint256
申明一个函数(后面详细介绍)
在整体上,智能合约不是一个完成的程序,需要被其他人调用自己才能实现真正的动作
msg.sender是调用者地址(后面会详细介绍)
balance[address] = uint256(具体问题,具体分析)
Struct
结构体
struct Voter{//投票者
uint weight;//权重
bool voted;//是否投过票
address delegate;//地址
uint vote;//票数
}
预定义变量
block
区块的相关信息
block.blockhash(uint blockNumber) retruns(bytes32) 指定区块的区块哈希,不推荐,建议使用blockhash(uint blockNumber)代替
block.coinbase(address):挖出当前区块的矿工地址
block.difficulty(uint):当前区块难度
block.gaslimit(uint):当前区块gas限额
block.number(uint):当前区块号
block.timestamp(uint):自uinx epoch其当前区块以秒计的时间戳
msg
msg.data(bytes):完整的calldata
msg.gas(uint):剩余gas,不推荐,建议使用gasleft()代替
msg.sender(address):当前调用的消息发送者
msg.sig(bytes4):calldata的前4个字节(也就是函数标识符)
msg.value(uint):随消息发送的wei的数量
Event
用来触发EVM中的log记录,使用如下语句声明和使用
event X(<parameter types>) //定义
emit X(<parameter>) //使用
event Send(address sender, address receive, uint amount); //事件
function sent() public payable{
//....
emit Send(msg.sender, msg.receive,msg.value);//触发事件
}
控制结构
if,else,while,do,for,break,continue,return,? :
在soldity中,其他类型是不会自动转换为bool的,所以if(1){}这样是不合法的
Units
EtherUnits
wei,szabo,finney,ether
1 ether = 1000 finney = 106 szabo = 1018 wei
TimeUnits
一个数值后跟seconds,minutes,hours,days,weeks是一个时间单位,默认秒(1 == 1seconds)
Contracts
合约Contract是一个代码的基本组成部分,和class类似
一个Contract里会包含状态变量,函数,函数修饰器和事件
Contract也有继承与多态
状态变量
储存在合约中的信息,如总量,个人的余额等等
contract C1{
uint xxx; //State Variable
}
函数
function (<parameter types>) [internal | external | public] [pure | view | payable] [returns (<return types>)]
{}
view:一个函数如果不会改变状态(包括send ether)则可以被申明为view
pure:一个函数如果是view,并且不会从状态中读取数据,则可以被申明为pure
如果一个函数什么都不return就相当于c++中的void 函数 不写returns即可
reutrn可以返回一个元组,用括号括起来
internal | external | public是不可选,必须写
pure | view | payable是可选的,可以不写
returns是可选的,可以不写
Payable
表示调用的时候可以发送币
msg.sender是发币地址,msg.value是数量
function deposit() payable returns(address addr,uint amount,bool success){
return(msg.sender,msg.value,this.send(msg.value));
}
没有申明可见性,默认是public
返回发送者地址,发了多少代币,是否发送成功
this代指本合约地址
这里是指sender向此合约打款
visibility
函数有四种可见性:external,public,internal,private
external表示只能从外部调用,如果想从内部调用,必须用this.fn
public 则表示内外都可以
internal 可以被自己或衍生类调用
private则只能自己调用
变量的visibility 不能是 external
internal ,private只是针对于外部合约而言,在区块链外部还是能查看相关数据的
visibility不写,默认为public
构造函数
一个合约有至多一个构造函数,在构造时会执行一次
Contract A{
bytes32 name;
constructor(bytes32 _name)public{
name = _name;
}
}
Receive Ether
一个合约有至多一个Receive ether函数,用如下语句声明。
receive() external payable{…}
没有function关键字
当收到ether时执行(.send()和.transfer())
//This contract keeps all Ether sent to it with no way
//to get it back
contract Sink{
event Received(address, uint);
receive() external payable{
emit Received(msg.sender, msg.value);
}
}
contract Source{
function sending(uint amount) internal payable {
// 向Sink合约发送币
address payable x = address(Sink);
x.transfer(10); // will call receive
}
}
fallback
- 一个合约至多有一个fallback函数,fallback函数是缺省函数,当调用了一个该合约不存在的函数时会执行fallback。
- 用如下语句声明,同样没有function关键词
fallback() external [payable]
要求某种条件必须成立
如果不满足,就全部不执行,所有操作将会回退(撤销)
函数修饰器function modifier
contract owned{
address owner;
mapping(address => bool) registeredAddresses;
uint price;
constructor()public {
owner = msg.sender;
}
modifier onlyOwner{
require(msg.sender == owner);
_;//如果满足上述条件,函数将会在这里接着执行
}
modifier costs(uint price) {//modifier可以接受参数
if(msg.value >= price){
_;//满足条件,函数将会在这里接着执行
}
}
function register() public payable costs(price){
registeredAddressed[msg.sender] =true;
}
function changePrice(uint _price) public onlyOwner{
price = _price;
}
}
继承
- 用is来实现合约继承
- 继承的子合约会继承父合约所有非private的东西,包括modifier
Contract A{…}
Contract B is A {…}
- 如果要override一个函数,在被覆盖的函数声明时必须加virtual关键字,在覆盖的函数后加override
- 如果希望能被在此override,也要加virtual关键字
- 函数默认调用最衍生的类定义的函数
- 如果要调用父类的函数,使用super.f()
- 函数override的时候必须保持同样的可见性,或从external变成public
- 另一个参数只能变得更加严格,即non-payable(也就是无参数)可以被override为view,view可以变成pure。payable是特殊的,必须保持为payable
contract Base{
function foo() virtual external view{}
}
contract Middle is Base{}
contract Inherited is Middle{
function foo() virtual override public pure{}
}
external -> public
view -> pure
符合override后的函数修饰
Abstract Contracts
Abstract contracts是未实现的Contract。可以用来做父类
abstract contract A{
function num() public pure virtual returns(uint);
}
contract B is A{
function num() public pure override returns(uint){
return 0;
}
}
Interface
和Abstract Contract 差不多,但是更严格一点
不能实现任何函数
不能定义构造函数
不能定义任何变量
不能继承(可以被继承)
-
(1)合约中的方法默认为public
-
(2)合约中的状态变量默认为internal
-
(3)this是指当前的合约地址,this.() 只能去访问当前合约的public类型的
-
(4)状态变量为public的状态变量,在合约内部会自动生成一个public同名的方法
-
(5)如果在合约中,重写public的状态变量同名的方法,将会覆盖掉合约自动生成的同名方法
-
(6)在继承中,子合约只能继承父合约中的所有public类型的方法
-
(7)在继承中,子合约只能继承父合约中的所有public类型和internal类型的状态变量
-
(8)支持多继承,继承的权限同上
-
(9)业务需求,可重写从父类继承过来的方法
-
(10)constant与view功能相同,尽量就用view
-
(11)function 合约名()可以表示构造函数,但是一般不建议使用,就用constructor()表示构造函数
-
(12)在部署合约时,会运行带有view / constant 的方法
-
(1)智能合约中,不支持直接返回string[],其他类型的数组,可以直接返回
-
(2)智能合约中,不支持字符串直接拼接,如果想要实现拼接功能,可以转为bytes在去实现相关操作
-
(3)方法修饰符,如果加了view将不会消耗gas,并且调用此方法,不需要用户私钥,可直接调用
-
(4)拷贝合约:合约 新合约名 = 合约(地址)
-
(5)address(0)代表空地址
-
(6)library库合约,特定地址部署一次,就可以被其他合约反复调用
-
(7)如果用继承父合约的相同状态变量或方法,会消耗大量gas,如果用library库合约就可以解决这个问题
-
(8)library库合约没有状态变量,一般使用是创建一个结构体,结构体中mapping去管理相关状态和判断条件
-
(9)abi为空的合约,不能被部署(WeBASE-Front上测试的)
abstract用法
-
(1)abstract 定义抽象合约,供其他合约继承使用
-
(1)抽象合约将合约的定义与其实现脱钩,从而提供了更好的可扩展性和自文档性
-
(2)简化了诸如Template方法的模式,并消除了代码重复。
-
(1)如果未实现合约中的至少一项功能,则需要将合同标记为抽象。
-
(2)即使实现了所有功能,合同也可以被标记为抽象的。
-
(3)这样的抽象合同不能直接实例化。
-
(4)如果抽象合约本身实现了所有定义的功能,也不能直接实例化
-
(5)如果合约继承自抽象合约,并且没有通过覆盖实现所有未实现的功能,则也需要将其标记为抽象。
-
(6)抽象合同不能覆盖未实现的虚函数
冻结和交易属性
- (1)blockhash(uint blockNumber)返回(bytes32):给定块的哈希-仅适用于256个最新块,不包括当前块
- (2)block.chainid(uint):当前链号
- (3)block.coinbase(应付地址):当前区块矿工的地址
- (4)block.difficulty(uint):当前区块难度
- (5)block.gaslimit(uint):当前块的gaslimit
- (6)block.number(uint):当前块号
- (7)block.timestamp(uint):当前块时间戳,以自unix纪元以来的秒数为单位
- (8)gasleft()返回(uint256):剩余gas
- (9)msg.data(字节calldata):完整的calldata
- (10)msg.sender(地址):消息的发件人(当前通话)
- (11)msg.sig(bytes4):调用数据的前四个字节(即函数标识符)
- (12)msg.value(uint):与消息一起发送的wei数
- (13)tx.gasprice(uint):交易的gas价格
- (14)tx.origin(地址):交易的发送者(完整的调用链)
event用法
-
(1)事件是以太坊日志记录/事件监视协议的抽象。
-
(2)日志条目提供了合同的地址,一系列多达四个主题以及一些任意长度的二进制数据。
-
(3)事件利用现有功能ABI来将其(连同接口规范)解释为适当类型的结构。
-
(4)给定一个事件名称和一系列事件参数,我们将它们分为两个子系列:被索引的子序列和未被索引的子序列。
-
(5)已索引的索引(最多可为3个)与事件签名的Keccak哈希一起使用,以形成日志条目的主题。
-
(6)未索引的事件形成事件的字节数组。
-
(1)对于所有最大长度为32个字节的类型,EVENT_INDEXED_ARGS数组包含的值直接,填充或符号扩展(对于有符号整数)为32个字节,就像常规ABI编码一样。
-
(2)对于所有“复杂”类型或动态长度类型,包括所有数组,字符串,字节和结构,EVENT_INDEXED_ARGS将包含特殊就地编码值的Keccak哈希值(请参见索引事件参数的编码),而不是直接编码的值。
-
(3)这允许应用程序有效地查询动态长度类型的值(通过将编码值的哈希值设置为主题),但使应用程序无法解码尚未查询的索引值。
-
(4)对于动态长度类型,应用程序开发人员面临快速搜索预定值(如果参数已索引)和易读性任意值(这要求不对参数进行索引)之间权衡的问题。
-
(5)开发人员可以通过定义两个具有相同值的参数来定义事件,从而克服这种折衷并实现有效的搜索和任意的可读性。
modifier用法
-
使用 修饰器modifier 可以轻松改变函数的行为。
-
例如,它们可以在执行函数之前自动检查某个条件。
-
修饰器modifier 是合约的可继承属性, 并可能被派生合约覆盖。
-
如果同一个函数有多个 修饰器modifier,它们之间以空格隔开,修饰器modifier 会依次检查执行。
-
修饰器modifier 或函数体中显式的 return 语句仅仅跳出当前的 修饰器modifier 和函数体。
-
结尾必须加_;
-
返回变量会被赋值,但整个执行逻辑会从前一个 修饰器modifier 中的定义的 “_” 之后继续执行。
-
修饰器modifier 的参数可以是任意表达式,在此上下文中,所有在函数中可见的符号,在 修饰器modifier 中均可见
-
可继承,覆盖,但是不可以重载
存储区域
memory
内存memory,用于暂存数据。其中存储的内容会在函数被调用(包括外部函数)时擦除,所以其使用开销相对较小。
storage
存储storage,贮存了合约声明中所有的变量。 虚拟机会为每份合约分别划出一片独立的 存储storage 区域,并在函数相互调用时持久存在,所以其使用开销非常大。
stack
栈stack,用于存放小型的局部变量。使用几乎是免费的,但容量有限。
不同数据类型的变量会有各自默认的存储地点:
- 状态变量总是会贮存在 存储storage 中
- 函数参数默认存放在 内存memory 中
- 结构、数组或映射类型的局部变量,默认会放在 存储storage 中
- 除结构、数组及映射类型之外的局部变量,会储存在栈中
** 一个常见误区就是声明了一个局部变量,就认为它会创建在 内存memory 中,其实它会被创建在 存储storage 中: **
/// 这份合约包含一处错误
pragma solidity ^0.4.0;
contract C {
uint someVariable;
uint[] data;
function f() public {
uint[] x;
x.push(2);
data = x;
}
}
局部变量 x 的数据类型是 uint[] storage,但由于 存储storage 不是动态分配的,它需要在使用前通过状态变量赋值。所以 x 本身不会被分配 存储storage 的空间,取而代之的是,它只是作为 存储storage 中已有变量的别名。
实际上会发生的是,编译器将 x 解析为一个 存储storage 指针,并默认将指针指向 存储插槽storage slot 0 。这就造成 someVariable (贮存在 存储插槽storage slot 0)会被 x.push(2) 更改。
注:在本例中,两个合约变量 someVariable 和 data 会被预先分配到两个 存储插槽storage slot 中,即 存储插槽storage slot 0 和 存储插槽storage slot 1 。上面的程序会使局部变量 x 变成指向保存了变量 someVariable 的 存储插槽storage slot 0 的指针。
/// 正确的做法
pragma solidity ^0.4.0;
contract C {
uint someVariable;
uint[] data;
function f() public {
uint[] x = data;
x.push(2);
}
}
pure用法
- 函数声明关键字 ,pure,不读取或修改状态。
- 编译器没有强制 pure 方法不能读取状态。
- 读取状态变量。
- 访问this.balance 或者 .balance
- 访问block.tx msg中任意成员(除了msg.sig和msg.data之外)
- 调用任何未标记为 pure 的函数。
- 使用包含某些操作码的内联汇编。
library用法
- 库与合约类似,只需要在特定的地址部署一次
- 库函数被调用,它的代码在调用合约的上下文中执行,即 this 指向调用合约,特别是可以访问调用合约的存储
- 每个库都是一段独立的代码,所以它仅能访问调用合约明确提供的状态变量(否则它就无法通过名字访问这些变量)
- 我们假定库是无状态的,所以如果它们不修改状态(view 或者 pure 函数), 库函数可以通过直接调用来使用
- 库可以看作是使用他们的合约的隐式的基类合约
- 虽然在继承关系中不会显式可见,但调用库函数与调用显式的基类合约十分类似 (如果 L 是库的话,可以使用 L.f() 调用库函数)
- 像库是基类合约一样,对所有使用库的合约,库的 internal 函数都是可见的
- 需要使用内部调用约定来调用内部函数,这意味着所有内部类型,内存类型都是通过引用而不是复制来传递
与合约相比
- 没有状态变量
- 不能够继承或被继承
- 不能接收以太币
using for用法
// 可用于在合约的上下文中,将库函数(来自库A)附加到任何类型(B)
using A for B
这些函数将接收被调用的对象作为它们的第一个参数
using A for *——库A的功能可以附加到任何类型。
附加库中的所有函数,即使那些第一个参数的类型与对象的类型不匹配的函数也是如此。
在调用函数时检查类型,并执行函数重载解析。
指令仅在当前合约内(包括其所有功能内)有效,并且在使用该合约的合约外无效。
struct用法
自定义结构体类型
struct Voter {
uint weight;
bool voted;
address delegate;
uint vote;
}
- 在合约外部声明结构体可以使其被多个合约共享
- 在合约内部定义结构体,这使得它们仅在此合约和衍生合约(继承 or 引用)中可见。
- 在函数中使用结构体时,一个结构体赋值给另一个局部变量,需是存储storage 类型的局部变量;在这个过程中并没有拷贝这个结构体,而是保存一个引用,所以对局部变量成员的赋值实际上会被写入状态。