🎯 什麼情境該想到我

當你想理解或撰寫「部署到區塊鏈、被交易觸發後自動執行且公開可驗證的程式」時。

本卡只涵蓋 Ch.7 “Smart Contracts and Solidity”(PDF p.254–310)——合約的定義、生命週期、Solidity 的基本語言構件。安全反模式(重入、溢位、權限、搶跑…)全部在 工具-智能合約安全反模式(Ch.9),本卡不重複。

⚙️ 怎麼用(心法與注意)

1. 先把「smart contract」這個名字的誤導拆掉

Ch.7 “What Is a Smart Contract?”(PDF p.255)直接說這個詞在以太坊的脈絡下是個誤稱

“In the 1990s, cryptographer Nick Szabo coined the term and defined it as ‘a set of promises, specified in digital form, including protocols within which the parties perform on the other promises.’ … In the context of Ethereum, the term is actually a bit of a misnomer, given that Ethereum smart contracts are neither smart nor legal contracts, but the term has stuck.”
—— 譯文:1990 年代,密碼學家 Nick Szabo 造出這個詞,把它定義為「一組以數位形式指定的承諾,包含各方履行其他承諾所依循的協定」。……在以太坊的脈絡下,這個詞其實有點名不副實,因為以太坊的智能合約既不聰明、也不是法律合約,但這個名字就這麼沿用下來了。

書自己採用的定義是:

“…immutable computer programs that run deterministically in the context of an Ethereum Virtual Machine as part of the Ethereum network protocol — i.e., on the decentralized Ethereum world computer.”
—— 譯文:……在以太坊虛擬機的脈絡中、作為以太坊網路協定的一部分而決定性地執行的不可變電腦程式——也就是跑在去中心化的以太坊世界電腦上。

書把這句定義拆成五個字(p.255–256),這五個字就是這張卡的骨架:

  • Computer programs(電腦程式)——“The word ‘contract’ has no legal meaning in this context.”(譯文:「合約」這個詞在這裡沒有任何法律意義。)
  • Immutable(不可變)——一經部署,程式碼就改不了;唯一的修改方式是部署一個新的實例
  • Deterministic(決定性)——給定觸發它的那筆交易脈絡與當下的鏈上狀態,任何人執行的結果都相同。
  • EVM context(執行脈絡極窄)——合約只能取用自己的 state、呼叫它的那筆交易的脈絡、以及最近幾個區塊的部分資訊。
  • Decentralized world computer(去中心化世界電腦)——EVM 在每個節點各跑一份,但因為初始狀態相同、產出狀態也相同,整個系統對外表現得像一台電腦。

2. 合約帳戶沒有私鑰,它「自己擁有自己」

Ch.7 章首(PDF p.254)與 “Life Cycle of a Smart Contract”(p.257):合約帳戶同時擁有程式碼與資料儲存,而 EOA 兩者皆無;合約帳戶沒有私鑰,因此以其合約程式碼所預先規定的方式「控制自己」。

“As the contract creator, you don’t get any special privileges at the protocol level (although you can explicitly code them into the smart contract). You certainly don’t receive the private key for the contract account, which in fact does not exist — we can say that smart contract accounts own themselves.”
—— 譯文:身為合約的建立者,你在協定層面上不會拿到任何特權(不過你可以把特權明確寫進合約程式碼裡)。你當然也不會拿到合約帳戶的私鑰——那把私鑰根本不存在;我們可以說,智能合約帳戶擁有它自己

實務含意:你想要的任何管理員權限,都得自己寫進程式碼(見下面的 onlyOwner)。

3. 合約不會自己跑——一定是交易觸發的

“Life Cycle of a Smart Contract”(PDF p.257):

“Importantly, contracts only run if they are called by a transaction. … Contracts never run ‘on their own’ or ‘in the background.’ Contracts effectively lie dormant until a transaction triggers execution, either directly or indirectly as part of a chain of contract calls.”
—— 譯文:重要的是,合約只有在被交易呼叫時才會執行。……合約絕不會「自己跑」或「在背景跑」。合約實際上是休眠的,直到某筆交易直接或間接(作為一連串合約呼叫的一環)觸發它為止。

同一段還給了三個常被忽略的性質:

  • 任何執行鏈的第一個合約,一定是被某個 EOA 發起的交易呼叫的。
  • 世界電腦可視為單執行緒機器,合約不會「平行」執行。
  • 交易是原子的:不管它呼叫了幾個合約,全部成功才寫入狀態;中途出錯則所有狀態變更「回滾」(rolled back),彷彿沒發生過——但失敗仍被記錄,且 gas 照扣

4. 「不可變」是真的;「刪不掉」是錯的

這是舊版這張卡最需要修正的地方。程式碼確實改不了,但合約可以被刪除——前提是作者事先把 selfdestruct 寫進去(p.258、p.283–284):

“Note that you must explicitly add this command to your contract if you want it to be deletable — this is the only way a contract can be deleted, and it is not present by default. In this way, users of a contract who might rely on a contract being there forever can be certain that a contract can’t be deleted if it doesn’t contain a SELFDESTRUCT opcode.”
—— 譯文:注意,如果你要讓合約可被刪除,必須明確把這道指令加進合約——這是刪除合約的唯一途徑,而且它預設不存在。如此一來,那些指望合約永遠存在的使用者,就能確定:只要合約裡沒有 SELFDESTRUCT opcode,它就刪不掉

補充幾點:SELFDESTRUCT(舊名 SUICIDE)會把合約的程式碼與 storage 從該位址移除,留下一個空帳戶;之後送進去的交易不會執行任何程式碼。這個操作耗「負 gas」(gas 退費),用來獎勵釋放節點的儲存資源。刪除不會抹掉該合約過去的交易歷史,因為區塊鏈本身是不可變的。selfdestruct(recipient) 要帶一個位址參數,用來接收合約剩餘的 ether。

5. 完整生命週期

把 p.257–258 與 p.283–284 串起來:

  1. 用高階語言(多半是 Solidity)寫 → 編譯成 EVM bytecode(solc --bin)。
  2. 用一筆合約建立交易部署:送到特殊的建立位址 0x0
  3. 合約位址由建立者帳戶 + nonce 推導而來,不是隨機的。
  4. 若有 constructor,它在建立交易中執行一次,用來初始化狀態,之後即被丟棄
  5. 此後合約休眠,等交易呼叫;ABI + 部署位址就是外界互動所需的全部(p.269:“All that is needed for an application to interact with a contract is an ABI and the address where the contract has been deployed.”)。
  6. 生命週期的另一端是 SELFDESTRUCT——如果作者寫了的話。

⚠️ 建構子在 v0.4.21 以前是「與合約同名的函式」,書明講這種寫法會出事:合約改名或拼錯,它就退化成一個任何人都能呼叫的普通函式,第三方可以在部署後把自己設成 owner 劫持合約。v0.4.22 起改用不具名的 constructor 關鍵字(p.283–284)。案例細節見 工具-智能合約安全反模式

6. 函式的兩組關鍵字:誰能呼叫 vs. 能做什麼

“Functions”(PDF p.280–282)給的宣告式:

function FunctionName([parameters]) {public|private|internal|external}
[pure|constant|view|payable] [modifiers] [returns (return types)]

第一組是可見性public(預設,合約 / EOA / 自身皆可呼叫)、external(像 public,但合約內部要呼叫得加 this.)、internal(僅合約內與繼承者)、private(像 internal,但繼承者也不能呼叫)。

這裡有一句必須記住的話(p.281):

“Keep in mind that the terms internal and private are somewhat misleading. Any function or data inside a contract is always visible on the public blockchain, meaning that anyone can see the code or data. The keywords described here only affect how and when a function can be called.”
—— 譯文:請記住,internalprivate 這兩個詞有點誤導。合約內的任何函式或資料在公開區塊鏈上永遠是可見的,意思是任何人都能看到程式碼或資料。這裡描述的關鍵字只影響一個函式如何、以及何時可以被呼叫。

第二組決定行為

  • viewconstant 是即將淘汰的別名)——承諾不修改任何狀態。書寫作當下編譯器只發警告、不強制,預期在 v0.5 才成為強制。
  • pure——既不讀也不寫 storage,只吃參數、吐回傳值;目的是鼓勵無副作用的宣告式風格。
  • payable——能接受入金;沒標 payable 的函式會拒收 ether(例外:coinbase 付款與 SELFDESTRUCT 繼承的 ether 仍會進來,因為那些付款本來就不執行程式碼)。

另外,每個合約可以有一個不具名的 fallback function,在沒有指名任何函式時被呼叫;它不能有參數、也不能有回傳值。

7. Function modifier:存取控制的基本樣式

“Function Modifiers”(PDF p.287–288)。_; 是佔位符,會被「被修飾函式」的程式碼替換掉,等於把 modifier 包在函式外面

modifier onlyOwner {
    require(msg.sender == owner);
    _;
}
 
function destroy() public onlyOwner {
    selfdestruct(owner);
}

“Function modifiers are an extremely useful tool because they allow us to write preconditions for functions and apply them consistently, making the code easier to read and, as a result, easier to audit for security.”
—— 譯文:函式修飾詞是極其有用的工具,因為它讓我們能為函式寫下前置條件並一致地套用,使程式碼更易讀,因而也更容易做安全稽核

書順帶提醒:判斷 owner 時不要用 tx.origin——那會讓惡意合約在你不知情下摧毀你的合約(p.286)。「為什麼」在 Ch.9,見 工具-智能合約安全反模式

書接著用繼承(contract Child is Parent,支援多重繼承)把 owned / mortal 抽成基底合約,理由同樣是模組化與可稽核性(p.289–290)。

8. 錯誤處理:require 守輸入,assert 守內部

“Error Handling (assert, require, revert)“(PDF p.291–292):出錯時所有狀態變更會沿著整條呼叫鏈回滾,這正是交易原子性的來源。慣例是——

“By convention, assert is used when the outcome is expected to be true, meaning that we use assert to test internal conditions. By comparison, require is used when testing inputs (such as function arguments or transaction fields), setting our expectations for those conditions.”
—— 譯文:依慣例,assert 用在「結果理應為真」的情況,也就是用它來檢驗內部條件;相對地,require 用來檢驗輸入(例如函式參數或交易欄位),為那些條件設定我們的預期。

throw 已淘汰,改用 revert。v0.4.22 起 require / revert 可以帶一段錯誤訊息,會記進交易 log。書明白指出這是個取捨:多寫檢查會稍微增加 gas 消耗,測試網合約值得多報錯,主網合約可能選擇省 gas(p.292)。

9. Events:合約唯一的對外廣播管道

“Events”(PDF p.293–294):交易完成(成功或失敗)會產生 transaction receipt,其中的 log entries 記錄執行期間發生的事,而 events 就是產生這些 log 的 Solidity 高階物件

“Events are especially useful for light clients and DApp services, which can ‘watch’ for specific events and report them to the user interface, or make a change in the state of the application to reflect an event in an underlying contract.”
—— 譯文:Events 對輕節點與 DApp 服務特別有用:它們可以「監看」特定事件,回報到使用者介面,或改變應用程式的狀態以反映底層合約中發生的事。

用法:event Withdrawal(address indexed to, uint amount); 宣告,用 emit Withdrawal(msg.sender, withdraw_amount); 發出。參數前加 indexed 會讓該值進入可搜尋/過濾的索引表——設計事件時就要先想好前端要用什麼條件查

10. Gas 是寫法的約束條件,不是事後才煩惱的事

“Gas Considerations”(PDF p.304–307)。超過 gas limit 時的三件事:拋出 out of gas 例外 → 狀態回滾到執行前 → 付出去的 ether 當作交易手續費,不退。因為 gas 由發起交易的使用者付,高 gas 成本的函式會勸退使用者,所以壓低 gas 成本是寫合約的人的責任。書給兩條具體戒律:

  • Avoid Dynamically Sized Arrays(避免動態大小陣列)——任何走訪動態陣列的迴圈都有 gas 用爆的風險,可能在找到答案前就耗盡,白花時間與 ether 卻毫無結果。
  • Avoid Calls to Other Contracts(避免呼叫其他合約)——對方函式的 gas 成本未知就有 OOG 風險。並且:“Avoid using libraries that are not well tested and broadly used. The less scrutiny a library has received from other programmers, the greater the risk of using it.”(譯文:避免使用未經充分測試、未被廣泛使用的函式庫。一個函式庫受到其他程式設計者的檢視越少,用它的風險就越大。)

估算方式是 contract.myMethod.estimateGas(...)。書提醒這只是估計——EVM 是圖靈完備的,同一個函式不同呼叫路徑的 gas 可能天差地遠。書的建議是把評估函式 gas 成本納入開發流程,避免部署到主網時才驚訝。細節見 工具-Gas與EVM

11. 型別上的兩個小習慣

“Data Types”(PDF p.273–274):uint 不帶尺寸後綴時預設是 256 位元,以匹配 EVM 的字長。書示範的可讀性改善很值得抄:用單位後綴取代裸數字——

require(withdraw_amount <= 100000000000000000);   // 不易讀
require(withdraw_amount <= 0.1 ether);            // 好讀

時間單位(seconds / minutes / hours / days)與 ether 單位(wei / finney / szabo / ether)都能當後綴用。

12. 呼叫其他合約:三種方式,風險遞增

“Calling Other Contracts”(PDF p.297–300):

“When writing smart contracts, you must keep in mind that while you may mostly expect to be dealing with EOAs, there is nothing to stop arbitrarily complex and perhaps malign contracts from calling into and being called by your code.”
—— 譯文:寫智能合約時你必須謹記:雖然你多半預期打交道的對象是 EOA,但沒有任何機制能阻止任意複雜、可能懷有惡意的合約呼叫你的程式、或被你的程式呼叫

  • new 自己建立實例——最安全,因為你確知它的介面與行為。
  • 把既有位址轉型成合約型別——風險高得多;那個位址上的 withdraw 可能跟你宣告的完全不是同一回事。
  • 低階 call / delegatecall——最靈活也最危險,等於在合約脈絡裡構造一筆原始交易;書在這裡直接把讀者指向 Ch.9 的重入攻擊。delegatecall 的差別是不改變 msg 脈絡msg.sender 維持原樣),常用來呼叫函式庫,讓別處的程式碼操作自己的 storage。

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意

  • 「合約」在這裡沒有法律意義。 書明說 “The word ‘contract’ has no legal meaning in this context.”——別把它當成一份可以上法院的契約,它就是一段照字面執行的程式。
  • 不可變 + 管錢 = 容錯空間極小。 書在講語言選擇時的原話是 “In smart contracts, bugs literally cost money.”(譯文:在智能合約裡,bug 是真的會花掉錢。)——這也是為什麼宣告式語言在合約領域比在一般軟體重要得多,即使實際上最流行的 Solidity 是命令式的(p.260–261,“Programmers, like most humans, resist change!”)。
  • 本卡反映的是 2018 年、Solidity v0.4.24 時代的內容(書自述撰寫當時版本為 0.4.24,並說 0.5 即將發布)。範例中的 this.balance、同名建構子、throwcallcode 等寫法都屬於那個年代。書出版之後的語言演進、工具鏈與部署慣例不在本卡範圍內,實際動工前請回查現行官方文件。
  • 合約安全的完整清單不在這裡。 重入、算術溢位、預設可見性、tx.origin 授權、搶跑等等都在 Ch.9,見 工具-智能合約安全反模式

🔗 相關工具

  • 工具-智能合約安全反模式 —— 同一本書 Ch.9;本卡刻意不寫的安全細節(重入、溢位、權限、DELEGATECALL、搶跑…)全在那張,寫任何管錢的合約前必讀
  • 工具-以太坊帳戶與交易 —— 合約帳戶與 EOA 的差別、以及「合約只有被交易呼叫才會執行」的另一半:交易本身長什麼樣
  • 工具-Gas與EVM —— 本卡第 10 點的展開:gas 怎麼計價、EVM 怎麼執行 bytecode
  • 工具-預言機 —— 合約的執行脈絡極窄(只看得到自己的 state 與最近的區塊),要拿鏈外資料就得靠預言機
  • 工具-代幣標準與DApp —— 合約最常見的實際用途,看代幣/NFT/DApp 怎麼用合約組起來
  • 工具-用例外處理錯誤 —— 本卡第 8 點(require 守輸入、assert 守內部)的通用工程版本
  • 工具-防禦式編程 —— 「假設輸入都是惡意的」那套基本功;合約版的展開在 Ch.9 的 Security Best Practices
  • 精通以太坊 —— 本卡來源(Ch.7 “Smart Contracts and Solidity”,PDF p.254–310)