Go-Ethereum

智能合約 - 使用帶有 delegateCall 的 Relay 更新實現

  • January 31, 2022

我知道智能合約應該是不可變的,這就是重點,但期望有人實現從第一天起就永不改變(沒有升級或沒有錯誤)的邏輯也是不現實的。

因此,我一直在閱讀有關解決這種不可變狀態的幾種方法。一種流行的方法似乎是將 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;
   }
}

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