Solidity學習紀錄

Posted by Kubeguts on 2017-09-12

這篇主要記錄著我對Solidity官網文檔的學習紀錄

簡單的合約開始介紹起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity ^0.4.0; // 告訴compiler要如何對待這份code

contract SimpleStorage {
uint storedData; // 宣告uint型態的變數 uint為 256 bits.

// 以下控制stored variable.
function set(uint x) {
storedData = x;
}

function get() constant returns (uint) {
return storedData;
}
}

另外一個比較複雜的合約

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
pragma solidity ^0.4.0;

contract Coin {
// The keyword "public" makes those variables
// readable from outside.
address public minter;

mapping (address => uint) public balances;
// 可把mapping 當成是hash tables
// 將address當參數 映射到balances中會得到uint型態的回傳值

// Events allow light clients to react on
// changes efficiently.
event Sent(address from, address to, uint amount);

// 和contract同名的函式名稱即為constructor,在合約被創造出來時呼叫
function Coin() {
minter = msg.sender;
}

function mint(address receiver, uint amount) {
if (msg.sender != minter) return;
balances[receiver] += amount;
}

function send(address receiver, uint amount) {
if (balances[msg.sender] < amount) return;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Sent(msg.sender, receiver, amount);
// msg.sender 合約的持有者
}
}
  • address:160-bit value that does not allow any arithmetic operations.

  • public:使其他合約可以存取自己的scope function,variable.

  • address public minter 可看成如下:

1
function minter() returns (address) { return minter; }
  • mapping (address => uint) public balances;可看成如下
1
2
3
function balances(address _account) returns (uint) {
return balances[_account];
}
  • event Sent(address from, address to, uint amount);

透過該Sent function方便追蹤錢的流出和流入地址是哪

  • Coin():為建構子,合約創造出來就會呼叫且建構子會儲存:
  1. msg:儲存創造合約的人的address以及其他properties (tx,block…),擁有直接和blockchain溝通的權利
  2. msg.sender:呼叫合約的人,若contract A呼叫contract B,那msg.sender在contract A B都是相同的。

Ethereum Virtual Machine

The Ethereum Virtual Machine or EVM is the runtime environment for smart contracts in Ethereum.

Features:

  • Overview
    The Ethereum Virtual Machine or EVM is the runtime environment for smart contracts in Ethereum
  • Accounts
    External accounts that are controlled by public-private key pairs (i.e. humans)
    Contract accounts which are controlled by the code stored together with the account.
    ----Every account has a persistent key-value store mapping 256-bit words to 256-bit words called storage.
  • Transactions
  • Gas
    The gas price is a value set by the creator of the transaction
    合約的手續費,用來執行合約的燃料,避免Contract有bug會把錢一直轉走
  • Storage, Memory and the Stack

Storage:
每個Contract都會持有自己的storage,storage為一個key-value store that maps 256-bit words to 256-bit words.

Memory
of which a contract obtains a freshly cleared instance for each message call

  • Delegatecall / Callcode and Libraries

Solidity Examples

Solidity Example 1:Voting Contract.

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
pragma solidity ^0.4.11;

/// @title Voting with delegation.
contract Ballot {
// This declares a new complex type which will
// be used for variables later.
// It will represent a single voter.
struct Voter {
uint weight; // weight is accumulated by delegation
bool voted; // if true, that person already voted
address delegate; // person delegated to
uint vote; // index of the voted proposal
}

// This is a type for a single proposal.
struct Proposal {
bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes
}

address public chairperson;

// This declares a state variable that
// stores a `Voter` struct for each possible address.
// 將每一個地址映射到對應的 Voter struct.
mapping(address => Voter) public voters;

// A dynamically-sized array of `Proposal` structs.
Proposal[] public proposals;

/// Create a new ballot to choose one of `proposalNames`.
function Ballot(bytes32[] proposalNames) {
chairperson = msg.sender;
voters[chairperson].weight = 1;

// For each of the provided proposal names,
// create a new proposal object and add it
// to the end of the array.
for (uint i = 0; i < proposalNames.length; i++) {
// `Proposal({...})` creates a temporary
// Proposal object and `proposals.push(...)`
// appends it to the end of `proposals`.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}

// Give `voter` the right to vote on this ballot.
// May only be called by `chairperson`.
function giveRightToVote(address voter) {
// If the argument of `require` evaluates to `false`,
// it terminates and reverts all changes to
// the state and to Ether balances. It is often
// a good idea to use this if functions are
// called incorrectly. But watch out, this
// will currently also consume all provided gas
// (this is planned to change in the future).
require((msg.sender == chairperson) && !voters[voter].voted && (voters[voter].weight == 0));
voters[voter].weight = 1;
}

/// Delegate your vote to the voter `to`.
function delegate(address to) {
// assigns reference
Voter storage sender = voters[msg.sender];
require(!sender.voted);

// Self-delegation is not allowed.
require(to != msg.sender);

// Forward the delegation as long as
// `to` also delegated.
// In general, such loops are very dangerous,
// because if they run too long, they might
// need more gas than is available in a block.
// In this case, the delegation will not be executed,
// but in other situations, such loops might
// cause a contract to get "stuck" completely.
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;

// We found a loop in the delegation, not allowed.
require(to != msg.sender);
}

// Since `sender` is a reference, this
// modifies `voters[msg.sender].voted`
sender.voted = true;
sender.delegate = to;
Voter delegate = voters[to];
if (delegate.voted) {
// If the delegate already voted,
// directly add to the number of votes
proposals[delegate.vote].voteCount += sender.weight;
} else {
// If the delegate did not vote yet,
// add to her weight.
delegate.weight += sender.weight;
}
}

/// Give your vote (including votes delegated to you)
/// to proposal `proposals[proposal].name`.
function vote(uint proposal) {
Voter storage sender = voters[msg.sender];
require(!sender.voted);
sender.voted = true;
sender.vote = proposal;

// If `proposal` is out of the range of the array,
// this will throw automatically and revert all
// changes.
proposals[proposal].voteCount += sender.weight;
}

/// @dev Computes the winning proposal taking all
/// previous votes into account.
function winningProposal() constant returns (uint winningProposal) {
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal = p;
}
}
}

// Calls winningProposal() function to get the index
// of the winner contained in the proposals array and then
// returns the name of the winner
function winnerName() constant
returns (bytes32 winnerName)
{
winnerName = proposals[winningProposal()].name;
}
}

Solidity Example 2:Simple Open Auction

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
118
119
pragma solidity ^0.4.11;

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 auctionStart;
uint public biddingTime;

// 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
) {
beneficiary = _beneficiary;
auctionStart = now;
biddingTime = _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() 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 <= (auctionStart + biddingTime));

// If the bid is not higher, send the
// money back.
require(msg.value > highestBid);

if (highestBidder != 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;
HighestBidIncreased(msg.sender, msg.value);
}

/// Withdraw a bid that was overbid.
function withdraw() 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() {
// 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 >= (auctionStart + biddingTime)); // auction did not yet end
require(!ended); // this function has already been called

// 2. Effects
ended = true;
AuctionEnded(highestBidder, highestBid);

// 3. Interaction
beneficiary.transfer(highestBid);
}
}
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
pragma solidity ^0.4.11;

contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint deposit;
}

address public beneficiary;
uint public auctionStart;
uint public biddingEnd;
uint public revealEnd;
bool public ended;

mapping(address => Bid[]) public bids;

address public highestBidder;
uint public highestBid;

// Allowed withdrawals of previous bids
mapping(address => uint) pendingReturns;

event AuctionEnded(address winner, uint highestBid);

/// Modifiers are a convenient way to validate inputs to
/// functions. `onlyBefore` is applied to `bid` below:
/// The new function body is the modifier's body where
/// `_` is replaced by the old function body.
modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { require(now > _time); _; }

function BlindAuction(
uint _biddingTime,
uint _revealTime,
address _beneficiary
) {
beneficiary = _beneficiary;
auctionStart = now;
biddingEnd = now + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}

/// Place a blinded bid with `_blindedBid` = keccak256(value,
/// fake, secret).
/// The sent ether is only refunded if the bid is correctly
/// revealed in the revealing phase. The bid is valid if the
/// ether sent together with the bid is at least "value" and
/// "fake" is not true. Setting "fake" to true and sending
/// not the exact amount are ways to hide the real bid but
/// still make the required deposit. The same address can
/// place multiple bids.
function bid(bytes32 _blindedBid)
payable
onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({
blindedBid: _blindedBid,
deposit: msg.value
}));
}

/// Reveal your blinded bids. You will get a refund for all
/// correctly blinded invalid bids and for all bids except for
/// the totally highest.
function reveal(
uint[] _values,
bool[] _fake,
bytes32[] _secret
)
onlyAfter(biddingEnd)
onlyBefore(revealEnd)
{
uint length = bids[msg.sender].length;
require(_values.length == length);
require(_fake.length == length);
require(_secret.length == length);

uint refund;
for (uint i = 0; i < length; i++) {
var bid = bids[msg.sender][i];
var (value, fake, secret) =
(_values[i], _fake[i], _secret[i]);
if (bid.blindedBid != keccak256(value, fake, secret)) {
// Bid was not actually revealed.
// Do not refund deposit.
continue;
}
refund += bid.deposit;
if (!fake && bid.deposit >= value) {
if (placeBid(msg.sender, value))
refund -= value;
}
// Make it impossible for the sender to re-claim
// the same deposit.
bid.blindedBid = 0;
}
msg.sender.transfer(refund);
}

// This is an "internal" function which means that it
// can only be called from the contract itself (or from
// derived contracts).
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != 0) {
// Refund the previously highest bidder.
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}

/// Withdraw a bid that was overbid.
function withdraw() {
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 (see the remark above about
// conditions -> effects -> interaction).
pendingReturns[msg.sender] = 0;

msg.sender.transfer(amount);
}
}

/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd()
onlyAfter(revealEnd)
{
require(!ended);
AuctionEnded(highestBidder, highestBid);
ended = true;
// We send all the money we have, because some
// of the refunds might have failed.
beneficiary.transfer(this.balance);
}
}

Solidity Example 4: Remote Purchase

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
pragma solidity ^0.4.11;

contract Purchase {
uint public value;
address public seller;
address public buyer;
enum State { Created, Locked, Inactive }
State public state;

function Purchase() payable {
seller = msg.sender;
value = msg.value / 2;
require((2 * value) == msg.value);
}

modifier condition(bool _condition) {
require(_condition);
_;
}

modifier onlyBuyer() {
require(msg.sender == buyer);
_;
}

modifier onlySeller() {
require(msg.sender == seller);
_;
}

modifier inState(State _state) {
require(state == _state);
_;
}

event Aborted();
event PurchaseConfirmed();
event ItemReceived();

/// Abort the purchase and reclaim the ether.
/// Can only be called by the seller before
/// the contract is locked.
function abort()
onlySeller
inState(State.Created)
{
Aborted();
state = State.Inactive;
seller.transfer(this.balance);
}

/// Confirm the purchase as buyer.
/// Transaction has to include `2 * value` ether.
/// The ether will be locked until confirmReceived
/// is called.
function confirmPurchase()
inState(State.Created)
condition(msg.value == (2 * value))
payable
{
PurchaseConfirmed();
buyer = msg.sender;
state = State.Locked;
}

/// Confirm that you (the buyer) received the item.
/// This will release the locked ether.
function confirmReceived()
onlyBuyer
inState(State.Locked)
{
ItemReceived();
// It is important to change the state first because
// otherwise, the contracts called using `send` below
// can call in again here.
state = State.Inactive;

// NOTE: This actually allows both the buyer and the seller to
// block the refund - the withdraw pattern should be used.

buyer.transfer(value);
seller.transfer(this.balance);
}
}

Solidity 語法

Address

Holds a 20 byte value (size of an Ethereum address)

Members of Addresses

address包含著有以下的properties:

  • balance:地址的餘額
  • transfer:轉錢到該地址 若有錯誤會發送exception
1
2
3
4
address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10)
x.transfer(10); // 送給x這個地址 10塊ether
  • send :也是轉錢到該地址 若有錯誤只會return false,使用要比較小心

  • call :傳送參數(.value())或函式回傳值給合約,

  • delegatecall

1
2
3
4
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
nameReg.call("register", "MyName");
nameReg.call(bytes4(keccak256("fun(uint256)")), a);
nameReg.call.value(10);

Enums:類似實作interface

Enums needs at least one member.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pragma solidity ^0.4.0;

contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;

function setGoStraight() {
choice = ActionChoices.GoStraight;
}

// Since enum types are not part of the ABI, the signature of "getChoice"
// will automatically be changed to "getChoice() returns (uint8)"
// for all matters external to Solidity. The integer type used is just
// large enough to hold all enum values, i.e. if you have more values,
// `uint16` will be used and so on.
function getChoice() returns (ActionChoices) {
return choice;
}

function getDefaultChoice() returns (uint) {
return uint(defaultChoice);
}
}

Functional的種類以及參數(constnat,payble)

  • Internal function: can only be called inside the current contract.
  • External function: consist of an address and a function signature and they can be passed via and returned from external function calls.

function宣告的格式:

1
2
3
4
5
6
7
8
9
function (<parameter types>) {internal|external} [constant] [payable] [returns (<return types>)]

parameter types:參數的形式
constant:標註該functionread-only,不會改變contractstate.
payable: 設定會需要收ether的函式都要加一個payable屬性,如果沒加而有人呼叫該函式順便帶ether的話就會造成error


ex:
function (address chairman) {} constant payble returns (uint) {};

兩種方法存取該function:

  • f: will result in an internal function,
  • this.f: an external function.

Internal Example: 類似OOD的Protected、不能被其他合約呼叫

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
pragma solidity ^0.4.5;

library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) returns (uint) f)
internal
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) returns (uint) f
)
internal
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}

contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal returns (uint) {
return x + y;
}
}

External: 其他合約可以呼叫該合約的function

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
pragma solidity ^0.4.11;

contract Oracle {
struct Request {
bytes data;
function(bytes memory) external callback;
}
Request[] requests;
event NewRequest(uint);
function query(bytes data, function(bytes memory) external callback) {
requests.push(Request(data, callback));
NewRequest(requests.length - 1);
}
function reply(uint requestID, bytes response) {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
}

contract OracleUser {
Oracle constant oracle = Oracle(0x1234567); // known contract
function buySomething() {
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(bytes response) {
require(msg.sender == address(oracle));
// Use the data
}
}

宣告Array

ex:

1
uint[] memory a = new uint[](7);

宣告 Struct: 類似宣告一個物件模板

1
2
3
4
struct Funder {
address addr;
uint amount;
}

Mappings

宣告成: mapping(_KeyType => _ValueType) 的形式,可以看成是hash table的形式,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pragma solidity ^0.4.0;

contract MappingExample {
mapping(address => uint) public balances;
//設定balances的index為addreess形態的,映射出後會得到uint型態的回傳值。

function update(uint newBalance) {
balances[msg.sender] = newBalance;
}
}

contract MappingUser {
function f() returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(this);
}
}

其他 Operators Involving LValues(可被assign的value)

delete a: 把a初始化成 0
(也可以用在array 都設成0, struct 都設成初始值)

Ether單位

Ether Uint:

1 ether =
1000000000000000000 wei, (10^18)
1000 finney, (10^3)
1000000 szabo (10^6)

全域可用變數及函式

Special Variables and Functions

  • suicide.(A合約): 將目前合約的所有ether都轉入到指定的A合約(contract)

  • delete:回收宣告的成員,並且返回一些gas當作回收的獎勵。

回收各型態的參考資料:http://me.tryblockchain.org/solidity-delete.html

Block and Transaction Properties

  • block.blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent blocks excluding current
  • 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
  • msg.data (bytes): complete calldata
  • msg.gas (uint): remaining gas
  • 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)(不建議用)

Error Handling

  • assert(bool condition):
    throws if the condition is not met - to be used for internal errors.
  • require(bool condition):
    throws if the condition is not met - to be used for errors in inputs or external components.
  • revert():
    abort execution and revert state changes

Mathematical and Cryptographic Functions

  • addmod(uint x, uint y, uint k) returns (uint):
    compute (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2**256.
  • mulmod(uint x, uint y, uint k) returns (uint):
    compute (x * y) % k where the multiplication is performed with arbitrary precision and does not wrap around at 2**256.
  • keccak256(…) returns (bytes32):
    compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
  • sha256(…) returns (bytes32):
    compute the SHA-256 hash of the (tightly packed) arguments
    sha3(…) returns (bytes32):
    alias to keccak256
  • ripemd160(…) returns (bytes20):
    compute RIPEMD-160 hash of the (tightly packed) arguments
  • ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):
    recover the address associated with the public key from elliptic curve signature or return zero on error (example usage)

Solidity的表達和控制的結構(Expressions and Control Structures)

Input Parameters and Output Parameters 接收參數和回傳的形式

1
2
3
4
5
6
7
8
9
10
pragma solidity ^0.4.0;

contract Simple {
// 接收 uint _a , uint _b變數
// 回傳uint -_sum uint o_product
function arithmetics(uint _a, uint _b) returns (uint o_sum, uint o_product) {
o_sum = _a + _b;
o_product = _a * _b;
}
}

External Function Calls

如果要得知呼叫某合約的function會有多少Wei和花費多少gas,可用
.value() .gas()

範例中的function info() 如果沒有加上 payable這個keyword,就無法使用 .value()

1
2
3
4
5
6
7
8
9
10
11
12
pragma solidity ^0.4.0;

contract InfoFeed {
//
function info() payable returns (uint ret) { return 42; }
}

contract Consumer {
InfoFeed feed;
function setFeed(address addr) { feed = InfoFeed(addr); }
function callFeed() { feed.info.value(10).gas(800)(); }
}

Creating Contracts via 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) payable {
x = a;
}
}

contract C {
D d = new D(4); // will be executed as part of C's constructor

function createD(uint arg) {
D newD = new D(arg);
}

function createAndEndowD(uint arg, uint amount) {
// Send ether along with the creation
D newD = (new D).value(amount)(arg);
}
}

Error handling: Assert, Require, Revertand Exceptions

  • require can be used to easily check conditions on inputs.
  • assert can be used for internal error checking.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity ^0.4.0;

contract Sharer {
function sendHalf(address addr) payable returns (uint balance) {
require(msg.value % 2 == 0); // Only allow even numbers
uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2);
// Since transfer throws an exception on failure and
// cannot call back here, there should be no way for us to
// still have half of the money.
assert(this.balance == balanceBeforeTransfer - msg.value / 2);
return this.balance;
}
}

assert 發生的狀況:

  • If you access an array at a too large or negative index (i.e. x[i] where i >= x.length or i < 0).
  • If you access a fixed-length bytesN at a too large or negative index.
  • If you divide or modulo by zero (e.g. 5 / 0 or 23 % 0).
  • If you shift by a negative amount.
  • If you convert a value too big or negative into an enum type.
  • If you call a zero-initialized variable of internal function type.
  • If you call assert with an argument that evaluates to false.

require 發生的狀況:

  • Calling throw.
  • Calling require with an argument that evaluates to false.
  • If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation call, send, delegatecall or callcode is used. The low level operations never throw exceptions but indicate failures by returning false.
  • If you create a contract using the new keyword but the contract creation does not finish properly (see above for the definition of “not finish properly”).
  • If you perform an external function call targeting a contract that contains no code.
  • If your contract receives Ether via a public function without payable modifier (including the constructor and the fallback function).
  • If your contract receives Ether via a public getter function.
  • If a .transfer() fails.

Solidity Contract(即Class)

創建contract的時候,constructor只會被呼叫一次

合約的Visibility and Getters

Visibility:

  • external:
    External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function f cannot be called internally (i.e. f() does not work, but this.f() works). External functions are sometimes more efficient when they receive large arrays of data.
  • public:
    Public functions are part of the contract interface and can be either called internally or via messages. For public state variables, an automatic getter function (see below) is generated.
  • internal:
    Those functions and state variables can only be accessed internally (i.e. from within the current contract or contracts deriving from it), without using this.
  • private:
    Private functions and state variables are only visible for the contract they are defined in and not in derived contracts.

Getter Functions

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

contract C {
uint public data;
function x() {
data = 3; // internal access 當成state變數
uint val = this.data(); // external access 當成是function
}
}

Function Modifier (類似:函式插槽)

Modifier可用用來擴充其他function的內容,需要被inherit才能使用。
通常modifier用來設定一些條件,幫助函式被執行時能夠先滿足該條件再被執行。

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
pragma solidity ^0.4.11;

contract owned {
function owned() { owner = msg.sender; }
address owner;
// 該owned合約只有宣告一個modifier onlyOwner,並且沒有使用它
// 該modifier只會被其他有繼承owned contract的合約所使用
//
// "_;" 要繼承onlyOwner 函式的內容
modifier onlyOwner {
require(msg.sender == owner);
_;
}
}

contract mortal is owned {
// 繼承 owned合約

// close函式 使用modifier "onlyOwner"
// 且close的內容會插入在 modifier中的 "_;"中
// 可以把modifier想像成 slot(插槽)
function close() onlyOwner {
selfdestruct(owner);
}
}

contract priced {
// Modifier 也可以接收參數
modifier costs(uint price) {
// 若滿足msg.value >= price 那就執行 使用該modifier函式的內容
if (msg.value >= price) {
_;
}
}
}

contract Register is priced, owned {
mapping (address => bool) registeredAddresses;
uint price;

function Register(uint initialPrice) { price = initialPrice; }

// 需要提供 payable,register才會接受ether
// 使用 costs 的modifier .
function register() payable costs(price) {
registeredAddresses[msg.sender] = true;
}

function changePrice(uint _price) onlyOwner {
price = _price;
}
}

contract Mutex {
bool locked;
modifier noReentrancy() {
require(!locked);
locked = true;
_;
locked = false;
}
// noReentrancy 被一個mutex所保護,必須要判斷locked是否是false才能執行 noReentrancy才能執行

// reentrant calls from within
// msg.sender.call cannot call f again.因爲lock住了

// 使用 noReentrancy(modifier),
function f() noReentrancy returns (uint) {
require(msg.sender.call());
return 7;
}
}

Constant State Variables

目前僅支援 uint, string使用

宣告一次後就不能再被改變

Constant Functions

不會改變contract state的值

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

contract C {
function f(uint a, uint b) constant returns (uint) {
return a * (b + 42);
}
}

Fallback Function

沒有名稱的function,並且沒有接收參數以及不會傳任何值。
當呼叫某合約的方法時,沒有辦法成功

如果有合約直接收到ether的話(亦即不是透過send() or transfer() 那就得定義一個fallback function,不然會throw an exception.

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
pragma solidity ^0.4.0;

contract Test {
function() { x = 1; }
uint x;
}

// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
function() payable { }
}

contract Caller {
function callTest(Test test) {
test.call(0xabcdef01);
// hash: 0xabcdef01 不存在
// 故 test.x的結果會變成 test.x = 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);
}
}

Events

Event所寫入的資料會被記錄在一個Receipt(transaction logs)資料裡,並等待被打包進區塊鏈。

白話一點就是:通知全網有一件事情發生。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.4.0;

contract ClientReceipt {
event Deposit(
address indexed _from,
bytes32 indexed _id,
uint _value
);

function deposit(bytes32 _id) payable {
// Any call to this function (even deeply nested) can
// be detected from the JavaScript API by filtering
// for `Deposit` to be called.
Deposit(msg.sender, _id, msg.value);
}
}

在javascript API的使用方式如下:

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(0x123 /* 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);
});

Inheritance

使用 is 來繼承其他contract.

提供多重繼承同時也包含多型
當一個合約R繼承其他A,B,C的合約時,只有R的合約被打包進去blockchain,其他ABC不會。(因為是將A,B,C的內容複製到R的合約內)

像是Python的繼承特性

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
pragma solidity ^0.4.0;

contract owned {
function owned() { owner = msg.sender; }
address owner;
}

// 使用 "is" 來繼承,可以存取繼承合約的 non-private members (包含 internal function and state variables),無法透過 this 來做externally access.

contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}

// 當成是 interface,等待被繼承實作
contract Config {
function lookup(uint id) returns (address adr);
}

contract NameReg {
function register(bytes32 name);
function unregister();
}
// 可以實現多重繼承
// 注意的是"owned" 一樣也被mortal繼承,故只有一個"owned"的instance (和C++的vritual inheritance一樣)

contract named is owned, mortal {
function named(bytes32 name) {
Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);
NameReg(config.lookup(1)).register(name);
}

// 可以override 繼承的function.注意要與原本的型態要一致
function kill() {
if (msg.sender == owner) {
Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);
NameReg(config.lookup(1)).unregister();
// 依然可以呼叫繼承合約內的函式
mortal.kill();
}
}
}

// 如果繼承的contract中有的人的contractor需要parameter,
// 那就得在 is 後面的地方輸入參數,
// 如下 "named("GoldFeed");
contract PriceFeed is owned, mortal, named("GoldFeed") {
function updateInfo(uint newInfo) {
if (msg.sender == owner) info = newInfo;
}

function get() constant returns(uint r) { return info; }

uint info;
}

多重繼承要注意的點:

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
pragma solidity ^0.4.0;

contract owned {
function owned() { owner = msg.sender; }
address owner;
}

contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}

contract Base1 is mortal {
function kill() { /* do cleanup 1 */ mortal.kill(); }
}


contract Base2 is mortal {
function kill() { /* do cleanup 2 */ mortal.kill(); }
}


contract Final is Base1, Base2 {
}

呼叫 Final.kill 僅會呼叫到 Base2的 kill function.
而忽略掉 Base1 的.

Abstract Contracts

不實作合約內的function內容,被繼承時在實作。
但可以定義變數,建構子等等。

例子:

1
2
3
4
5
pragma solidity ^0.4.0;

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

Interfaces

和Abstract很像,不過不能有任何的function被實作
以下為限制條件

  • Cannot inherit other contracts or interfaces.
  • Cannot define constructor.
  • Cannot define variables.
  • Cannot define structs.
  • Cannot define enums.
1
2
3
4
5
pragma solidity ^0.4.11;

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

Libraries

和contract(class)很像,不過僅會在特地的address部署一次而已。

因為佈一個contract需要gas,
不過如果要重複使用Set的話,又不想重複宣告contract,那就可以用
Libraries來實作出一個Set,並且佈出去就會產生一個地址 (linker),
其他合約就可以透過linker來使用Set.

直接看例子

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
pragma solidity ^0.4.11;

library Set {
// We define a new struct datatype that will be used to
// hold its data in the calling contract.
struct Data { mapping(uint => bool) flags; }

// Note that the first parameter is of type "storage
// reference" and thus only its storage address and not
// its contents is passed as part of the call. This is a
// special feature of library functions. It is idiomatic
// to call the first parameter 'self', if the function can
// be seen as a method of that object.
function insert(Data storage self, uint value)
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}

function remove(Data storage self, uint value)
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}

function contains(Data storage self, uint value)
returns (bool)
{
return self.flags[value];
}
}


contract C {
Set.Data knownValues;

function register(uint value) {
// The library functions can be called without a
// specific instance of the library, since the
// "instance" will be the current contract.
require(Set.insert(knownValues, value));
}
// In this contract, we can also directly access knownValues.flags, if we want.
}

Style Guide (套用eslint for solidity)

  • Indentation: 4 spaces (avoid using tabs.)
  • Order of Functions: (function的寫法的優先順序)
  1. constructor
  2. fallback function (if exists)
  3. external
  4. public
  5. internal
  6. private

Web3.js 學習目標

瞭解:
1.如何把key pair抓出來
2.如何簽驗章
3.如何進行加解密
4.如何發events