Solidity

如何使用映射和狀態變數使 Solidity 合約可升級?

  • March 6, 2021

在使契約可升級的同時,我在拆分程式碼實現和儲存的方法方面遇到了問題。

在這種方法中,我為每個 uint256 類型和地址的狀態變數創建了儲存槽。這些變數是靜態的,不會隨著時間的推移而擴展,我將插槽定義為

對於 uint256 _totalSupply

bytes32 internal constant _TOTAL_SUPPLY_SLOT = 0x6cac11967c5738fe648c7b461acdbb3138d8619be340b9043646ca5ab64ec74b;

使用 eip1967 獲取插槽

_TOTAL_SUPPLY_SLOT == bytes32(uint256(keccak256("eip1967.tokenGeyserStorage.totalSupply")) - 1)

並且相應地

   function _totalSupply() internal view returns (uint256) {
   return getUint256(_TOTAL_SUPPLY_SLOT);
   }

   function _setTotalSupply(uint256 _value) internal {
       setUint256(_TOTAL_SUPPLY_SLOT, _value);
   }

在哪裡

   function setUint256(bytes32 slot, uint256 _value) private {
       // solhint-disable-next-line no-inline-assembly
           assembly {
               sstore(slot, _value)
           }
       }

and getter as 

   function getUint256(bytes32 slot) private view returns (uint256 str) {
       // solhint-disable-next-line no-inline-assembly
       assembly {
           str := sload(slot)
       }
   } 

像這樣的數據類型呢

mapping(address => uint256) private _balances;

and

IERC20 public token;

我們知道映射可以增長,如何為它們分配一個槽,setter 和 getter 會是什麼樣子?

由於 IERC20 也是一種不同的數據類型,我們應該如何處理這些狀態變數,同時讓合約可升級並保留這些狀態?

任何可以幫助更好地實施代理的概念,例如菱形和刻面?

首先是簡短的答案:

我們知道映射可以增長,如何為它們分配一個槽,setter 和 getter 會是什麼樣子?

您可以定義一個同時使用“插槽”和“索引”的雜湊函式,例如(偽)、雜湊(abi.encodePacked(“myMapping”, key))。這將為每個值生成一個唯一位置,然後您可以獲取或設置該位置。

由於 IERC20 也是一種不同的數據類型,我們應該如何處理這些狀態變數,同時讓合約可升級並保留這些狀態?

您只需要一個地址。你的“令牌”是IERC20(<address>). 實現細節,即函式,是在編譯時定義的“類型”。您不會將其儲存為“數據”。

現在長答案。我認為你走錯了路,這讓你很難過。

您的方法似乎遵循“永恆儲存”,這通常不是創建可升級契約的首選現代方式。也有代理的元素,它似乎導致了最糟糕的世界。例如,您不應該需要組裝來進行永久儲存。你為什麼這樣做還不清楚。

考慮:

contract Storage {

 function getUint(bytes32 key) public view returns(unit) {
   return datastoreUint[key];
 }

contract Logic }

 Storage storage; // instantiated in constructor

 function getSomething(address user) public view returns(unit) {
   bytes32 key = hashFunction(arguments); // pseudo
   return storage.getUint(key);
 }

在 Eternal Storage 中,一般的想法是實現應該知道它用於重要變數的槽。儲存合約不需要彙程式序。它只有每個縮放器數據類型的鍵/值對映射並返回任何.

您可以使其與上述建議一起工作,這些建議解決了映射和契約類型,也適用於數組。結構將分解為單個成員的多個提取和散列方案的另一個參數。

您可能會看到該模式迫使您編寫非常抽象和繁忙的程式碼來處理變數。它容易出錯,而且不是特別好讀。它還涉及“實現”和“儲存”之間的大量通信,這將在執行時增加 gas 成本。

代理方法讓您可以使用正常的語法編寫程式碼,並在稍加自律的情況下安全地升級聯繫人。這是一個很大的話題,超出了原始問題的範圍,所以我不會在這裡解釋它是如何工作的。在這個網站和其他地方有一些關於“代理”的好文章。

可升級的代理合約將使用彙編器來儲存一兩個變數。就這樣。彙編器用於避免與實現合約發生儲存衝突。也就是說,代理(轉發器)合約需要寫下一些東西,實現的地址,並且它需要將它寫在一個肯定不會被不知道它的實現覆蓋的位置。該問題的解決方案是依靠一個抗碰撞的位置——通常是一個由散列函式生成的密鑰——一個遙遠的偽隨機位置,你只能通過彙程式序才能到達。

代理升級的邏輯合約如何共享數據

在這裡查看一個很好解決的實現範例。https://github.com/OpenZeppelin/openzeppelin-contracts/tree/solc-0.6/contracts/proxy。(為了簡單起見,我選擇了這個版本,所以在使用之前請注意版本)。

乍一看可能有點奇怪,但值得研究。

  • 代理委託給實現。上下文是代理,數據儲存在代理中。
  • 代理使用彙程式序定位實現,並包含一個更改實現的值(地址)的函式。
  • Proxy 使用“Ownable”模式來保護升級功能。

可升級合約也可能使用“Ownable”模式,這會帶來一些挑戰——如果代理本身和實現都實現“changeOwner”怎麼辦?應該執行哪個函式?

他們用一個簡單的規則解決了這個問題。存在僅用於管理升級過程的 ProxyAdmin。Proxy 的規則很簡單:來自 ProxyAdmin 的所有內容都在“這裡”執行,而不來自 ProxyAdmin 的所有內容都用於實現。ProxyAdmin 由部署者/治理者“擁有”。實現做它做的任何事情都不會與代理/升級問題發生衝突或乾擾。

希望能幫助到你。

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