此次選取的代碼是智能合約漏洞檢測及分析工具Oyente的源代碼。
一、Oyente
Oyente是melon.fund於2018年10月發布的一款為現有的以太坊智能合約開發人員構建的符號執行工具,以發現智能合約中潛在的安全漏洞。
開發語言:Python
工具類型:靜態分析工具
分析內容:EVM字節碼
工具原理:Oyente將需要分析的合約的字節碼和當前以太坊的全局狀態作為輸入,檢測合約是否存在安全問題,並向用戶輸入有問題的符號路徑。在這個過程中,使用Z3求解器來確定可滿足性。
模塊划分:Oyente遵循模塊化設計,由四個主要組件組成,分別是分別是CFGBuilder,Explorer,CoreAnalysis和Validator。
(1)CFGBuilder:構造合約的控制流圖,其中節點是基本執行塊,邊表示這些塊之間的執行跳轉。
(2)Explorer:主要模塊,象征性地執行合約。
(3)CoreAnalsis:Explorer的輸出將輸入CoreAnalsis,在其中我們實現針對漏洞的邏輯。
(4)Validator:在向用戶報告前,由Validator確認是否有誤報。
主要作用:(1)幫助開發人員編寫更好的合約 (2)防止用戶調用有問題的合約
局限性:可能會錯過嚴重違規、可能會產生誤報、也可能無法在現實合約中獲得足夠的代碼覆蓋率(在Parity錢包僅獲得20.2%的覆蓋率)
源代碼:https://github.com/melonproject/oyente
相關論文:Loi Luu, Duc-Hiep Chu, Hrishi Olickel, Prateek Saxena, Aquinas Hobor: Making Smart Contracts Smarter. IACR Cryptology ePrint Archive 2016: 633 (2016)
二、源代碼分析
(一)源代碼特點
1、目錄結構
圖1 源代碼目錄結構
做法:除了自動生成的文件外,該源代碼分成了三個文件夾:mic_utils、oyente、web,分別存儲接口相關代碼、oyente實現相關代碼和網頁相關代碼,結構簡明清晰,讓人一目了然。三個文件夾中各自的內容如下圖2-4所示。
圖2 misc_utils文件夾內容
圖3 oyente文件夾內容
圖4 web文件夾內容
2、命名規范
1 def ceil32(x): 2 return x if x % 32 == 0 else x + 32 - (x % 32) 3 4 def isSymbolic(value): 5 return not isinstance(value, six.integer_types) 6 7 def isReal(value): 8 return isinstance(value, six.integer_types) 9 10 def isAllReal(*args): 11 for element in args: 12 if isSymbolic(element): 13 return False 14 return True 15 16 def to_symbolic(number): 17 if isReal(number): 18 return BitVecVal(number, 256) 19 return number 20 21 def to_unsigned(number): 22 if number < 0: 23 return number + 2**256 24 return number 25 26 def to_signed(number): 27 if number > 2**(256 - 1): 28 return (2**(256) - number) * (-1) 29 else: 30 return number 31 32 def check_sat(solver, pop_if_exception=True): 33 try: 34 ret = solver.check() 35 if ret == unknown: 36 raise Z3Exception(solver.reason_unknown()) 37 except Exception as e: 38 if pop_if_exception: 39 solver.pop() 40 raise e 41 return ret
做法:
(1)變量名:由於Python中變量不用提前定義,此處直接使用小寫字母來表示變量
(2)函數名:使用了駝峰命名法(Camel-Case),即第一個單詞以小寫字母開始;第二個單詞的首字母大寫或每一個單詞的首字母都采用大寫字母;且使用了易於理解的命名,讓人見其形而知其意。
(二)符合代碼規范和風格一般要求的做法
1、組織結構和目錄結構都比較清晰
2、使用了編程中常用的駝峰命名法,命名比較規范且易於理解
3、嚴格使用縮進和大小寫來進行組織
(三)有悖於“代碼的簡潔、清晰、無歧義”基本原則的做法
全程沒有注釋,即使閱讀了工具相關的論文之后也不能理解代碼的具體含義和用途。
改進方式:在合適的地方進行注釋,有助於對代碼的理解和應用。
三、Python編碼規范(國內)
(一)格式上的編碼規范
行長度:每行不超過80個字符;不要用反斜杠連接行
以下情況除外:(1)長的導入模塊語句
(2)注釋里的URL
語句:通常每個語句應該獨占一行
導入格式:每個導入應該獨占一行
縮進:用4個空格來縮進代碼。絕對不可以使用Tab鍵,也不能將Tab和空格混用!!!
空行:頂級定義之間空兩行, 方法定義之間空一行。
空格:按照標准的排版規范來使用標點兩邊的空格
(1)括號內不能有空格
(2)逗號, 分號, 冒號前面不要加空格, 但在它們后面應該加空格(除了在行尾)
(3)參數列表, 索引或切片的左括號前不應加空格
(4)在二元操作符兩邊都加上一個空格, 比如賦值(=), 比較(==, <, >, !=, <>, <=, >=, in, not in, is, is not), 布爾(and, or, not);算術運算符自行把握,但要注意保持一致
(5)當'='用於指示關鍵字參數或默認參數值時, 不要在其兩側使用空格
分號:不要在行尾加分號, 也不要用分號將兩條命令放在同一行。
括號:寧缺毋濫的使用括號。
除非是用於實現行連接, 否則不要在返回語句或條件語句中使用括號; 不過在元組兩邊使用括號是可以的
(二)命名規范
應該避免的名稱:
(1)單字符名稱, 除了計數器和迭代器.
(2)包/模塊名中的連字符(-)
(3)雙下划線開頭並結尾的名稱(Python保留, 例如__init__)
命名約定:
(1)所謂"內部(Internal)"表示僅模塊內可用, 或者, 在類內是保護或私有的.
(2)用單下划線(_)開頭表示模塊變量或函數是protected的(使用import * from時不會包含).
(3)用雙下划線(__)開頭的實例變量或方法表示類內私有.
(4)將相關的類和頂級函數放在同一個模塊里. 不像Java, 沒必要限制一個類一個模塊.
(5)對類名使用大寫字母開頭的單詞(如CapWords, 即Pascal風格), 但是模塊名應該用小寫加下划線的方式(如lower_with_under.py). 盡管已經有很多現存的模塊使用類似於CapWords.py這樣的命名, 但現在已經不鼓勵這樣做, 因為如果模塊名碰巧和類名一致, 這會讓人困擾
(三)函數和方法
【下文所指的函數,包括函數, 方法以及生成器】
一個函數必須要有文檔字符串, 除非它滿足以下條件:外部不可見、非常短小、簡單明了
文檔字符串:
(1)應該包含函數做什么, 以及輸入和輸出的詳細描述
(2)應該提供足夠的信息, 當別人編寫代碼調用該函數時, 只要看文檔字符串就可以了
(3)對於復雜的代碼, 在代碼旁邊加注釋會比使用文檔字符串更有意義
關於函數的幾個方面(如下所示:Args、Returns、Raises)應該在特定的小節中進行描述記錄, 每節應該以一個標題行開始, 標題行以冒號結尾;除標題行外,,其他內容應被縮進2個空格。
- Args:列出每個參數的名字, 並在名字后使用一個冒號和一個空格, 分隔對該參數的描述.如果描述太長超過了單行80字符,用2或者4個空格的懸掛縮進(與文件其他部分保持一致)。 描述應該包括所需的類型和含義, 如果一個函數接受*foo(可變長度參數列表)或者**bar (任意關鍵字參數), 應該詳細列出*foo和**bar。
- Returns:(或者 Yields: 用於生成器)描述返回值的類型和語義。 如果函數返回None, 這一部分可以省略。
- Raises:列出與接口有關的所有異常。
1 def fetch_bigtable_rows(big_table, keys, other_silly_variable=None): 2 """Fetches rows from a Bigtable. 3 4 Retrieves rows pertaining to the given keys from the Table instance 5 represented by big_table. Silly things may happen if 6 other_silly_variable is not None. 7 8 Args: 9 big_table: An open Bigtable Table instance. 10 keys: A sequence of strings representing the key of each table row 11 to fetch. 12 other_silly_variable: Another optional variable, that has a much 13 longer name than the other args, and which does nothing. 14 15 Returns: 16 A dict mapping keys to the corresponding table row data 17 fetched. Each row is represented as a tuple of strings. For 18 example: 19 20 {'Serak': ('Rigel VII', 'Preparer'), 21 'Zim': ('Irk', 'Invader'), 22 'Lrrr': ('Omicron Persei 8', 'Emperor')} 23 24 If a key from the keys argument is missing from the dictionary, 25 then that row was not found in the table. 26 27 Raises: 28 IOError: An error occurred accessing the bigtable.Table object. 29 """ 30 pass