Смарт-контракт — реализация обновления с использованием реле с делегатомCall

Я знаю, что смарт-контракты должны быть неизменяемыми, и в этом весь смысл, но ожидать, что кто-то внедрит логику, которая никогда не изменится (без обновлений или без ошибок) с первого дня, также нереально.
Поэтому я читал о нескольких методах обхода этого неизменного состояния. Кажется, что популярный метод использует 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: я знаю, что этот метод выше позволяет нам создать «обновляемый контракт» в случае, если нам нужно обновить нашу логику (функции и т. д.), однако он не позволяет нам изменять/добавлять нашу структуру переменных состояния. Есть ли обходной путь?

Большое спасибо, рад быть частью этого сообщества!

Ответы (2)

Это просто ответ на ваш 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;
    }
}