Upload
nicholas-lin
View
167
Download
0
Embed Size (px)
Citation preview
ethereum smart contract
林修平
線上即時編譯• online compiler :
https://ethereum.github.io/browser-solidity/
手動編譯 (using geth)
• connect to console
• set up a chain
• connect to main chain : go-ethereum/build/bin/geth console
• connect to testnet : go-ethereum/build/bin/geth --testnet console
• build a private net : go-ethereum/build/bin/geth --datadir “your_directory" --rpc --rpcport port --rpccorsdomain "*" --port "30303" --nodiscover --ipcapi "admin,db,eth,debug,miner,net,shh,txpool,personal,web3" --rpcapi "db,eth,net,web3" --autodag --networkid number --nat "any" console
手動編譯 (using geth)
• in console
• var contractABI = web3.eth.contract([{contractABI}]);
• var contract123 = contractABI.new( parameters, {from: address, data: bytecode, gas: gas }, callback_function)
快速介紹
快速介紹• 貨幣單位: ether
• address :代表一個 account ,可提錢存錢。分為兩種• 使用者• 合約:
• 會有一份 code 和這個 address 綁在一起。• 同一份 code 在不同 address 上就是不同合約。
• transaction :• 可為單純的送錢• 或用來執行合約(同時也可附錢)
快速介紹• 合約:分為狀態( state )和動作( function )
• state :可永久保留,使用者用來記錄合約的相關資訊• function :對這合約狀態產生影響的動作• 註: Ethereum 的設計機制是不鼓勵儲存 state 的,用到 state 耗費的 gas 特別高。
• 因為每個節點都要儲存一份一樣的資料,如果大家都用到大量的儲存,會造成所維護的鏈非常笨重。• gas
• 每一個函式都是由許多單元運算所組成,每個運算都有固定的算力消耗,因此每執行一次函式都要付出相對應該付的費用,而這個費用是用 ether 來付。• 當函式寫好, gas 耗費就已經固定,唯一可變的是每單位 gas 所價值的 ether 。你每單位
gas 給越多 ether ,礦工更願意收入你的 transaction ,也就更快被放進鏈裡。
快速介紹• 如果是公鏈,每分錢每分貨,每個狀態都要花成本,所以會斤斤計較• 如果使用的是私鏈呢?
• gas 好像就沒有這麼重要了?• 因為要有多少 ether 有多少 ether
• 但是狀態的儲存就可以想存多少就存多少嗎?• 沒錯,但同樣的,最後儲存的成本還是會轉嫁到每個節點身上。
運作過程
• 狀態• 函式(動作):操作資料• 邏輯:用函式達成目的
運作過程
運作過程
contract Count123{uint counts(0);function incre(){
counts = count + 1;
}}
Transaction(Deploy)
運作過程
contract Count123{uint counts(0);function incre(){
counts = count + 1;
}}
運作過程
Transaction(Invoke)
Count123.incre()contract Count123{
uint counts(0);function incre(){
counts = count + 1;
}}
狀態宣告
狀態宣告
contract bikeRenting {
address public owner;
address public currentRenter;
uint public expireTime;
…
}
• 型態 能見度 變數名稱 ( type visibility variable_name)
• type : bool, int, uint, address, mapping, bytes, string, struct
• visibility : public, private(default)
• public : 外界可以直接讀取其值(in contract) uint public totalCount = 3;(in console) mycontract.totalCount(); //3(in other contract) thecontract.totalCount() //3
• variable_name
狀態宣告
• Array in solidity :
• address[] owners; address[3] threeOwners;
• 支援 push : owners.push(address)
• delete— 將該位置的值變成 0 : delete owners[2]
• 缺點:長度不變,只將該位置變 0
• 每刪一個就將該位置後面的都往前移• 在某些情況下改用 mapping
狀態宣告
• mapping :
• mapping(typeA => typeB) map123;
• mapping(address => uint) deposits;
• deposits[0x123456789abcdef] = 10;
• 初始值、未指定過的,皆為零
狀態宣告
• 專有變數:• 單位: ether 、 finney 、 wei
• 時間: seconds 、 weeks 、 years 、 now
• now :目前時間• 更精確來說,是這一個區塊的時間,小心使用
狀態宣告
• 專有變數:• msg :這個 transaction 相關的資訊
• msg.sender :該 transaction 的發起人• msg.value :該 transaction 附帶的錢
• address 相關• address.balance :得知該 address 的餘額• address.send(amount) :送錢到該 address
狀態宣告
• 特殊函式:• throw
• 將該 transaction 做的改變還原,並沒收其附上的所有的gas
狀態宣告
初始化
初始化contract bikeRenting {
address public owner;address public currentRenter;uint public expireTime;uint public unitPrice;
function bikeRenting(uint _unitPrice){
owner = msg.sender;
currentRenter = 0x0;
expireTime = now;
unitPrice = _unitPrice;
}
}
• constructor :• 和 contract 名稱同名• 只會執行一次• 用來做合約剛跑起來所需要做的一次性工作• 非必需
初始化
函式
contract bikeRenting {address public owner;address public currentRenter;uint public expireTime;uint public unitPrice;function bikeRenting(uint _unitPrice){
…}
function rent() payable returns(bool){if(currentRenter != 0x0) return false;else{
if( (msg.value/1 ether) / unitPrice < 1) return false; else {
expireTime = now + 15 minutes * (msg.value/1 ether)/unitPrice; currentRenter = msg.sender; rentingRecord(msg.sender, now, (msg.value/1 ether)/unitPrice);
}}
}
}
目前是否有人租借
一單位多少時間檢查是否有給滿一單位的錢
函式
function functionName(parameter1, parameter2, …) returns(type) {
…
}
• parameters : bool a, uint b, address c, …
• returns :• 非必需• function foo() returns(uint, address, bool) {…}
• return (1, 0x0, true);
函式
• 函式的能見度( visibility ):• public(default)
• private :只有這個 contract
• internal :這個 contract 和繼承這個 contract 的 contract
• external :除了這個 contract 和繼承這個 contract 的contract之外,即 internal 的相反
• payable :函式是否可以收錢
函式
contract bikeRenting {address public owner;address public currentRenter;uint public expireTime;uint public unitPrice;function bikeRenting(uint _unitPrice){…}function rent() payable returns(bool){…}
function isInUse(bool ifWantToRent) payable returns(bool){if( expireTime > now ) return true;else{
currentRenter = 0x0;if( ifWantToRent == true) rent();
}}
}
檢查是否有人租借
ifWantToRent 由函式呼叫者給定,給 true 表示要順便租借,則繼續執行 rent() 函式
如果過期時間在未來,表示有人租借
函式
函式• 要確保 isInUse 這個函式的呼叫人有直接租借的權利,因為他付出成本來確認是否還在租借• 為什麼需要別人來確認?
• 因為智慧合約並不是活的,它只有在收到呼叫的時候才會動起來。• 所以即便有寫明 expireTime ,智慧合約也沒辦法在 expireTime 到的時候自動執行,需要外界觸發。
• 但靠外界觸發,如果同時有一個人以上呼叫租借的函式,區塊鏈沒辦法保證誰的 transaction 會先被執行。• 所以呼叫 isInUse 函式來檢查的人付出了成本,我們要確保檢查的人如果剛好要租借可以有最優先的順序。
contract bikeRenting {address public owner;address public currentRenter;uint public expireTime;uint public unitPrice;function bikeRenting(uint _unitPrice){…}function rent() payable returns(bool){…}function isInUse(bool ifWantToRent) payable returns(bool){…}
function collectMoney() {if( msg.sender == owner){
owner.send(this.balance) ;}
}
}
確認只有 owner 可以領錢
函式
執行的權限
執行的權限• 合約並沒有原生的限制函式呼叫者的設計,任何人都可以送
transaction 到合約,所以必須在函式內做權限的控制。• 用 msg.sender 和有權限的 address 進行比對function foo() {
if(msg.sender == owner){
…
}
}
重複性的動作
• modifier
• _;
• 填補 code
• 如果要使用 modifier : function bar() foo1() {…}
• 使用多個 modifier : function bar() foo1() foo2() foo3(){…}
重複性的動作
modifier foo1 { do_something _; or_do_something_here}
contract foo1 {
…
modifier ownerCheck { if(msg.sender == owner ) _;}function collectMoney() ownerCheck {
owner.send(this.balance) ;}
…
}
重複性的動作
contract modifierTest{modifier foo1(){
foo1before;_;foo1after;
}modifier foo2(){
foo2before;_;foo2after;
}function bar() foo1() foo2() {
bar;}
}
重複性的動作
呼叫 bar 執行順序:foo1beforefoo2beforebarfoo2afterfoo1after
由合約產生合約
由合約產生合約
contract foo{ … }
address newFooAddr = new foo();
• 回傳的是一個 address
• foo foo1 = foo(newFooAddr); ,回傳的是一個 contract
• 注意:產生 contract 的 gas通常很多,而這些 gas 都算在該次的transaction 上,所以當函式會產生新合約,記得給夠多的 gas
fallback 函式
fallback 函式
• 如果沒有執行任何一個合約裡的函式,則執行 fallback 函式• 函式名給錯、單純送錢
• 存在弱點• 執行的邏輯是由該合約的人決定
function () {…
}
可能危險
可能危險• throw when address.send() fails
• 所有目前執行完的結果都會被還原
for(uint i=0; i<investorsCount; i++) {
if( inverstors[i].send(100) == false )
throw;}
可能危險• throw when address.send() fails
• why address.send() fail?
• 1. out of gas
• supply enough gas
• 2. callstack
• 1024 layer
可能危險• throw when address.send() fails
• use a withdraw pattern
• still, this solution leaves the problems mentioned to the msg sender
function withdraw(amount) {if( balances[msg.sender] >=
amount ) {msg.sender.send(amount);
balances[msg.sender] -= amount;}
}
可能危險• shared state between external call and external callable
functionsfunction extCall() {…external_call();…if(shared_state) {
…}…
}
function extCallable(){do_something_on_shared_state…
}
呼叫其他合約的函式
該函式中又呼叫了我們合約的這個函式
可能危險• state corruption
• 1. sum of sizes of 1st and 2nd state variables are less than 256 bytes
• 2. first variable is not a signed interger or bytesXX typefunction extCall() {
uint32 a;uint32 b;function run() returns(uint32){
a--;return b;
}}
fixed after compiler version 0.4.4
Misc
• selfdestruct(recipient)
• 終結合約,將資料和程式碼清空,並將餘額轉給 recipient ( address )。• event : 將資料寫入 transaction receipt ,可用做紀錄或搜尋
• event paymentRecord(address indexed buyer, uint value)
• indexed屬性:寫入 topics中• 如果寫入的資料超過 32 byte ,則會改存資料的雜湊值
• transaction receipt :• data
• topics :搜尋時可以用此區域的值來當篩選條件
Misc
• contract inheritance : contract Final is most-base-like, …, most-derived {…}
Misc
TIPS
• use delete on array to delete all elements
• --vmdebug, --verbosity
• --targetgaslimit