Solidity

哪些設計模式適合乙太坊智能合約中的資料結構修改?

  • February 29, 2020

我希望聽到人們在乙太坊智能合約中實施的設計模式,以允許對資料結構進行部署後修改。

例如,假設我有一個契約,其中包含一個定義地址的結構。我是否應該意識到我想將電子郵件地址屬性添加到地址。是否有任何預部署設計模式考慮允許我這樣做。

一個更複雜的例子..如果我有一個名為“問題”和“答案”的契約的兩個屬性,突然間我想要有多個可能的答案..如何進行這樣的改變?

我的問題/擔憂是,例如,如果您通過瀏覽器與所述契約互動,您可以簡單地更新前端指向的契約(更新後)。但是您如何解決在更新過程中維護任何契約數據的問題部署..?

謝謝

您需要考慮以下幾點:

*這必須從一開始就計劃好。*您需要考慮以下 5 點來設計您的智能合約:

  1. 你必須有一個好的測試策略和戰術。因為更新智能合約的成本真的會毀了你的生活。
  2. 保持您的智能合約**模組化,並將規則和邏輯與資料結構完全分開。**因此,如果您需要更改某些內容,您只需更改相關契約,而無需更改許多或所有契約。
  3. 您應該準備好緊急停止或斷路器,以便能夠在任何遷移期間停止所有操作。因為您不希望在您遷移時以及之後人們仍然可以將數據更新/插入舊版本的智能合約的情況。
  4. 之前應該提供從您的智能合約中讀取所有數​​據的能力。當然,您可以通過將所有數據的讀取限制為所有者或任何其他受信任的使用者甚至另一個智能合約來進行許可讀取。您需要閱讀舊版本的智能合約並插入新版本。
  5. 您將使用以下策略與您的智能合約進行通信。我從Smart Contact Best Practices複製了它們:

升級損壞的合約

如果發現錯誤或需要改進,則需要更改程式碼。發現一個bug是不好的,但是沒有辦法處理它

但是,有兩種最常用的基本方法。兩者中較簡單的是擁有一個註冊合約,其中包含最新版本合約的地址。對於合約使用者來說,更無縫的方法是擁有一個將呼叫和數據轉發到最新版本合約的合約。

範例 1:使用註冊合約儲存最新版本的合約

在這個例子中,呼叫沒有被轉發,所以使用者應該在每次與它互動之前獲取目前地址。

contract SomeRegister {
   address backendContract;
   address[] previousBackends;
   address owner;

   function SomeRegister() {
       owner = msg.sender;
   }

   modifier onlyOwner() {
       require(msg.sender == owner)
       _;
   }

   function changeBackend(address newBackend) public
   onlyOwner()
   returns (bool)
   {
       if(newBackend != backendContract) {
           previousBackends.push(backendContract);
           backendContract = newBackend;
           return true;
       }

       return false;
   }
}

這種方法有兩個主要缺點:

  1. 使用者必須始終查找目前地址,任何不這樣做的人都有使用舊版本合約的風險
  2. 更換契約時,您需要仔細考慮如何處理契約數據

另一種方法是讓合約將呼叫和數據轉發到最新版本的合約:

範例 2:使用 DELEGATECALL 轉發數據和呼叫

contract Relay {
   address public currentVersion;
   address public owner;

   modifier onlyOwner() {
       require(msg.sender == owner);
       _;
   }

   function Relay(address initAddr) {
       currentVersion = initAddr;
       owner = msg.sender; // this owner may be another contract with multisig, not a single contract owner
   }

   function changeContract(address newVersion) public
   onlyOwner()
   {
       currentVersion = newVersion;
   }

   function() {
       require(currentVersion.delegatecall(msg.data));
   }
}

這種方法避免了前面的問題,但也有其自身的問題。您必須非常小心在本契約中儲存數據的方式。如果您的新契約的儲存佈局與第一個不同,您的數據最終可能會損壞。此外,這個簡單版本的模式不能從函式返回值,只能轉發它們,這限制了它的適用性。(更複雜的實現嘗試使用內聯彙編程式碼和返回大小系統資料庫來解決這個問題。)

無論您採用哪種方法,重要的是要有某種方法來升級您的合約,否則當在其中發現不可避免的錯誤時,它們將變得無法使用。

我為此在 Medium 上創建了一個故事,標題為:乙太坊 dApp 的基本設計考慮(1):可升級的智能合約

我們也可以使用 VARIANT 類型的解決方案(是的,我也不喜歡它)。

有一個映射(uint => StorageItem)。

  • uint 是欄位 ID。
  • StorageItem 合約將有一個字元串值和一個整數類型。該類型將允許您從字元串轉換為所需的結束類型。

這裡的一個弱點是,在某些時候,solidity 將支持我們想要利用的新類型。

引用自:https://ethereum.stackexchange.com/questions/1134