0x03 智能合约之Solidity基础知识
一个简单的例子
pragma solidity ^0.4.21;
contract SimpleAuction {
// Parameters of the auction. Times are either
// absolute unix timestamps (seconds since 1970-01-01)
// or time periods in seconds.
address public beneficiary;
uint public auctionEnd;
// Current state of the auction.
address public highestBidder;
uint public highestBid;
// Allowed withdrawals of previous bids
mapping(address => uint) pendingReturns;
// Set to true at the end, disallows any change
bool ended;
// Events that will be fired on changes.
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
// The following is a so-called natspec comment,
// recognizable by the three slashes.
// It will be shown when the user is asked to
// confirm a transaction.
/// Create a simple auction with `_biddingTime`
/// seconds bidding time on behalf of the
/// beneficiary address `_beneficiary`.
function SimpleAuction(
uint _biddingTime,
address _beneficiary
) public {
beneficiary = _beneficiary;
auctionEnd = now + _biddingTime;
}
/// Bid on the auction with the value sent
/// together with this transaction.
/// The value will only be refunded if the
/// auction is not won.
function bid() public payable {
// No arguments are necessary, all
// information is already part of
// the transaction. The keyword payable
// is required for the function to
// be able to receive Ether.
// Revert the call if the bidding
// period is over.
require(now <= auctionEnd);
// If the bid is not higher, send the
// money back.
require(msg.value > highestBid);
if (highestBid != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their money themselves.
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
/// Withdraw a bid that was overbid.
function withdraw() public returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `send` returns.
pendingReturns[msg.sender] = 0;
if (!msg.sender.send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd() public {
// It is a good guideline to structure functions that interact
// with other contracts (i.e. they call functions or send Ether)
// into three phases:
// 1. checking conditions
// 2. performing actions (potentially changing conditions)
// 3. interacting with other contracts
// If these phases are mixed up, the other contract could call
// back into the current contract and modify the state or cause
// effects (ether payout) to be performed multiple times.
// If functions called internally include interaction with external
// contracts, they also have to be considered interaction with
// external contracts.
// 1. Conditions
require(now >= auctionEnd); // auction did not yet end
require(!ended); // this function has already been called
// 2. Effects
ended = true;
emit AuctionEnded(highestBidder, highestBid);
// 3. Interaction
beneficiary.transfer(highestBid);
}
}
文件布局
声明引用的版本号
pragma solidity ^0.4.0;
引入其他文件
import "filename";
import * as symbolName from "filename";
import {symbol1 as alias, symbol2} from "filename";
import "filename" as symbolName;
引入路径
所有路径名都被视为绝对路径,默认从主目录下引入。要从当前文件的同一目录中导入文件x,使用import “./x”作为x
在编译中还可以使用映射源文件
import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;
solc github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ source.sol
solc module1:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ \
module2:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin_old/ \
source.sol
Remix中的映射
允许直接使用网络地址
import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;.
注释
// This is a single-line comment.
/*
This is a
multi-line comment.
*/
参考格式:Doxygen
智能合约结构
Solidity合同与面向对象语言中的类相似。每个合约都可以包含状态变量,函数, 函数修饰符,事件,结构类型和枚举类型的声明。此外,合同可以继承其他合同。
类型
由于Solidity是一个静态类型的语言,所以编译时需明确指定变量的类型(包括本地或状态变量)Solidity编程语言提供了一些元类型(elementary types)可以组合成复杂类型。变量也支持在表达式运算,后面有一些关于运算符执行的先后顺序说明。
类型本身包括后面讲到的值类型(Value Types),引用类型(Refrence Type),一些复杂的内置数据结构等。
值类型(Value Type)
值类型又包含:
-
布尔(Booleans)
bool : 返回值为true
orfalse
-
整型(Integer)
int / uint : 有符号和无符号整型 -
地址(Address)
address:20个字节的值
地址成员:- balance 和 transfer
- send
- call,callcode和delegatecall
-
字节数组(byte arrays)
byte 为bytes1的alias,引用连接
成员:- .length
动态大小的字节数组:
- bytes : 动态大小的字节数组
- string:动态大小的UTF-8编码字符串
-
有理数和整型(Rational and Integer Literals,String literals)
取值范围 0-9 -
字符串文字
字符串文字用双引号或单引号("foo"或'bar')编写,隐式转换 -
十六进制字面量(Hexadecimal Literals)
十六进制文字以前缀为关键字hex,并用双引号或单引号(hex"001122FF")
括起来。它们的内容必须是十六进制字符串,它们的值将是这些值的二进制表示。
- 枚举类型(Enums)
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
- 函数(Function Types)
function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]
- 存储类型
- memory:内存
- storage:存储
- calldata:存储函数参数的不可修改的非持久性区域
- 数组(array)
成员:- .length
uint[] memory a = new uint[](7);
bytes memory b = new bytes(len);
- 结构(Structs)
// Defines a new type with two fields.
struct Funder {
address addr;
uint amount;
}
- 映射(Mapping)
mapping(_KeyType => _ValueType)_KeyType_ValueType
删除
delete a
基本类型之间的转换
隐式转换
uint8可以转换为 uint16 and int128 to int256
但是 int8 不能转换为 uint256 ,因为uint256不能为-1
显式转换
int8 y = - 3 ;
uint x = uint (y );
自动会默认一个类型
uint24 x = 0x123 ;
var y = x ;
单位和全局可用变量
Ether 单位:
wei,finney,szabo或ether
时间单位
seconds,minutes,hours,days,weeks和 years
特殊变量和函数
阻止和事务属性
- block.blockhash(uint blockNumber) returns (bytes32):给定块的hash - 仅适用于256个最新块,不包括当前块
- block.coinbase(address):当前块矿工的地址
- block.difficulty(uint):当前难度
- block.gaslimit(uint):当前块gaslimit
- block.number(uint):当前块数
- block.timestamp(uint):当前块时间戳,因为unix时期以来的秒数
- gasleft() returns (uint256):剩余gas
- msg.data(bytes):完成calldata
- msg.sender(address):消息的发送者(当前发送地址)
- msg.sig(bytes4):calldata的前四个字节(即函数标识符)
- msg.value(uint):与消息一起发送的wei的数量
- now(uint):当前块时间戳(别名为block.timestamp)
- tx.gasprice(uint):交易的gas价格
- tx.origin(address):交易的发送者
错误处理(Error Handling)
- assert(bool condition):
如果条件不满足则抛出 - 用于内部错误。 - require(bool condition):
如果条件未满足则抛出 - 用于输入或外部组件中的错误。 - revert():
中止执行并恢复状态更改
数学和加密函数
-
addmod(uint x, uint y, uint k) returns (uint)
计算添加以任意精度执行的位置 -
mulmod(uint x, uint y, uint k) returns (uint):
计算以任意精度执行乘法的位置 -
keccak256(...) returns (bytes32)
计算(紧密排列的)参数的Ethereum-SHA-3(Keccak-256)散列 -
sha256(...) returns (bytes32)
计算(紧密排列)参数的SHA-256哈希值 -
sha3(...) returns (bytes32)
别名 keccak256 -
ripemd160(...) returns (bytes20)
计算(紧密排列)参数的 RIPEMD-160哈希值 -
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):
从椭圆曲线签名中恢复与公钥相关的地址,或在错误时返回零(示例用法)
地址相关
-
<address>.balance (uint256)
地址的余额,单位wei。 -
<address>.transfer(uint256 amount)
发起转账。failure:throws ,损失2300gas。 -
<address>.send(uint256 amount) returns (bool)
发起转账。failure:return false ,损失2300gas。 -
<address>.call(...) returns (bool)
发起低级别转账。failure:return false ,损失全部gas,可以调整 -
<address>.callcode(...) returns (bool)
发起低级别转账。failure:return false ,损失全部gas,可以调整 -
<address>.delegatecall(...) returns (bool)
发起低级别转账。failure:return false ,损失全部gas,可以调整
合同相关
- this 当前合同
- selfdestruct(address recipient)
销毁当前合同,将资金发送到指定地址 - suicide(address recipient)
selfdestruct 别名
表达式和控制结构
输入参数和输出参数
输入参数
pragma solidity ^0.4.16;
contract Simple {
function taker(uint _a, uint _b) public pure {
// do something with _a and _b.
}
}
输出参数
return v
返回多个值
return (v0, v1, ..., vn)
控制结构
- switch
- goto.
- if, else
- while, do, for, break, continue,
- return,
- ? :
函数调用
this.g(8);
c.g(2);
内部函数调用
pragma solidity ^0.4.16;
contract C {
function g(uint a) public pure returns (uint ret) { return f(); }
function f() internal pure returns (uint ret) { return g(7) + f(); }
}
外部函数调用
pragma solidity ^0.4.0;
contract InfoFeed {
function info() public payable returns (uint ret) { return 42; }
}
contract Consumer {
InfoFeed feed;
function setFeed(address addr) public { feed = InfoFeed(addr); }
function callFeed() public { feed.info.value(10).gas(800)(); }
}
命名的呼叫和匿名功能参数
pragma solidity ^0.4.0;
contract InfoFeed {
function info() public payable returns (uint ret) { return 42; }
}
contract Consumer {
InfoFeed feed;
function setFeed(address addr) public { feed = InfoFeed(addr); }
function callFeed() public { feed.info.value(10).gas(800)(); }
}
省略函数参数名称
pragma solidity ^0.4.16;
contract C {
// omitted name for parameter
function func(uint k, uint) public pure returns(uint) {
return k;
}
}
通过 new 创建合同
pragma solidity ^0.4.0;
contract D {
uint x;
function D(uint a) public payable {
x = a;
}
}
contract C {
D d = new D(4); // will be executed as part of C's constructor
function createD(uint arg) public {
D newD = new D(arg);
}
function createAndEndowD(uint arg, uint amount) public payable {
// Send ether along with the creation
D newD = (new D).value(amount)(arg);
}
}
合同
函数修饰符
- external
外部函数,可以从其他合同和交易中调用它们。不能在内部调用,也就是说f()
不起作用,但是可以使用this.f()
来调用。在接收大量数据的时候更高效。 - public
公共函数。默认值。
可以在内部或者通过消息调用。 - internal
内部函数。只能在内部合约进行使用,不能使用this。 - private
私有函数。只能在当前合约内使用。
所有外部观察者都可以看到合约内的所有内容。private 只会阻止其他合约访问和修改信息,但在区块链之外,整个世界仍然可以看到代码。
Getter函数
编译器会自动为所有的public 函数或者变量生成 Getter函数。
添加验证( Modifier )
使用方式:
/**
* @dev Allows owner to remove an employee.
* @param employeeId The id of the employee.
*/
function removeEmployee(address employeeId)
public
onlyOwner
employee_exist(employeeId)
{
_settlePayment(employeeId);
_totalSalary = _totalSalary.sub(employees[employeeId].salary);
delete employees[employeeId];
}
译自官方文档
恒定状态变量
状态变量可以声明为constant。
函数
声明 View
函数可以声明view,在这种情况下,它们保证不修改状态。
声明pure
函数可以声明为pure,在这种情况下,它们保证不读取或修改状态。
以下内容被认为是从状态中读取的:
- 从状态变量读取。
- 访问this.balance或<address>.balance。
- 访问任何成员block,tx,msg(与除外msg.sig和msg.data)。
- 调用任何未标记的功能pure。
- 使用包含某些操作码的内联汇编。
Fallback 函数
合同可以有一个未命名的功能。这个函数不能有参数,也不能返回任何东西。如果没有其他函数与给定的函数标识符匹配(或者根本没有提供数据),它将在对合同的调用中执行。
pragma solidity ^0.4.0;
contract Test {
// This function is called for all messages sent to
// this contract (there is no other function).
// Sending Ether to this contract will cause an exception,
// because the fallback function does not have the `payable`
// modifier.
function() public { x = 1; }
uint x;
}
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
function() public payable { }
}
contract Caller {
function callTest(Test test) public {
test.call(0xabcdef01); // hash does not exist
// results in test.x becoming == 1.
// The following will not compile, but even
// if someone sends ether to that contract,
// the transaction will fail and reject the
// Ether.
//test.send(2 ether);
}
}
函数重载
允许有相同名称但是不同参数的方法共存
pragma solidity ^0.4.16;
contract A {
function f(uint _in) public pure returns (uint out) {
out = 1;
}
function f(uint _in, bytes32 _key) public pure returns (uint out) {
out = 2;
}
}
Events
事件方便外部进行监听。
pragma solidity ^0.4.0;
contract ClientReceipt {
event Deposit(
address indexed _from,
bytes32 indexed _id,
uint _value
);
function deposit(bytes32 _id) public payable {
// Events are emitted using `emit`, followed by
// the name of the event and the arguments
// (if any) in parentheses. Any such invocation
// (even deeply nested) can be detected from
// the JavaScript API by filtering for `Deposit`.
emit Deposit(msg.sender, _id, msg.value);
}
}
在web3中调用事件
var abi = /* abi as generated by the compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */);
var event = clientReceipt.Deposit();
// watch for changes
event.watch(function(error, result){
// result will contain various information
// including the argumets given to the `Deposit`
// call.
if (!error)
console.log(result);
});
// Or pass a callback to start watching immediately
var event = clientReceipt.Deposit(function(error, result) {
if (!error)
console.log(result);
});
日志记录
log0, log1, log2, log3, log4...
pragma solidity ^0.4.10;
contract C {
function f() public payable {
bytes32 _id = 0x420042;
log3(
bytes32(msg.value),
bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20),
bytes32(msg.sender),
_id
);
}
}
继承(Inheritance )
详细的路径可以参考专门写继承的一个C3 Linearization 线性化python。
如果想使用父类方法,需要声明 super
。
构造函数
同名函数为构造函数,也就是在调用的时候默认会执行的函数。可以声明为public,internal。
抽象合约
仅仅声明,没有实体
pragma solidity ^0.4.0;
contract Feline {
function utterance() public returns (bytes32);
}
接口
接口与抽象类似,但是还有更多的限制。
- 无法继承其他合同或接口。
- 无法定义构造函数。
- 无法定义变量。
- 无法定义结构。
- 无法定义枚举。
pragma solidity ^0.4.11;
interface Token {
function transfer(address recipient, uint amount) public;
}
库
部署在特定地址的公用函数
Using For
我理解类似apply或者中间件的意思,所有的参数使用先经过这个use 函数的处理。
contract C {
using Set for Set.Data; // this is the crucial change
Set.Data knownValues;
function register(uint value) public {
// Here, all variables of type Set.Data have
// corresponding member functions.
// The following function call is identical to
// `Set.insert(knownValues, value)`
require(knownValues.insert(value));
}
}
优先级
Precedence | Description | Operator |
---|---|---|
1 | Postfix increment and decrement |
++ , --
|
New expression | new <typename> |
|
Array subscripting | <array>[<index>] |
|
Member access | <object>.<member> |
|
Function-like call | <func>(<args...>) |
|
Parentheses | (<statement>) |
|
2 | Prefix increment and decrement |
++ , --
|
Unary plus and minus |
+ , -
|
|
Unary operations | delete |
|
Logical NOT | ! |
|
Bitwise NOT | ~ |
|
3 | Exponentiation | ** |
4 | Multiplication, division and modulo |
* , / , %
|
5 | Addition and subtraction |
+ , -
|
6 | Bitwise shift operators |
<< , >>
|
7 | Bitwise AND | & |
8 | Bitwise XOR | ^ |
9 | Bitwise OR | | |
10 | Inequality operators |
< , > , <= , >=
|
11 | Equality operators |
== , !=
|
12 | Logical AND | && |
13 | Logical OR | || |
14 | Ternary operator | <conditional> ? <if-true> : <if-false> |
15 | Assignment operators |
= , |= , ^= , &= , <<= , >>= , += , -= , *= , /= , %=
|
16 | Comma operator | , |