Я знаю, что смарт-контракты должны быть неизменяемыми, и в этом весь смысл, но ожидать, что кто-то внедрит логику, которая никогда не изменится (без обновлений или без ошибок) с первого дня, также нереально.
Поэтому я читал о нескольких методах обхода этого неизменного состояния. Кажется, что популярный метод использует 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
В настоящее время я работаю над его сокращением.
Существует центральный реестр для отслеживания используемых адресов библиотек. Их изменение изменяет адрес, на который делегируются прокси.
Прокси подключается к хранилищу через интерфейс библиотеки. Это создает данные вызова для отправки их прокси-серверу, который затем делегирует их библиотеке.
Если вы хотите обновить библиотеку, вы повторно развертываете ее и меняете адрес в центральном реестре.
Вот урезанный пример, запись в блоге скоро.
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;
}
}