智能合約升級的基本思路以及具體簡單demo實現
1.智能合約可升級性
智能合約一旦部署無法進行修改,常見方案
1.1 主從合約
Master-Slave contracts
部署一個主合約,以及其他合約,其中主合約負責存儲所有其他合約的地址,並在需要時返回所需的地址。
優點:簡單
缺點:不易進行合約資產轉移到新合約
1.2永久存儲合約
Eternal Storage contracts
邏輯合約和數據合約彼此分開。 數據合約是永久性的,不可升級。 邏輯合約可以根據需要多次升級,並將更改通知給數據合約。通常會和主從技術相結合。
缺點:數據合約不可更改、邏輯合約外部調用數據合約消耗gas
1.3 可升級存儲代理合約
Upgradable Storage Proxy Contracts
代理合約以及邏輯合約,將繼承同一存儲合約。
代理合約將有一個回退函數,委託調用邏輯合約,從而實現邏輯合約在代理存儲中進行更改。
1.4委託調用
EVM所提供的DELEGATECALL操作碼,DELEGATECALL就像是一個普通的CALL 調用操作碼,不同之處在於目標地址上的代碼是在調用合約上下文中執行的,而原始調用的msg.sender以及msg.value將被保留。
分離邏輯和數據合約
邏輯合約通過setter更新數據,而數據合約只允許邏輯合約調用setter。這允許在保持數據不變的同時更換實現邏輯,從而實現完全可升級的系統。
通過引導用戶使用新的邏輯合約(通過諸如ENS的解析器)並更新數據合約的權限來允許新的邏輯合約執行setter,就可以實現合約的更新。
鍵值對數據模型分離邏輯和數據合約
不使用最終期望數據結構(struct,mapping等)來定義合約數據模型,所有數據都被抽象化並存儲在鍵值對中,然後使用一個標準的命名系統以及sha256散列算法用於查找數據值。
2.實現DEMO
storage 存儲
implementionV1 原智能合約
implementionV2 新智能合約
//0.4.25
contract StorageStructure {
address public implementation;
address public owner;
mapping (address => uint) internal points;
uint internal totalPlayers;
}
contract Proxy is StorageStructure {
//確保只有所有者可以運行這個函數
modifier onlyOwner() {
require (msg.sender == owner);
_;
}
//設置管理者owner地址
constructor() public {
owner = msg.sender;
}
//更新實現合約地址
function upgradeTo(address _newImplementation)
external onlyOwner
{
require(implementation != _newImplementation);
_setImplementation(_newImplementation);
}
//回調
function () payable public {
address impl = implementation;
require(impl != address(0));
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize)
let result := delegatecall(gas, impl, ptr, calldatasize, 0, 0)
let size := returndatasize
returndatacopy(ptr, 0, size)
switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
//設置當前實現地址
function _setImplementation(address _newImp) internal {
implementation = _newImp;
}
}
contract ImplementationV1 is StorageStructure {modifier onlyOwner() {
require (msg.sender == owner);
_;
}
function addPlayer(address _player, uint _points)
public onlyOwner
{
require (points[_player] == 0);
points[_player] = _points;
}
function setPoints(address _player, uint _points)
public onlyOwner
{
require (points[_player] != 0);
points[_player] = _points;
}
}
contract ImplementationV2 is ImplementationV1 {
function addPlayer(address _player, uint _points)
public onlyOwner
{
require (points[_player] == 0);
points[_player] = _points;
totalPlayers++;
}
}
-
依次部署 proxy合約、ImplementationV1合約
-
調用proxy合約的upgradeTo(address)函數:
address爲部署後的implementionV1合約地址。
-
重新部署 proxy合約,此時只需使用proxy合約即可
-
合約升級: 重寫並部署implementionV2合約 ,並將其合約地址,作爲調用proxy合約的upgradeTo(address)參數
相關文章鏈接
https://hackernoon.com/how-to-make-smart-contracts-upgradable-2612e771d5a2
https://yos.io/2018/10/28/upgrading-solidity-smart-contracts/