Contrato inteligente: implementación de actualización mediante un relé con delegarCall

Sé que se supone que los contratos inteligentes son inmutables y ese es el objetivo, pero esperar que alguien implemente una lógica que nunca cambie (sin actualizaciones ni errores) desde el día 1 tampoco es realista.
Por lo tanto, he estado leyendo acerca de varios métodos para solucionar este estado inmutable. Parece que un método popular es usar delegarCall con un contrato de retransmisión, pero tengo problemas para saber cómo usar este método porque no pude encontrar ningún ejemplo.
¿Alguien tendría la amabilidad de mirar ese ejemplo simple que creé y decirme qué estoy haciendo incorrectamente?
https://gist.github.com/fabdarice/d513d620d9355312d085c7a68e6c6118
Relé.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;
  }
}  

Donación.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
  }
}  

DonaciónNuevo.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
  }
}  

aplicación.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() ;
})

PD: Sé que este método anterior nos permite crear un "contrato actualizable" en caso de que necesitemos actualizar nuestra lógica (funciones, etc.), sin embargo, no nos permite modificar/agregar nuestra estructura de variables de estado. ¿Hay una solución a esto también?

Muchas gracias, feliz de ser parte de esta comunidad!

Respuestas (2)

Esta es solo una respuesta a su PS, ya que aún no estoy muy versado en Solidity para tratar de encontrar el problema con su código.

Si desea tener la capacidad de actualizar el código y mantener el almacenamiento, podría pensar en separar el almacenamiento y la lógica. Tenga un contrato de almacenamiento dedicado, que acepte llamadas de escritura desde direcciones confiables (por ejemplo, los contratos lógicos). Todo el almacenamiento importante debe estar asociado con este. También debe considerar hacerlo lo más flexible posible, de modo que sea poco probable que necesite una actualización.

Este artículo contiene un ejemplo, así como muchas otras sugerencias para escribir contratos inteligentes actualizables:

https://blog.colony.io/writing-upgradeable-contracts-in-solidity-6743f0eecc88

Este es un ejemplo de bibliotecas actualizables.

https://github.com/kyriediculous/knuckles/tree/master/contracts/contracts

Actualmente estoy trabajando en cortarlo.

Hay un registro central para realizar un seguimiento de las direcciones de biblioteca que se utilizan. Cambiar estos cambia la dirección a la que delegan los proxies.

El proxy está vinculado al almacenamiento a través de una interfaz de la biblioteca. Esto construye los datos de llamada para enviarlos al proxy que luego los delega a la biblioteca.

Si desea actualizar la biblioteca, vuelva a implementarla y cambie la dirección en el registro central.

Aquí hay un ejemplo reducido, publicación de blog próximamente.

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;
    }
}