智能合約 - 使用帶有 delegateCall 的 Relay 更新實現
我知道智能合約應該是不可變的,這就是重點,但期望有人實現從第一天起就永不改變(沒有升級或沒有錯誤)的邏輯也是不現實的。
因此,我一直在閱讀有關解決這種不可變狀態的幾種方法。一種流行的方法似乎是將 delegateCall 與 Relay 契約一起使用,但我正在為如何實際使用此方法而苦苦掙扎,因為我找不到任何範例。
有人會好心看看我創建的那個簡單的例子,並告訴我我做錯了什麼嗎?
https://gist.github.com/fabdarice/d513d620d9355312d085c7a68e6c6118
Relay.sol
contract Relay { address public currentVersion; address public owner; mapping (address => uint) user_amounts; modifier onlyOwner() { if (msg.sender != owner) { throw; } _; } 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() { if(!currentVersion.delegatecall(msg.data)) throw; } }
捐贈.sol:
contract Donation { mapping (address => uint) user_amounts; /* DOES THIS METHODS MODIFY user_amounts of the Relay contract ??? */ function sendDonation(uint n) { user_amounts[msg.sender] = user_amounts[msg.sender] + n } }
DonationNew.sol:
contract DonationNew { mapping (address => uint) user_amounts; function sendDonation(uint n) { user_amounts[msg.sender] = user_amounts[msg.sender] + n } function cancelDonation() { user_amounts[msg.sender] = 0 } }
應用程序.js:
// First, deploying Relay, then deploying Donation and retrieve Donation contract address in 'donation_contract_address' // Then, linking Relay to my first version of my contract Donation Relay.deployed().then(function(contractInstance) { contractInstance.changeContract(donation_contract_address); }) // Then, I want to call sendDonation from the Donation contract // !!!!! I DON'T KNOW WHAT IS THE CORRECT WAY TO CALL THIS !!!!!! Relay.deployed().then(function(contractInstance) { contractInstance.sendDonation(5) ; }) // OR Relay.deployed().then(function(contractInstance) { contractInstance.currentVersion.delegateCall(sendDonation(5)) ; }) // Now I want to update the Donation contract to add the cancelDonation function // First I deploy the new contract DonationNew and retrieve it's address in 'donation_new_contract_address' Relay.deployed().then(function(contractInstance) { contractInstance.changeContract(donation_new_contract_address); }) // are the state variables still available from the old contract to the new one? // Then if I want to call the new function : Relay.deployed().then(function(contractInstance) { contractInstance.cancelDonation() ; })
PS:我知道上面的這個方法允許我們在需要更新我們的邏輯(函式等)的情況下創建一個“可升級合約”,但是它不允許我們修改/添加我們的狀態變數結構。這也有解決方法嗎?
非常感謝,很高興成為這個社區的一員!
這只是對您的 PS 的回答,因為我還不精通 Solidity 尚未嘗試找出您的程式碼有什麼問題。
如果你想在保留儲存的同時升級程式碼,你可以考慮分離儲存和邏輯。有一個專用的儲存合約,它接受來自可信地址的寫呼叫(例如邏輯合約)。所有重要的儲存都應與此相關聯。您還應該考慮使其盡可能靈活,以便不太可能需要升級。
本文包含一個範例以及編寫可升級智能合約的許多其他建議:
https://blog.colony.io/writing-upgradeable-contracts-in-solidity-6743f0eecc88
這是可升級庫的範例。
https://github.com/kyriediculous/knuckles/tree/master/contracts/contracts
我目前正在努力削減它。
有一個中央系統資料庫來跟踪正在使用的圖書館地址。更改這些會更改代理委託的地址。
代理通過庫的介面連結到儲存。這構造了 calldata 以將其發送到代理,然後代理將其委託給庫。
如果要升級庫,請重新部署它並更改中央系統資料庫中的地址。
這是一個精簡的範例,即將發布部落格文章。
pragma solidity ^0.4.23; contract Registry { mapping (bytes32 => address) public libraries; mapping (bytes32 => address) public contracts; function addLibrary(bytes32 _name, address _lib) external { require(libraries[_name] == address(0), "LIBRARY_ALREADY_EXISTS"); require(_lib != address(0), "INSERT_VALID_LIBRARY_ADDRESS"); libraries[_name] = _lib; } function addContract(bytes32 _name, address _contract) external { Enabled(_contract).setCMCAddress(address(this)); contracts[_name] = _contract; } } interface ContractProvider { function libraries(bytes32 _name) external view returns (address); function contracts(bytes32 _name) external view returns (address); } contract Enabled { address public CMC; function setCMCAddress(address _CMC) external { if (CMC != 0x0 && msg.sender != CMC) { revert(); } else { CMC = _CMC; } } } contract setXproxy is Enabled { function () payable public { address _impl = ContractProvider(CMC).libraries('setXlib'); 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) } } } } library setXinterface { struct X { uint x; } function setX(X storage _X, uint _x) external; } library setXlib { function setX(setXinterface.X storage _X, uint _x) external { _X.x = _x; } } contract setXstorage is Enabled { using setXinterface for setXinterface.X; setXinterface.X X; function setX(uint _x) external { X.setX(_x); } function getX() external view returns (uint) { return X.x; } }