Ethereum智能合約 101

Posted by Benjamin Lu on 2017-08-23

Ethereum智能合約 101

安裝go-ethereum

Mac

Windows或其他版本

開啟一個私有鏈開發節點

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
geth --dev
WARN [07-03|13:08:30] No etherbase set and no accounts found as default
INFO [07-03|13:08:30] Starting peer-to-peer node instance=Geth/v1.6.5-stable-cf87713d/darwin-amd64/go1.8.3
INFO [07-03|13:08:30] Allocated cache and file handles database=/var/folders/hs/tgqs62993t9gkrkr86d7z2hc0000gn/T/ethereum_dev_mode/geth/chaindata cache=128 handles=1024
INFO [07-03|13:08:30] Initialised chain configuration config="{ChainID: 1337 Homestead: 0 DAO: <nil> DAOSupport: false EIP150: 0 EIP155: 0 EIP158: 0 Metropolis: 0 Engine: ethash}"
WARN [07-03|13:08:30] Ethash used in test mode
INFO [07-03|13:08:30] Initialising Ethereum protocol versions="[63 62]" network=1
INFO [07-03|13:08:30] Loaded most recent local header number=0 hash=e5be92…38f3bc td=131072
INFO [07-03|13:08:30] Loaded most recent local full block number=0 hash=e5be92…38f3bc td=131072
INFO [07-03|13:08:30] Loaded most recent local fast block number=0 hash=e5be92…38f3bc td=131072
INFO [07-03|13:08:30] Starting P2P networking
INFO [07-03|13:08:30] started whisper v.5.0
INFO [07-03|13:08:30] RLPx listener up self="enode://f7a7c9f414faa49e0e6bb72c1bddead9c6f8074e48323d444c23cafffb3583b42ab0dd496053f81f76004a4931228cce5989a3227130be42c7dd81e3332d4831@[::]:65282?discport=0"
INFO [07-03|13:08:30] IPC endpoint opened: /var/folders/hs/tgqs62993t9gkrkr86d7z2hc0000gn/T/ethereum_dev_mode/geth.ipc

上方會顯示測試鏈放置資料的路徑.../chaindata資料夾底下
network=1表示要跟此節點互相同步區塊要設定的subnet id
同時創世區塊要相同,且透過admin.addPeer()指令進行p2p網路的溝通
之後透過指定的ipc可以跟這個節點溝通,此案例是/var/folders/hs/tgqs62993t9gkrkr86d7z2hc0000gn/T/ethereum_dev_mode/geth.ipc

透過Geth JavaScript console 連接節點

此例IPC是/var/folders/hs/tgqs62993t9gkrkr86d7z2hc0000gn/T/ethereum_dev_mode/geth.ipc

1
geth attach <IPC path>

操作節點指令

官方文件

Account

Account列表

1
eth.accounts

創一個新帳號

1
2
personal.newAccount("your password")
"0x4560eb5a01478cbfa1390a4134cd752d99746266"

解鎖帳號才能做交易

1
2
personal.unlockAccount("0x4560eb5a01478cbfa1390a4134cd752d99746266", "your password")
true

以後開啟節點直接解鎖

1
2
3
4
5
geth --dev --unlock 0x4560eb5a01478cbfa1390a4134cd752d99746266
...
Passphrase: <your password>

控制Miner

預設用第一個account的地址作為coinbase地址
所以開始挖礦以後,coinbase地址就會有錢

用PoA還是PoW的設定可以看meetup的文章

公網在PoS上線前,還是用PoW算sha3()
PoA 不用挖礦,只需要指定節點做Authority

Parity 1.5版 (Rust語言撰寫)以後支援建立Proof of Authority Chains
geth 1.6 (Go語言撰寫)之後支援PoA
但兩者的PoA根據不同的EIP來實作

geth實作的PoA為EIPs 225
Clique PoA protocol

1
2
3
4
5
6
7
8
9
miner.start() // 開始挖礦 coinbase地址會有ETH讓你做交易或轉帳
miner.start(3) // 同時3條thread在挖礦
miner.stop() // 結束挖礦
eth.mining // 看是不是在挖礦
eth.coinbase // 礦工會得到虛擬貨幣獎勵的地址
miner.setEtherbase(eth.accounts[2]) // 換礦工地址

交易相關指令

你有多少錢

Ether unit單位表

1
2
3
4
5
6
7
eth.getBalance("0x4560eb5a01478cbfa1390a4134cd752d99746266")
120000000000000000000 // 單位是wei, 1 ether = 10的負18次方個wei
web3.fromWei(eth.getBalance(eth.accounts[0])) // 第0個地址有多少ETH
web3.toWei(120) // 120個ether是多少個wei
"120000000000000000000"

做一個轉帳交易

1
2
> eth.accounts
["0x4560eb5a01478cbfa1390a4134cd752d99746266", "0xcd9f3bf82cce5a654a6e7f729200f86dc9cc96c7"]

0x4560eb5a01478cbfa1390a4134cd752d99746266轉帳1個ETH到0xcd9f3bf82cce5a654a6e7f729200f86dc9cc96c7

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
eth.sendTransaction({
from: "0x4560eb5a01478cbfa1390a4134cd752d99746266",
to: "0xcd9f3bf82cce5a654a6e7f729200f86dc9cc96c7"
value: web3.toWei(1)
})
"0x4aa3627a3cc245542cf57c51036689216371bfb373744186f229207fb63cbdf7"
eth.getTransaction("0x4aa3627a3cc245542cf57c51036689216371bfb373744186f229207fb63cbdf7")
{
blockHash: "0xd14ff6de146210fc3b27e9771faa932de12a9abf721b12aaef88088d8d1395ae",
blockNumber: 136,
from: "0x4560eb5a01478cbfa1390a4134cd752d99746266",
gas: 90000, // 花掉的gas
gasPrice: 0, // 私有鏈沒有gasPrice的問題
hash: "0x4aa3627a3cc245542cf57c51036689216371bfb373744186f229207fb63cbdf7",
input: "0x",
nonce: 0, // 用這個值在處理double spending
r: "0xebd558f63d574a0002ba3be4b495c058ab6ad6d1803d1eb1bc10fa01e9be32e7",
s: "0x81d5efa2f81a7e11e639ff9a64aa9554f8bab9fe0bf43995ae3b63d4eb4fa1e",
to: "0xcd9f3bf82cce5a654a6e7f729200f86dc9cc96c7",
transactionIndex: 0,
v: "0xa95",
value: 1000000000000000000
}
web3.fromWei(eth.getBalance("0xcd9f3bf82cce5a654a6e7f729200f86dc9cc96c7")) // 的確收到1ETH

智能合約 101 - Solidity為例

Ethereum的EVM上智能合約語言有以下幾種:

  1. Solidity (Javascript-like)
  2. Viper (Python-like)
  3. Serpent (Python-like)
  4. LLL (Lisp-like)
  5. Mutan (Golang-like)

這邊用Solidity當作寫合約的語言

智能合約IDE

Remix 線上IDE

常用關鍵字

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
// 部署合約或呼叫合約的人
msg.sender
// 部署或呼叫合約的人付的錢
msg.value
// 合約的餘額
this.balance
// 由合約的錢轉帳this.balance數量的虛擬貨幣給address,會在鏈上形成交易
address.transfer(this.balance);
// 合約方法會需要收ether需要加關鍵字payable
// 沒有加的話呼叫該函式順便帶ether的話就會造成error
function pledge(bytes32 _message) payable {
if (msg.value == 0 || complete || refunded) revert(); // 權限管控 不合格 停止執行 回復合約狀態
pledges[numPledges] = Pledge(msg.value, msg.sender, _message);
numPledges++;
}
// 使用其他合約物件,要寫路徑和副檔名
import "Another.sol"
// 毀滅合約,將合約餘額轉給某個地址
// 當合約被銷毀, 如果有人再寄錢 (Ethers) 到該合約地址, 是不會被退回的, 它也就此消失
suicide(address)
// 0.4.0以上版本
selfdestruct(address)
// 資料格式
struct Pledge {
uint amount;
address eth_address;
bytes32 message;
}
// 修飾子等級
// function預設是public,state預設是internal
public - all
private - only this contract
internal - only this contract and contracts deriving from it
external - Cannot be accessed internally, only externally.
// mapping存key-value資料
mapping (address => uint) pendingWithdrawals;
// modifier重用檢查
modifier onlyOwner() {
if (msg.sender != owner) throw;
// 0.4.0之後這樣寫
_;
}
function forceOwnerChange(address _newOwner) onlyOwner
{
// just some example condition
if (uint(owner) & 0 == 1)
// This did not refund for Solidity
// before version 0.4.0.
return;
// refund overpaid fees
}

其他語法請看官方文件

撰寫合約程式

1
2
3
4
5
6
7
pragma solidity ^0.4.0;
contract test {
function multiply(uint a) constant returns(uint d) {
return a * 7;
}
}

透過web3.js和ABI建立對應合約的js物件

ABI為Application Binary Interface
要讓其他程式語言看懂合約的接口所以需要該介面

1
2
3
4
5
// web3常用格式,透過ABI產生可與合約互動的js物件
var contractClass = web3.eth.contract(<ABI>)
// 等下用此物件來部署此類合約
var browser_ballot_sol_testContract = web3.eth.contract([{"constant":true,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"payable":false,"type":"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
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
// 評估要花多少gas,通常會將此數字乘以1.2
web3.eth.estimateGas({data: '0x' + <bytecode>})
// 估計部署合約要花多少gas
web3.eth.estimateGas({data: '0x6060604052341561000f57600080fd5b5b60ab8061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063c6888fa114603d575b600080fd5b3415604757600080fd5b605b60048080359060200190919050506071565b6040518082815260200191505060405180910390f35b60006007820290505b9190505600a165627a7a72305820668369d5ec98bbd9e537eb377a779e58f87d27b4cb70f247366fafb14ceff1170029'})
98392
// 部署合約並產生合約的實例
var browser_ballot_sol_test = browser_ballot_sol_testContract.new(
{
// 用哪個地址去部署合約
from: web3.eth.accounts[0],
// 合約程式碼的bytecode
data: '0x6060604052341561000f57600080fd5b5b60ab8061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063c6888fa114603d575b600080fd5b3415604757600080fd5b605b60048080359060200190919050506071565b6040518082815260200191505060405180910390f35b60006007820290505b9190505600a165627a7a72305820668369d5ec98bbd9e537eb377a779e58f87d27b4cb70f247366fafb14ceff1170029',
// 要花的gas
gas: '4700000'
}, function (e, contract){
// 當成功部署合約 顯示合約地址和部署合約的交易
console.log(e, contract);
if (typeof contract.address !== 'undefined') {
console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
}
})
Contract mined! address: 0x2ff5f980d80cb37a5cd92678157ed49b06ef324f transactionHash: 0x0b5ba1bbdc28dddaa6b0b0207e9c72576c8c10b9db9b49b680791a33a4ab0bc7
eth.getTransaction("0x0b5ba1bbdc28dddaa6b0b0207e9c72576c8c10b9db9b49b680791a33a4ab0bc7")
{
blockHash: "0xd139f77cba988b25ae7f10c12138b28e6630808259b86f290540bf7d80cbd10d",
blockNumber: 858,
from: "0x4560eb5a01478cbfa1390a4134cd752d99746266",
gas: 4700000,
gasPrice: 0,
hash: "0x0b5ba1bbdc28dddaa6b0b0207e9c72576c8c10b9db9b49b680791a33a4ab0bc7",
input: "0x6060604052341561000f57600080fd5b5b60ab8061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063c6888fa114603d575b600080fd5b3415604757600080fd5b605b60048080359060200190919050506071565b6040518082815260200191505060405180910390f35b60006007820290505b9190505600a165627a7a72305820668369d5ec98bbd9e537eb377a779e58f87d27b4cb70f247366fafb14ceff1170029",
nonce: 1,
r: "0x47f9359d20cded498ef3719390e4fbfe654f6438fbec71c397b29c70e8224f0d",
s: "0x4cf4a110964044afd7de6bc3d1905f90583013d9948ed37736ebdc99b894c63d",
to: null,
transactionIndex: 0,
v: "0xa95",
value: 0
}
// 從js程式其他地方建立此合約地址的實例(意圖查看或改變此合約地址的state)
var contract = web3.eth.contract(<ABI>).at(<contract address>)
var contract = web3.eth.contract([{"constant":true,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"payable":false,"type":"function"}]).at("0x2ff5f980d80cb37a5cd92678157ed49b06ef324f")
// 與合約互動
browser_ballot_sol_test.multiply(7)
49 // 與合約互動的結果,呼叫合約的constant方法 不會在鏈上產生交易,不用等挖礦,也沒有改變state(不是合約的成員變數),純粹回傳某個值
contract.multiply(15)
105

區塊鏈系統的好處是
所有跟此節點同步區塊的節點,都可以跟此合約地址的合約互動與透過介面操作該合約的state
形成一個網路,發展更多應用

meetup的合約教學

更複雜的合約案例 - 募款

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
pragma solidity ^0.4.0;
contract smartSponsor {
address public owner;
address public benefactor;
bool public refunded;
bool public complete;
uint public numPledges;
struct Pledge {
uint amount;
address eth_address;
bytes32 message;
}
mapping(uint => Pledge) public pledges;
// constructor
function smartSponsor(address _benefactor) {
owner = msg.sender;
numPledges = 0;
refunded = false;
complete = false;
benefactor = _benefactor;
}
// add a new pledge
function pledge(bytes32 _message) payable {
if (msg.value == 0 || complete || refunded) revert();
pledges[numPledges] = Pledge(msg.value, msg.sender, _message);
numPledges++;
}
function getPot() constant returns (uint) {
return this.balance;
}
// refund the backers
function refund() {
if (msg.sender != owner || complete || refunded) revert();
for (uint i = 0; i < numPledges; ++i) {
pledges[i].eth_address.transfer(pledges[i].amount);
}
refunded = true;
complete = true;
}
// send funds to the contract benefactor
function drawdown() {
if (msg.sender != owner || complete || refunded) revert();
benefactor.transfer(this.balance);
complete = true;
}
}

原文