Zihao

Make small but daily progress

0%

0x03--智能合约之Solidity基础知识

一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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);
}
}

文件布局

声明引用的版本号

1
pragma solidity ^0.4.0;

引入其他文件

1
2
3
4
5
import "filename";
import * as symbolName from "filename";
import {symbol1 as alias, symbol2} from "filename";
import "filename" as symbolName;

引入路径

所有路径名都被视为绝对路径,默认从主目录下引入。要从当前文件的同一目录中导入文件x,使用import “./x”作为x
在编译中还可以使用映射源文件

1
2
3
4
5
6
7
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中的映射

允许直接使用网络地址

1
import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;.

注释

1
2
3
4
5
6
// 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 or false

  • 整型(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)

    1
    2
    enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
    ActionChoices choice;
  • 函数(Function Types)

    1
    function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]
  • 存储类型

    • memory:内存
    • storage:存储
    • calldata:存储函数参数的不可修改的非持久性区域
  • 数组(array)
    成员:

    • .length
      1
      2
      uint[] memory a = new uint[](7);
      bytes memory b = new bytes(len);
  • 结构(Structs)

    1
    2
    3
    4
    5
    // Defines a new type with two fields.
    struct Funder {
    address addr;
    uint amount;
    }
  • 映射(Mapping)

1
mapping(_KeyType => _ValueType)_KeyType_ValueType

删除

1
delete a

基本类型之间的转换

隐式转换

uint8可以转换为 uint16 and int128 to int256
但是 int8 不能转换为 uint256 ,因为uint256不能为-1

显式转换

1
2
int8  y  =  - 3 ; 
uint x = uint (y );

自动会默认一个类型

1
2
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):
    从椭圆曲线签名中恢复与公钥相关的地址,或在错误时返回零(示例用法

地址相关

  • .balance (uint256) 地址的余额,单位wei。
  • .transfer(uint256 amount) 发起转账。failure:throws ,损失2300gas。
  • .send(uint256 amount) returns (bool) 发起转账。failure:return false ,损失2300gas。
  • .call(...) returns (bool) 发起低级别转账。failure:return false ,损失全部gas,可以调整
  • .callcode(...) returns (bool) 发起低级别转账。failure:return false ,损失全部gas,可以调整
  • .delegatecall(...) returns (bool) 发起低级别转账。failure:return false ,损失全部gas,可以调整

合同相关

  • this 当前合同
  • selfdestruct(address recipient)
    销毁当前合同,将资金发送到指定地址
  • suicide(address recipient)
    selfdestruct 别名

表达式和控制结构

输入参数和输出参数

输入参数

1
2
3
4
5
6
7
pragma solidity ^0.4.16;

contract Simple {
function taker(uint _a, uint _b) public pure {
// do something with _a and _b.
}
}

输出参数

1
return v

返回多个值

1
return (v0, v1, ..., vn)

控制结构

  • switch
  • goto.
  • if, else
  • while, do, for, break, continue,
  • return,
  • ? :

函数调用

1
2
this.g(8);
c.g(2);

内部函数调用

1
2
3
4
5
6
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(); }
}

外部函数调用

1
2
3
4
5
6
7
8
9
10
11
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)(); }
}

命名的呼叫和匿名功能参数

1
2
3
4
5
6
7
8
9
10
11
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)(); }
}

省略函数参数名称

1
2
3
4
5
6
7
8
pragma solidity ^0.4.16;

contract C {
// omitted name for parameter
function func(uint k, uint) public pure returns(uint) {
return k;
}
}

通过 new 创建合同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 )

使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @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,在这种情况下,它们保证不修改状态。

  • 写入状态变量。
  • 发射事件
  • 创建其他合同。* 使用selfdestruct
  • 通过电话发送以太。
  • 调用任何未标记的功能viewpure
  • 使用低级别呼叫。
  • 使用包含某些操作码的内联汇编

声明pure

函数可以声明为pure,在这种情况下,它们保证不读取或修改状态。

以下内容被认为是从状态中读取的:

  • 从状态变量读取。
  • 访问this.balance或
    .balance。
  • 访问任何成员block,tx,msg(与除外msg.sig和msg.data)。
  • 调用任何未标记的功能pure。
  • 使用包含某些操作码的内联汇编。

Fallback 函数

合同可以有一个未命名的功能。这个函数不能有参数,也不能返回任何东西。如果没有其他函数与给定的函数标识符匹配(或者根本没有提供数据),它将在对合同的调用中执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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);
}
}

函数重载

允许有相同名称但是不同参数的方法共存

1
2
3
4
5
6
7
8
9
10
11
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

事件方便外部进行监听。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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中调用事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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…

1
2
3
4
5
6
7
8
9
10
11
12
13
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。

抽象合约

仅仅声明,没有实体

1
2
3
4
5
pragma solidity ^0.4.0;

contract Feline {
function utterance() public returns (bytes32);
}

接口

接口与抽象类似,但是还有更多的限制。

  • 无法继承其他合同或接口。
  • 无法定义构造函数。
  • 无法定义变量。
  • 无法定义结构。
  • 无法定义枚举。
1
2
3
4
5
pragma solidity ^0.4.11;

interface Token {
function transfer(address recipient, uint amount) public;
}

部署在特定地址的公用函数

Using For

我理解类似apply或者中间件的意思,所有的参数使用先经过这个use 函数的处理。

1
2
3
4
5
6
7
8
9
10
11
12
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 &#124;
10 Inequality operators <><=>=
11 Equality operators ==!=
12 Logical AND &&
13 Logical OR &#124;&#124;
14 Ternary operator <conditional> ? <if-true> : <if-false>
15 Assignment operators =, &#124;=, ^=&=<<=>>=+=-=*=/=%=
16 Comma operator ,

欢迎关注我的其它发布渠道