- 通读solidity官方文档,参考资料
- Solidity是一种面向合同编写的高级语言,用于实现智能合同。它受到
c++
、Python
和JavaScript
的影响,被设计为在Ethereum虚拟机(EVM)上运行。 - Solidity是静态类型的,支持继承、库和复杂的用户定义类型。
- 正如您将看到的,可以为投票、众筹、盲目拍卖、数字钱包等创建智能合约。
一、合约的结构
合约在Solidity中,和面向对象语言中的Class类似。每个合约中可以包含状态变量,函数,函数修饰符,事件,结构类型以及枚举类型。此外合约可以继承另外一个合约。
- 状态变量
State variable
- 函数
Function
- 函数修饰符
Modifier
- 事件
Event
- 结构类型
Struct Types
- 枚举类型
enum Types
状态变量 State variable
状态变量是永久存储在合约中的值。
pragma solidity ^0.4.0;
contract SimpleStorage {
uint storedData; // State variable
// ...
}
函数 Function
函数是合约中可执行的代码单元。
pragma solidity ^0.4.0;
contract SimpleAuction {
function bid() public payable { // 函数 Function
// ...
}
}
函数修饰符 Modifier
函数修饰符可用于以声明的方式修改函数的语义(请参阅合约部分中的函数修饰符)。
pragma solidity ^0.4.22;
contract Purchase {
address public seller;
modifier onlySeller() { // 修饰符 Modifier
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}
function abort() public onlySeller { // 使用修饰符
// ...
}
}
事件 Event
事件是在EVM日志功能中方便使用的接口。
pragma solidity ^0.4.21;
contract SimpleAuction {
event HighestBidIncreased(address bidder, uint amount); // Event 事件
function bid() public payable {
// ...
emit HighestBidIncreased(msg.sender, msg.value); // Triggering event 触发事件
}
}
结构类型 Struct Types
struct 中可以自定义组合多个变量类型(请参阅类型部分的Struts)
pragma solidity ^0.4.0;
contract Ballot {
struct Voter { // 结构体 Struct
uint weight;
bool voted;
address delegate;
uint vote;
}
}
枚举类型 Enum Types
枚举可以使用有限的“常量值”集创建自定义类型(请参阅类型部分的Enum)。
pragma solidity ^0.4.0;
contract Purchase {
enum State { Created, Locked, Inactive } // Enum
}
二、类型 Types
值类型
- 布尔(
Booleans
) - 整型(
Integer
) - 地址(
Address
) - 定长字节数组(
fixed byte arrays
) - 有理数和整型(
Rational and Integer Literals,String literals
) - 枚举类型(
Enums
) - 函数(
Function Types
)
引用类型
- 数据位置(
Data location
)
每个复杂类型,例如数组和结构,都有一个附加的注解,即“数据位置”,关于它是存储在memory
还是存储在storage
。根据上下文的不同,总是存在一个默认值,但是可以通过类型判断。函数参数(包括返回参数)的默认值是存储在memory
,本地变量的默认值是存储在storage
。
还有第三个数据位置calldata,它是一个不可修改的、非持久性的区域,函数参数在其中存储。外部函数的函数参数(而不是返回参数)被强制使用calldata
,用法更像 memory
。
pragma solidity ^0.4.0;
contract C {
uint[] x; // x 的数据位置是指向storage
// memoryArray 的数据位置是内存,
function f(uint[] memoryArray) public {
x = memoryArray; // 把数组中所有数据放在storage中
var y = x; // 分配一个指针,y的数据位置是storage
y[7]; // 返回第8个元素
y.length = 2; // 通过y的指向,修改x的值
delete x; // 清空x的数组,同时也修改了y
// 接下来这些代码不会生效,它需要创建一个副本 /
// 重命名storage中的数组,但是storage是静态分配的。
// y = memoryArray;
// 这也不起作用,因为它将“重置”指针,但是它不能指向任何合理的位置。
// delete y;
g(x); // 调用g,传递一个对x的引用值
h(x); // 调用h并在内存中创建一个独立的临时副本
}
function g(uint[] storage storageArray) internal {}
function h(uint[] memoryArray) public {}
}
- 总结:
强制指定数据存放位置:
-> 外部函数的参数(不是返回值):calldata
-> 状态变量:storage
默认数据存放的位置:
-> 函数参数(含返回值):memory
-> 所有其他本地的变量:storage
- 数组
Arrays
数组在编译的时候可以是固定大小,也可以是动态的。对于storage
中的数组,元素类型可以是任何类型(例如:其他arrays
、mapping
或structs
)。对于memory
数组,它不能是mapping
。并且如果是一个公开可见函数的参数,必须是ABI类型。
举例:定义长度为5的uint
动态数组是uint[][5]
(注意,与其他一些语言相比,符号是颠倒的),要访问第三个动态数组中的第二个uint
,可以使用x[2][1]
(索引是基于零的,访问工作与声明的方式相反,即x[2]
从右边的类型中删除一个级别)。
字节和字符串类型的变量是特殊的数组。字节类似于byte[]
,但它在calldata
中被紧密地打包。字符串等于bytes
,但不允许长度或索引访问(目前为止)。
注意:
所以bytes
比byte[]
更可取,因为它开销更小。
由于bytes
与string
,可以自由转换,你可以将字符串s
通过bytes(s)
转为一个bytes
。但需要注意的是通过这种方式访问到的是UTF-8
编码的码流,并不是独立的一个个字符。比如中文编码是多字节,变长的,所以你访问到的很有可能只是其中的一个代码点。
- 结构体
Structs
Solidity 提供一种新的方式定义结构体,如下面代码:
pragma solidity ^0.4.11;
contract CrowdFunding {
// 定义一种新的类型有两个属性字段
struct Funder {
address addr;
uint amount;
}
struct Campaign {
address beneficiary;
uint fundingGoal;
uint numFunders;
uint amount;
mapping (uint => Funder) funders;
}
uint numCampaigns;
mapping (uint => Campaign) campaigns;
function newCampaign(address beneficiary, uint goal) public returns (uint campaignID) {
campaignID = numCampaigns++; // campaignID 返回一个变量
// 创建新的结构体并保存在 storage 中。这里我们不用赋值 mapping 类型
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}
function contribute(uint campaignID) public payable {
Campaign storage c = campaigns[campaignID];
// 创建一个新的临时memory结构,初始化给定值。
// 并将其复制到 storage 中。
// 注意,您还可以使用Funder(msg.sender, msg.value) 来初始化。
c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
c.amount += msg.value;
}
function checkGoalReached(uint campaignID) public returns (bool reached) {
Campaign storage c = campaigns[campaignID];
if (c.amount < c.fundingGoal)
return false;
uint amount = c.amount;
c.amount = 0;
c.beneficiary.transfer(amount);
return true;
}
}
上面的代码向我们展示的一个简化版的众筹项目,其实包含了一些 struct
的使用。
结构体可以包含 mapping
和 arrays
,但是不能包含 自己类型的结构体,因为结构体的大小是有限的。
需要注意的是在函数中,将一个 struct
赋值给一个局部变量(默认是 storage
类型),实际是拷贝的引用,所以修改局部变量值时,会影响到原变量。
当然,你也可以直接通过访问成员修改值,而不用一定赋值给一个局部变量,如 campaigns[comapingnId].amount = 0
映射 Mappings
Mapping
类型被声明为Mapping(_KeyType => _ValueType)
。这里的_KeyType
几乎可以是任何类型,除了Mapping
、动态大小的数组 Arrays
、合约 contract
、枚举 enum
和结构 struct
。_ValueType
实际上可以是任何类型,包括Mapping
。
Mapping
可以被视为hash表,它实际上是初始化的,以便每个可能的键都存在,并映射到一个值,该值的字节表示都是0:类型的默认值。但是,相似之处就在这里结束了:key
数据实际上并没有存储在Mapping
中,只有它的keccak256
hash
值用于查找值。
因此,Mapping
没有长度或键或值“set”的概念。
映射只允许状态变量(或作为内部函数中的存储引用类型)。
可以将Mapping
标记为public
,并合约创建一个getter
。_KeyType
将成为getter
的必需参数,它将返回_ValueType
。
_ValueType
也可以是Mapping
。getter将递归地为每个_KeyType
提供一个参数。
pragma solidity ^0.4.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() public returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(this);
}
}
注意:Mapping
是不可迭代的,但是可以在其之上实现数据结构。例如,请参见可迭代映射。
左值的相关运算符 Operators Involving LValues
左值
,是指位于表达式左边的变量,可以是与操作符直接结合的形成的,如自增,自减;也可以是赋值,位运算。
可以支持操作符有:-=
,+=
,*=
,%=
,|=
,&=
,^=
,++
,--
。
特殊的运算符delete
delete
运算符,用于将某个变量重置为初始值。对于整数,运算符的效果等同于a = 0
。而对于定长数组,则是把数组中的每个元素置为初始值,变长数组则是将长度置为0。对于结构体,也是类似,是将所有的成员均重置为初始值。
delete
对于mapping
几乎无影响,因为键可能是任意的,且往往不可知。所以如果你删除一个结构体,它会递归删除所有非mapping
的成员。当然,你是可以单独删除 mapping
里的某个键,以及这个 mapping
的某个值。
需要强调的是delete a的行为更像赋值,为a赋予一个新对象。我们来看看下文的示例:
pragma solidity ^0.4.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() public {
uint x = data;
delete x; // 把x设置为0,但是不影响x的结构
delete data; // sets data to 0, does not affect x which still holds a copy
uint[] storage y = dataArray;
delete dataArray; //删除data,同样也不会影响x,因为是值传递,它存的是一份原值的拷贝。
// y is affected which is an alias to the storage object
// On the other hand: "delete y" is not valid, as assignments to local variables
// referencing storage objects can only be made from existing storage objects.
}
}
基本类型间的转换
- 隐式转换
如果一个操作符被应用到不同的类型,编译器会试图隐式地将一个操作数转换为另一个操作数(同样适用于赋值)。一般来说,如果值类型之间的隐式转换在语义上有意义并且没有信息丢失,那么它是可能的:uint8
可以转换为uint16
,而int128
可以转换为int256
,但是int8
不能转换为uint256
(因为uint256
不能保存例如-1
)。此外,无符号整数可以转换为大小相同或更大的字节,但反之亦然。可以转换为uint160
的任何类型也可以转换为address
。 - 显式转换
如果编译器不允许隐式转换,但是您知道自己在做什么,那么显式类型转换有时是可能的。请注意,这可能会给您一些意料之外的行为,所以一定要测试以确保结果是您想要的!在下面的例子中,你将一个负的int8
转换成一个uint
:
int8 y = -3;
uint x = uint(y);
在这个代码段的末尾,x将有值0xfffff..fd(64个十六进制字符),是-3在两个二进制的256位的补码表示法中。
如果一个类型被显式地转换为较小的类型,高阶位将被切断:
uint32 a = 0x12345678;
uint16 b = uint16(a); // b will be 0x5678 now
-类型推断(Type Deduction
)
为了方便,并不总是需要明确指定一个变量的类型,编译器会通过第一个向这个对象赋予的值的类型来进行推断
uint24 x = 0x123;
var y = x;
函数的参数,包括返回参数,不可以使用var这种不指定类型的方式。
需要特别注意的是,由于类型推断是根据第一个变量进行的赋值。
所以代码for (var i = 0; i < 2000; i++) {}
将是一个无限循环,因为一个uint8
的i的将小于2000
。
pragma solidity ^0.4.4;
contract Test{
function a() returns (uint){
uint count = 0;
for (var i = 0; i < 2000; i++) {
count++;
if(count >= 2100){
break;
}
}
return count;
}
}
单位和全局可用的变量Units and Globally Available Variables
- 以太坊单位 Ether Units
一个数字可以在以太坊上以wei、finney、szabo或 ether 后缀的单位进行转换,其中假定没有后缀的以太货币数为 wei ,例如,2 ether = 2000 finney的值为true。 - 时间单位 Time Units
在以秒为基本单位的时间单位和以秒为基本单位的单位之间,可以用诸如秒、分、小时、日、周、年等后缀来表示时间单位之间的转换。
1 = = 1秒
1分钟= 60秒
1小时= 60分钟
1天= 24小时
1周= 7天
1年= 365天
如果你用这些单位进行日历计算,要小心,因为不是每一年都等于365天,甚至不是每一天都因为闰秒而有24小时。由于无法预测闰秒,因此必须由外部oracle更新确切的日历库。
特殊的变量和函数 Special Variables and Functions
全局命名空间中总是存在一些特殊的变量和函数,它们主要用于提供关于区块链的信息,或者是通用的实用工具函数。
- 块和交易属性
Block and Transaction Properties
block.blockhash(uint blockNumber) returns (bytes32)
: hash of the given block - only works for 256 most recent, excluding current, blocks - deprecated in version 0.4.22 and replaced by blockhash(uint blockNumber).
block.coinbase (address)
: current block miner’s address
block.difficulty (uint)
: current block difficulty
block.gaslimit (uint)
: current block gaslimit
block.number (uint)
: current block number
block.timestamp (uint)
: current block timestamp as seconds since unix epoch
gasleft() returns (uint256)
: remaining gas
msg.data (bytes)
: complete calldata
msg.gas (uint)
: remaining gas - deprecated in version 0.4.21 and to be replaced by gasleft()
msg.sender (address)
: sender of the message (current call)
msg.sig (bytes4)
: first four bytes of the calldata (i.e. function identifier)
msg.value (uint)
: number of wei sent with the message
now (uint)
: current block timestamp (alias for block.timestamp)
tx.gasprice (uint)
: gas price of the transaction
tx.origin (address)
: sender of the transaction (full call chain)
请注意
msg 的所成员变量,包括msg.sender和msg.value可以随着每次外部函数调用而改变。这包括对库函数的调用。
请注意
不要依赖 block.timestamp,now 和 blockhash 作为机性的来源,除非你知道自己在做什么。
时间戳和块散列在某种程度上都可以受到挖掘程序的影响。例如,采矿社区中的不良行为者可以在选定的散列上运行赌场支付函数,如果他们没有收到任何钱,就重新尝试不同的散列。
当前块时间戳必须严格大于上一个块的时间戳,但唯一的保证是它将介于标准链中两个连续块的时间戳之间。
请注意
由于可伸缩性的原因,块散列不能用于所有块。您只能访问最近的256块的散列,所有其他值都为零。
ABI 转码的函数 ABI Encoding Functions
-
abi.encode(...) returns (bytes)
: ABI-encodes the given arguments -
abi.encodePacked(...) returns (bytes)
: Performes packed encoding of the given arguments -
abi.encodeWithSelector(bytes4 selector, ...) returns (bytes)
: ABI-encodes the given arguments
starting from the second and prepends the given four-byte selector -
abi.encodeWithSignature(string signature, ...) returns (bytes)
: Equivalent to abi.encodeWithSelector(bytes4(keccak256(signature), ...)
异常处理 Error Handling
assert(bool condition):
如果条件不满足,则使交易无效——用于内部错误。
require(bool condition):
如果条件不满足,则返回—用于输入或外部组件中的错误。
require(bool condition, string message):
如果条件不满足,则返回—用于输入或外部组件中的错误。还提供一个错误消息。
revert():
中止执行并恢复状态更改
revert(string reason):
中止执行并恢复状态更改,提供一个解释字符串
数学和加密功能 Mathematical and Cryptographic Functions
以下函数可以根据方法和返回值的字面意思理解,因为这里翻译很怪 ,或者参考资料
addmod(uint x, uint y, uint k) returns (uint):
mulmod(uint x, uint y, uint k) returns (uint):
keccak256(...) returns (bytes32):
sha256(...) returns (bytes32):
sha3(...) returns (bytes32):
ripemd160(...) returns (bytes20):
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):
keccak256("ab", "c")
keccak256("abc")
keccak256(0x616263)
keccak256(6382179)
keccak256(97, 98, 99)
账户相关 Address Related
.balance (uint256):
返回Wei单位的余额
.transfer(uint256 amount)
: 给该Address发送 Wei单位的amount,默认消耗2300gas ,不可调整
.send(uint256 amount) returns (bool)
:
给该Address发送 Wei单位的amount, 失败的时候返回false,默认消耗2300gas ,不可调整
.call(...) returns (bool)
: 失败的时候返回false,转发所有gas,可调整
.delegatecall(...) returns (bool):失败的时候返回false,转发所有gas,可调整
合同相关 Contract Related
this
(current contract’s type):
指当前合同
selfdestruct(address recipient)
:
销毁当前合同,将其资金发送到指定地址
suicide(address recipient)
:
已弃用的别名 ,请使用selfdestruct