藉助 Solidity 來識別智慧合約的調配模式

買賣虛擬貨幣
由於所有已部署合約都有 24KB 的硬限制,所以除了少數極其簡單的應用程式外,其他所有的 Ethereum 應用都是由多個智慧合約組成的。如何才能確保各智慧合約間的安全協作呢?在將程式碼分解為多個可操作的合約後,我們便會發現有的合約中的函式需要透過另一個合約才能進行呼叫。

例如,在 Uniswap v2 中,只有合約工廠(contract factory)可以對 Uniswap Pair 進行初始化。(Uniswap v2:https://github.com/Uniswap/uniswap-v2-core/tree/master/contracts)

對於 Uniswap 團隊來說,只需要稍微檢查一下就可以解決的問題對於很多其他專案來說,卻需要從頭開始重新編寫調配解決方案。

在瞭解問題和開發模式的過程中,我們深入理解了如何藉助多個智慧合約來構建應用程式,從而使 Yield 更加健壯且安全。

(Yield:http://yield.is/)

在本文中,我們將藉助幾個知名專案中的例項來深入分析智慧合約的調配方式。希望大家在讀完本文之後可以對照自己專案的需求,選擇出最適合自己的方法。

背景知識

前文提到,我們首先要把專案分解成多個智慧合約,這是出於技術和精神兩個層面的限制要求。

技術限制源於2016年11月發生的一項改變。當時,Ethereum 主網(包括EIP-170)實施了 Spurious Dragon 硬分叉。此舉將已部署的智慧合約的大小限制在24576位元組以內。

(Spurious Dragon硬分叉:https://blog.ethereum.org/2016/11/18/hard-fork-no-4-spurious-dragon/)
(EIP-170:https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md)

在沒有此項限制時,攻擊者可以在部署智慧合約期間進行無限次的計算。此舉雖然不會對儲存在區塊鏈中的資料產生任何影響,但卻是一種針對 Ethereum 節點的拒絕服務(Denial-Of-Service)攻擊。

由於當時的區塊 gas 限制無法支援這種規模的智慧合約,所以這項改變當時並沒有受到重視:
“該解決方案為儲存在區塊鏈中的物件大小設定了最高上限,並使其略高於當前gas上限值(在最壞的情況下,可以使用470萬gas,23200位元組來建立合約)”

在 DeFi 大爆炸之前,我們為 Yield 編寫了2000行智慧合約程式碼,部署後的程式碼加起來接近 100KB,稽覈員完全沒有覺得專案過於複雜。
但我們仍需把解決方案分解為多個合約。

複雜性和麵向物件程式設計

將區塊鏈應用分解為多個智慧合約的第二個原因與技術限制無關,而是與“人”的精神限制有關。

在特定的時間內,我們大腦中能儲存的資訊量是一定的。相比於處理單一且涉及面廣的大問題,人們在處理多個相互間存在關聯的小問題時表現會更好。

實際上,Object-Oriented Programming(物件導向程式設計)可以提高軟體的複雜性。透過定義代表某種概念的“物件”,並將變數和函式看作該物件的屬性,開發人員能夠在心裡更好地描畫和理解需要解決的問題。

Solidity 在合約層面使用了物件導向的程式設計技術。我們可以將合約看作是一個具有變數和函式的“物件”,在腦海中將複雜的區塊鏈應用程式想象成多個合約的集合,每個合約代表一個單獨的實體。

例如,在 MakerDAO 中,每種加密貨幣都有單獨的合約,此外還有記錄債務的合約、表示債務和外界間閘道器的單獨合約等等。我們不可能將所有內容都編寫在同一個合約中——即使可能,也會異常困難。

把一整個大問題分解成具有內在聯絡的多個小問題可以幫助我們更快地找出解決方案。

實現

接下來,讓我們一起來研究一下 Uniswap、MakerDAO 以及 Yield 的實現方式。

從簡單的例子開始——Uniswap 和 Ownable.sol

我很喜歡 Uniswap v2,因為它非常簡單。開發人員用410行智慧合約程式碼就成功建立了去中心化交易所。僅部署了兩類合約:工廠和不限數量的配對交換合約。

其工廠合約的設計方式決定了其配對交易合約的部署需要經過兩個步驟。首先部署合約,然後用將要進行交易的兩個代幣對其進行初始化。
由於需要確保只有建立配對交易合約的工廠才能對合約進行初始化,所以他們重新實施了Ownable 模式。

此舉取得的效果還不錯,如果你也遇到了同樣的問題,可以借鑑這個方法。如果你知道自己的合約只需要給另一個合約開通訪問特權的話,可以使用 Ownable.sol。甚至都用不到 Uniswap 這種工廠。你可以在一個使用者中部署兩個合同(Boss和Minion,Minion繼承於Ownable.sol),然後執行 minion.transferOwnership(address(boss))。

更為完善的例子——Yield

Yield 的解決方案就沒有 Uniswap v2 那麼簡單了。其核心由五個合約組成,並且特權訪問關係並不是一一對應的。部分合約具備的限制功能可以幫助我們訪問其他核心合約。

因此,我們只需擴充套件 Ownable.sol 以生成兩個訪問層,其中一層有多個成員:

合約所有者可以向特權列表中新增地址(authorized)。繼承合約(Inheriting contracts)可以包含 onlyOrchestrated 修改器以限制對授權地址的訪問。

每個地址都會與一個函式簽名共同註冊,從而收緊對函式的訪問許可權,增強安全性。

(函式簽名:https://www.4byte.directory/)

由於我們會在期間部署 orchestrate 合約,所以沒有取消訪問許可權的函式,owner 會透過呼叫所有合約上的 transferOwnership(adress(0)) 來放棄其特權訪問。

我們自己的平臺代幣 yDai,將從 Orchestrated 繼承並限制 mint 在 owner 放棄其特權之前建立的特定合約:

這種模式相對容易實現和除錯,並且可以實現我們合約中的函式。

極具迷惑性的例子——MakerDAO

大家都非常討厭 MakerDAO 中各種難以理解的術語。但在搞清楚 Yield 的調配模式後,我才發現它們二者的實現方式幾乎是完全相同的。

1.合約部署者是 wards 的原成員。
2.wards 可以 rely 其他人(usr),並使其同樣成為 watds。
3.可以限制函式(auth),只有 wards 可以執行函式。

例如,MakerDAO 的 Vat.sol 合約中的 fold 函式可被用於更新利率累加器,並且只能被其集合中的另一函式呼叫(Jug.sol合約,drip函式)。如果我們觀察一下該函式的話,便會發現用於調配的 auth 修改器,

從某種程度上來說,auth 和其他調配實現是對 private 和 internal 函式概念的擴充套件,僅適用於在合約間進行訪問控制。

MakerDAO 與我們專案的實現方式非常類似。

1.合約部署者是 wards 原成員。在 Yield 中,即 owner。
2. wards可以 rely 其他人並使其成為 wards。在 Yield 中,只有 owner 可以 orchestrate 其他地址並 authorized。
3.函式受限制(auth),因此只有 wards 可以執行受限函式。在 Yield 中,我們提到 onlyOrchestrated 地址可以呼叫標記函式。進一步限制對函式的訪問。

除了在Yield中使用了兩個訪問層(owner和authorized)以及函式限制以外,二者的實現方式是一樣的。可見,合約調配是一種一經實現即可重複使用的常見模式。

從稽覈員和使用者的角度出發,我們還開發了一個收集區塊鏈事件,並展現合約所有權和調配圖的指令碼(可以在上線時透過我們的網站獲取)。

總結

智慧合同的調配是一個在很多專案中反覆出現的問題,在遇到此類問題,大家往往都會從零開始進行調配。但其實此類問題的解決方案都是十分類似的。

我們可以遵循以上標準來實現安全有效的調配,希望大家可以深入理解文中的示例原理,並形成適合自己的解決方案。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;