从 Demo 中学习 Solidity
從 Demo 中學習 Solidity [注解譯文]
前
(全文參考) 
 Solidity官方文檔
以太坊白皮書_ZH
以太坊白皮書_EN
發現網上的資料太過瑣碎, 驚奇的發現官方有詳細的教程, 和例子.
雖說, 是E文, 自己慢慢啃吧,也算是半學習半翻譯吧
分布式數據庫
對說區塊鏈一定形式上是分布式數據庫的理解 引用
pragma solidity ^0.4.0;contract SimpleStorage {uint storedData;function set(uint x) {storedData = x;}function get() constant returns (uint) {return storedData;} }第一行告訴該合約用的是0.4.0版本的solidity編寫,并且這些代碼具有向上兼容性。保證不會在不同solidity編譯版本下編譯會出現不同的行為。
從Solidity角度來看,合約就是存在于以太坊區塊鏈中的一個特定地址中的代碼和數據集合。uint storedData 聲明了一個類型為 uint(256位的無符號整型)的變量,變量名稱為 storedData。你可以把它想象為數據庫中的一個字段,該字段是可以被數據庫中的方法進行查詢,修改。在以太坊中,這個字段是屬于一個合約字段。在這個例子中,該變量可以通過提供的get,set方法進行獲取或是修改。 在Solidity中,訪問一個狀態變量是不需要通過this來引用的。
這個合約很簡單,只是允許以太坊上的任何人來存儲一個數據到某個節點,同時把這個操作發布到以太坊中,當然,以太坊上的其他節點同樣可以通過調用set方法來修改你已經存儲好的值。雖然有被修改,但是對該值操作的任何歷史記錄都是保存在以太坊中的。不用擔心你的存儲記錄或是修改記錄會丟失。后面我們會將到如何對合約進行限制,只允許你一個人修改這個數據
子代幣例程
這個例程是用于實現一個簡單的加密貨幣, 這里種幣的無中生有是可能的. 但是,只有它的合約創建人, 才可以實現這個幣的簽發. 不過還好發送幣到其他的地址, 不需要注冊賬戶, 只是一個 ETH 密鑰對就好
pragma solidity ^0.4.0;contract Coin {// The keyword "public" makes those variables// readable from outside.address public minter;mapping (address => uint) public balances;// Events allow light clients to react on// changes efficiently.event Sent(address from, address to, uint amount);// This is the constructor whose code is// run only when the contract is created.function Coin() public {minter = msg.sender;}function mint(address receiver, uint amount) public {if (msg.sender != minter) return;balances[receiver] += amount;}function send(address receiver, uint amount) public {if (balances[msg.sender] < amount) return;balances[msg.sender] -= amount;balances[receiver] += amount;Sent(msg.sender, receiver, amount);} }這個合約包含著一些新的概念, 下面一個個的明確一下
地址變量
address public minter;這里聲明了一個地址類型的變量, 其訪問類型是Public. address 類型是一個沒有算數操作符的160位的值. 它用來存儲合約地址,或者外部用戶的密鑰對. public 關鍵字自動的使得該變量是可以被外部訪問的. 沒有 Public 的話,其他合約是無法訪問這個變量的
function minter() returns (address) { return minter; }例如執行這個函數. 當然, 添加這樣的一個函數可能是沒用的, 因為他的名稱和我們之前已經聲明的變量一樣了,不過編譯器將會支出問題, 不慌…
映射(map)
下面這一行
mapping (address => uint) public balances;也是創建了一個公有變量, 但是這個是一種比 address 更復雜的數據類型. 這種類型建立了 address 和 unsigned int 的映射關系.
可以想象是一個 哈希表 被初始化為, 每一個已經存在的鍵,都和一個字節數據全是零的值相對應. 他們是成對出現的, 所以不可能得到全部是鍵, 或者全部是值的 一個list. (….這段好鬼復雜,后面看不懂了) 
 在這種情況下, getter 函數 將會被Public這個關鍵字自動創建, 看著和下面類似
正如你所見, 你可以使用這種方法 很容易的查詢一個賬戶的余額
事件
event Sent(address from, address to, uint amount);正如event這個名字一樣, 這行創建了一個事件 , 而他會在最后一行的 Send 函數觸發. 當這些事件在鏈上被觸發的時候, 用戶接口(或者服務器應用)可以監聽這些事件而并不需要花費很多(GAS) .
當事件被觸發的呼喚, 監聽這同時會收到, from , to , amount 這幾個參數. 這些參數使得我們可以更容易的追蹤到這些交易.
為了監聽到這些事件, 需要使用以下代碼
Coin.Sent().watch({}, '', function(error, result) {if (!error) {console.log("Coin transfer: " + result.args.amount +" coins were sent from " + result.args.from +" to " + result.args.to + ".");console.log("Balances now:\n" +"Sender: " + Coin.balances.call(result.args.from) +"Receiver: " + Coin.balances.call(result.args.to));} }) // watch 右括號others
注意自動生成的balance 函數式怎么被用戶接口調用的
這在這個結構中特定的 coin 函數,是在這個合約創建的時候執行的, 而不可以在以后調用. 
 所以這個函數用于永久的保存,合約部分屬性. 通過msg 這個全局變量( 連同一起保存的 有 tx and block). 
 例如: 無論從哪里調用合約函數, msg.Sender 即合約創建者的地址 
最終的, 合約中真正完成功能的,并且可以被其他合約和用戶調用的是 mint 和 send 這兩個函數.
如果 mint 被非合約創建者(地址,用戶) ,什么都不會發生[合約中代碼可見, 直接返回了]. 
 另一方面, send 可以被任何人(只要之前擁有這個子幣的) 用于發送到其他的地址上去. 
注意 ,如果你使用這份合約來發送代幣到其他的地址, 你通過區塊瀏覽器是什么也看不到的, 因為事實上你所發送的代幣,和余額的變化, 只是儲存在特定的代幣合約的數據字段, 通過 事件的使用 ,就可以相當容易的創建一個區塊瀏覽器, 來跟蹤這個新的代幣的余額和交易了 
 (事實上 通過第三方網站ETHSCAN好像可以添加新的token)
區塊鏈基礎(Blockchain Basics)
區塊鏈其實作為一個概念理解起來編程實現并不難. 因為大多數的難題(complication)(挖礦, 哈希, 橢圓曲線加密, 點對點網絡等) 都只是提供了一些概念和特性. 一旦你了解了這些特性, 就不用去理解其深層次的具體實現, 比如你使用 亞馬遜的 AWS 云, 完全不用了解他的內部網絡實現對吧?
交易(Transaction)
區塊鏈是一個全球的 共享交易數據庫. 這就意味著, 每個人加入了這個網絡,就可以讀取整個網絡的數據. 這樣的話, 你如果需要改變一點內容, 那你就需要創建一個被其他所有人接受的所謂的交易(transaction). 交易這個詞暗示著要不就是沒有,要不就是被全部應用(全部節點記錄).而且, 當你的交易被記錄在數據庫中了,就沒有人能改變它.舉個例子, 想象有一個表, 列出了所有的用戶的一直數字貨幣的余額. 如果有一個轉賬請求, 數據庫實際上,就是從這一個賬戶的余額減去(Subtract)值, 另一個賬戶增加值.如果因為一些原因, 在另一賬戶增加余額不可行, 那么原賬戶也是不會改變的而且, 一次交易都會被發送者進行數字簽名(RSA 體系下,即用自己的秘鑰去加密內容,別人用公鑰驗證簽名).這就簡單的保證了, 合法的數據庫修改. 在數字貨幣中這個簡單的檢查,確保了用于了賬戶的秘鑰(私鑰)的人,才能從賬戶轉賬區塊
在比特幣體系中, 一個主要的要克服的障礙(obstacle)是 雙花攻擊(Double-spend).發生在當一個當網絡中的兩筆交易想同時清空同一個賬號的余額會發生什么?也許一個沖突?(這里可能是指的同時的進行a 對 b , a對 c 的轉賬)理論上(abstract)你是不必要關心這個問題的. 你的交易將會有一個特定的順序, 這些交易將會被打包(bundle)在一個叫做區塊的東西里, 然后區塊將會被處理(execute)和分發到所有的在線節點上. 如果兩個交易互相沖突(contradict), 后來的那個將會被節點拒絕, 并且不會成為節點的一部分這些區塊按時間的先后組成了一個線性的序列, 所有這也是 `區塊鏈` 這個詞的出處. 新的區塊被加入鏈中的時間間隔(intervals)差別不大, 以以太坊為例, 大約每17秒一個. 作為"序列排序機"(`order selection mechanism`)的一部分(被稱之為挖礦的行為), 可能發生,區塊回滾(reverted). 但是僅僅會在區塊的鏈尾(tip),. 隨著更多的區塊產生, 回滾的可能性也越小. 所以, 你的交易數據可能會被回滾, 甚至從區塊鏈中移除(..這點沒懂, 是不是叔塊和重鏈的原因?)以太坊虛擬機(The Ethereum Virtual Machine)
概述(overview)
以太坊虛擬機(EVM) 是以太坊的智能合約的運行時環境(runtime environment).它不僅僅是一個沙箱, 并可以實現代碼運行和網絡, 文件系統, 其他進程 的完全隔離. 智能合約之間甚至也可以互相隔離.賬戶(Account)
在以太坊中有兩種賬戶共享同一地址空間,- 外部賬戶 被公鑰密鑰對所控制
 合約賬戶 被賬號存儲的代碼所控制
外部賬戶 是被公鑰所確定的, 而合約賬戶是在合約創建時確定, (合約賬戶產生于(derive from) 合約創建者的地址,而且創建者在其中轉入一些余額叫做nonce)
不論是否賬戶儲存了代碼, 這兩種類型的賬戶都是被EVM 等同的對待.
交易(Transactions)
一次交易,是一個消息,從一個賬戶發送到另一個賬戶(可能是自身, 或者特殊的黑洞賬戶(zero-account), 見下), 消息可以包含二進制數據(payload)和ETHer(以太幣)如果目標賬戶包含代碼, 并且代碼被執行,那么這些數據將會被視為執行的輸入數據如果目標賬戶是零賬戶(zero-account)(地址是 0 的賬戶), 這個交易將會創建一個新的合約. 正如上面提到的, 新的合約地址不是 0 , 而是從創建者的賬戶的交易發送產生. 這個合約所包含的載荷(payload)被轉換為EVM的操作碼, 并且執行. 執行的輸出將會永久的保存在合約的代碼里.這就意味著為了創建一個合約, 你不需要發送真正的代碼到合約, 使用的僅僅是這個合約執行的返回值 
 (這個感覺很重要, 因為之前部署的命令沒看懂, 這個就想到于保存了句柄對吧?) 
 (個人理解, 就是合約創建時候的結果將會執行,并保存)
Gas(特定詞匯)
每一次交易都會包含一定量的GAS.其目的是限制轉賬或者合約執行的工作量. 當EVM 執行交易的時候, GAS會被按照一定規則的消耗(deplete)gas的值是可以被交易的發起者所設置的, 發起者需要支付 ` gas_price * gas ` 的量.如果在執行交易后有GAS剩余, 那么也將會返還到發起者的賬戶上.如果gas被耗盡,(等同于(i.e.) it is negative), 一個 `out-of-gas` 的異常將會被拋出, 并且由此次交易發生的所有改變將會被回滾.外存, 內存,和棧
每一個用戶,都會有自己固定的內存空間, 稱之為存儲(storge). 儲存是一個 256bit映射256bit 的鍵值對.通過合約去列舉存儲是不可能的, 因為它所消耗的代價是很大的.一個合約只能做到,讀或者寫它自己的存儲區域內容 第二存儲區是被稱為內存的地方, 每一個合約每一次消息調用都擁有一個,新的被清空的實例(instance).內存是線性的,并可以做到字節等級的尋址, 但是讀取位寬限制在 256bit ,同時,寫入寬度是 8b 或者 256b.內存拓展是以字長為單位(256b). 當訪問一個之前不可達(untouched)的內存字的時候, 一定量的gas 將會被消耗.內存增長和gas消耗是呈指數的關系的.EVM 不是基于寄存器的, 而是基于棧的 機器, 所以所有的計算是通過一個稱之為棧(stack)的東西執行的.其最大值是1024個元素(element),每個元素256bit.如棧的結構, 只可被頂端訪問, 并且遵循如下規則: 允許拷貝最上面的16個元素中的一個到棧頂或是棧頂和它下面的16個元素中的一個進行交換。所有其他操作會從棧中取出兩個(有可能是1個,多個,取決于操作)元素,把操作結果在放回棧中。當然也有可能把棧中元素放入到存儲或是主存中,但是不可能在沒有移除上層元素的時候,隨意訪問下層元素。指令集(instruction Set)
EVM指令集保留最小序列,用于避免不正確不完整的不一致問題. 所有的指令基于基礎數據類型 ,256b word. 可以實現常規的算術, 位 及邏輯運算. 條件和無條件跳轉也是OK的.而且(furthermore),合約可以訪問當前塊的相關屬性, 像是 序號(number) 時間戳(timestamp)消息調用(message calls)
合約可以調用其他的合約 或者發送以太幣到其他的賬戶通過消息調用的功能.消息調用和交易類似, 一樣的擁有 源(source), 目標(target), 載荷(payload), 以太幣, gas, 和返回數據.事實上, 每一次交易都是由頂層的消息調用,繼而創建更多的調用.一個合約可以定義內部消息調用需要消耗多少gas,多少gas需要被保留。如果在內部消息調用中出現out-of-gas異常,合約會被通知,會在棧里用一個錯誤值來標記。這種情況只是這次調用的gas被消耗完。在Solidity,這種情況下調用合約會引起一個人為異常,這種異常會拋出棧的信息。上面提到,調用合約會被分配到一個新的,并且是清空的主存,并能訪問調用的負載。調用負載時被稱為calldata的一個獨立區域。調用結束后,返回一個存儲在調用主存空間里的數據。這個存儲空間是被調用者預先分配好的。調用限制的深度為1024.對于更加復雜的操作,我們更傾向于使用循環而不是遞歸。代理調用/ 代碼調用和庫(Delegatecall / Callcode and Libraries)
存在一種特殊的消息調用,叫做代理調用。除了目標地址的代碼在調用方的上下文中被執行,而且msg.sender和msg.value不會改變他們的值,其他都和消息調用一樣。這就意味著合約可以在運行時動態的加載其他地址的代碼。存儲,當前地址,余額都和調用合約有關系。只有代碼是從被調用方中獲取。這就使得我們可以在Solidity中使用庫。比如為了實現復雜的數據結構,可重用的代碼可以應用于合約存儲中。
日志(logs)
我們可以把數據存儲在一個特殊索引的數據結構中。這個結構映射到區塊層面的各個地方。為了實現這個事件,在Solidity把這個特性稱為日志。合約在被創建出來后是不可以訪問日志數據的。但是他們可以從區塊鏈外面有效的訪問這些數據。因為日志的部分數據是存儲在bloom filters上。我們可以用有效并且安全加密的方式來查詢這些數據。即使不用下載整個區塊鏈數據(輕客戶端)也能找到這些日志
創建(create)
合約可以通過特殊的指令來創建其他合約。這些創建調用指令和普通的消息調用唯一區別是:負載數據被執行,結果作為代碼被存儲,調用者在棧里收到了新合約的地址。
自毀(self-destruct)
從區塊鏈中移除代碼的唯一方法是合約在它的地址上執行了selfdestruct操作。這個賬號下剩余的以太幣會發送給指定的目標,存儲和代碼從棧中刪除。
后記
終于算是差不多的自己翻譯了第一篇文章, 翻譯的同事夜場學到了不少東西, 具體后面的EVM的架構理解還是需要自己深入研究一下, 翻譯質量很差,我自己都快看不懂了...自己算是動手翻譯不易,要是有幫助或者支持的話,扔個硬幣吧(碼字不易QVQ)
ADDR: 0xf1aF694c9A24963110A334B5cede1f1BD0047041 
 (ETH)
總結
以上是生活随笔為你收集整理的从 Demo 中学习 Solidity的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 第二个智能合约
 - 下一篇: 通过例子学Solidity[注释翻译]