Contratos inteligentes actualizables

Si el emisor del contrato quiere tener una forma de actualizar el código del contrato, para que los datos de la cuenta y otras cosas se transfieran, ¿puede Ethereum proporcionar esto? ¿También se puede hacer esto sin cambiar la dirección del contrato o siempre es necesario implementar un nuevo contrato?

¿Existen mecanismos de "anexo" para agregar alguna funcionalidad nueva a un contrato sin una reescritura total?

Esto puede ser valioso para los lectores potenciales de esta pregunta interesante: OpenZeppelin acaba de agregar una función de capacidad de actualización en su asistente de contrato inteligente , lo que facilita la generación de tokens actualizables ERC721, ERC20 y ERC1155 con el patrón UUPS o transparente.
Si alguien es más un aprendiz visual como yo, este video rápido fue útil cuando estaba actualizando mi contrato Ethereum: youtube.com/watch?v=sQJ-XQBzEuc

Respuestas (13)

Sí. Hay una serie de enfoques mediante los cuales puede actualizar un Contract1a Contract2, manteniendo su estado (datos y saldo) con la misma dirección que antes.

¿Como funciona esto? Una forma es usar un contrato de proxy con una fallbackfunción en la que cada llamada/trx de método se delegue al contrato de implementación (que contiene toda la lógica).ingrese la descripción de la imagen aquí

Una llamada de delegado es similar a una llamada regular, excepto que todo el código se ejecuta en el contexto de la persona que llama (proxy), no del destinatario (implementación). Debido a esto, una transferencia en el código del contrato de implementación transferirá el saldo del proxy, y cualquier lectura o escritura en el almacenamiento del contrato se leerá o escribirá desde el almacenamiento del proxy.

En este enfoque, los usuarios solo interactúan con el contrato de proxy y podemos cambiar el contrato de implementación manteniendo el mismo contrato de proxy.

ingrese la descripción de la imagen aquí

La fallbackfunción se ejecutará en cualquier solicitud, redirigiendo la solicitud a la implementación y devolviendo el valor resultante (usando códigos de operación).

Esta fue una explicación básica que es suficiente para que podamos trabajar con contratos actualizables. En caso de que desee profundizar en el código de contrato de proxy y los diferentes patrones de proxy, consulte estas publicaciones.

¿Cómo puedo escribir contratos inteligentes actualizables?

OpenZeppelin proporciona increíbles herramientas CLI y bibliotecas JS que se encargan de todos los contratos complejos anteriores proxy, vinculándolos al contrato de implementación (lógico) y administrando todos los contratos que implementa utilizando la CLI para la capacidad de actualización, lista para usar.

Lo único que debe hacer es escribir sus contratos y usar la CLI de OpenZeppelin o las bibliotecas para implementar los contratos.

NOTA: Hay algunas limitaciones que debe tener en cuenta, en términos de cómo debe escribir sus contratos y cómo debe actualizarlos. También hay una serie de soluciones alternativas a estas limitaciones en esta publicación .

En este patrón, ¿qué detiene un cebo y un interruptor maliciosos? Es decir, usted compra sus tokens, confiando en el saldo, luego, más tarde, el desarrollador cambia a un nuevo contrato en el que se le quitan los tokens.
No compre los tokens en primer lugar a menos que confíe en el desarrollador con la única actualización correcta. Un buen proyecto de DAO tendrá las claves de proxy de actualización controladas por un multisig o por votación directa de DAO. Pero si le preocupan los cebos y cambios maliciosos, entonces probablemente no debería confiar en el proyecto en primer lugar, ya que hay miles de otras formas de arruinarlo.
También quisiera aclarar que usted mismo puede implementar fácilmente los contratos de proxy de actualización; puede encontrar un ejemplo aquí: github.com/Dawn-Protocol/dawn-erc20-erc777
¿El contrato de representación y el contrato de implementación existen en la cadena con direcciones separadas? Si se actualiza un contrato, ¿las personas aún pueden llamar al contrato anterior directamente sin pasar por el proxy?

Una vez que un contrato está en la cadena de bloques, es definitivo y no se puede cambiar. Ciertos parámetros, por supuesto, se pueden cambiar si se les permite cambiar a través del código original.

Un método para actualizar contratos es usar un sistema de control de versiones. Por ejemplo, podría tener un contrato de entrada que simplemente reenvíe todas las llamadas a la versión más reciente del contrato, según lo define un parámetro de dirección actualizable. También puede usar un registro de nombres y actualizarlo para que apunte a la versión de contrato más reciente.

Otro método es poner su código lógico en una biblioteca, luego usar la función CALLCODE, a través de bibliotecas en Solidity, para llamar al código ubicado en una dirección específica y actualizable. De esta manera, los datos de usuario persisten entre versiones. Esto tiene la limitación de que el ABI del contrato lógico debe permanecer igual en el tiempo.

Aquí hay una esencia antigua que usé para demostrar la segregación de datos/código hace un tiempo.

Edición de la hacienda:

Comenzando con el lanzamiento de Homestead, ahora hay un DELEGATECALLcódigo de operación. Esto le permite esencialmente reenviar llamadas a un contrato separado mientras mantiene msg.sendery todo el almacenamiento.

Por ejemplo, podría tener un contrato que mantenga la misma dirección y almacenamiento, pero reenvíe todas las llamadas a una dirección almacenada en una variable:

contract Relay {
    address public currentVersion;
    address public owner;

    function Relay(address initAddr){
        currentVersion = initAddr;
        owner = msg.sender;
    }

    function update(address newAddress){
        if(msg.sender != owner) throw;
        currentVersion = newAddress;
    }

    function(){
        if(!currentVersion.delegatecall(msg.data)) throw;
    }
}
También podría valer la pena mencionar los solucionadores de nombres.
Aquí hay un gran ejemplo de esta idea completamente desarrollada: gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f
Esto es muy inteligente y es una forma de inyección de dependencia: el contacto de entrada depende de la dirección de la versión actual.
¡Hola, Tjaden Hess, Jossie Calderón! Estoy un poco confundido acerca de cómo implementar realmente este concepto. He hecho una esencia con un ejemplo simple con una donación de contrato único que debe actualizarse. ¿Alguien sería tan amable de echarle un vistazo y decirme si estoy malinterpretando completamente el concepto? gist.github.com/fabdarice/d513d620d9355312d085c7a68e6c6118 ¡ Aprecio mucho esto, gracias!
Hola, Tjaden Hess, buen ejemplo, ¿cómo puedo manejar este contrato a través de web3? Supongamos que tengo un contrato llamado contrato DemoVersion1.sol que contiene el siguiente código contract DemoVersion1 { function checkVersion() returns (uint){ return 1; } }y tengo que actualizar el contrato a la versión dos que contiene el siguiente código. contract DemoVersion2 { function checkVersion() returns (uint){ return 2; } }¿Cómo puedo manejar llamar a los métodos? de contrato, ¿alguien puede explicar o señalar el ejemplo adecuado?
@fabdarice ¿Lo descubriste? ¿Puedes publicar otra esencia de trabajo?
@fabdarice Entonces, ¿el tamaño de los datos que una función debe conocerse de antemano? ¿Qué sucede si devuelvo una matriz dinámica?
Eso es correcto, necesita saber el tamaño de los datos. Que yo sepa, no se recomienda en Solidity devolver una matriz dinámica => captador con parámetros de índice
¿Cree que hay un problema de confianza con los contratos de "representación"? Es bueno para los desarrolladores que el contrato inteligente de uno sea "actualizable", pero esto realmente significa que hay una pizarra en blanco en lo que respecta a los cambios que esos desarrolladores pueden hacer en el contrato inteligente existente, ¿verdad? ¿No hay alguna degradación de la confianza que puede sufrir el contrato por su inmutabilidad?
Sí, este es un problema que debe abordarse según el caso de uso. A menudo, una buena idea es proporcionar un mecanismo de "suscripción" que permita a los usuarios hacer la transición al nuevo contrato a su discreción. Otra forma es simplemente usar una resolución de nombres y un modelo TOFU
Pregunta para la que soy demasiado perezoso para ponerme a prueba en este momento. ¿El uso de un relé no hace que el modificador de 'vista' sea inútil? No hay costo de gas a menos que sea llamado por otros contratos, que es lo que es. ¿O estoy equivocado?
No veo nada como llamada delegada en esencia: s
Como ahora hay mejor información pública disponible sobre la capacidad de actualización, moveré la marca de la respuesta correcta a una respuesta más detallada.
¿Podemos agregar nuevas funciones en el contrato actualizado? @Tjaden Hess♦
En su código esencial, lo liberé, separando el almacenamiento y la lógica en dos contratos diferentes; cada llamada del contrato lógico al contrato que tiene el almacenamiento cobra 2.000 adicionales de gas, hay alguna forma de evitar este costo adicional de gas? @TjadenHess
Esa esencia está bastante desactualizada, ahora debería usar llamadas delegadas a través de contratos de biblioteca. Pero, en general, siempre habrá una sobrecarga debido a la capa adicional de direccionamiento indirecto. Cambiar contextos es algo costoso ya que los nodos potencialmente tienen que obtener cada estado de contrato del disco

Un método es usar un Sistema de Contratos como se describe a continuación:

  1. Contrato "Registro" - contendrá pares "nombre - dirección" para todos los contratos de su sistema;
  2. contrato Backend;
  3. Contrato Frontendusando Backend;
  4. Implementar Registery obtener la dirección;
  5. Implementar Backendy registrar la dirección de Backenden ya implementada Register;
  6. Codifique la dirección de Registeren la fuente de Backend. Antes de cualquier llamada , debe llamar a su Backendy obtener la dirección real de .FrontendRegisterBackend

Luego, puede actualizar su Backendcontrato en cualquier momento: simplemente implemente el nuevo y vuelva a registrarlo en el archivo Register.

Llamando al contrato externo: solidity.readthedocs.org...

Vea también la discusión del foro: forum.ethereum.org...


UPD: la misma forma pero más eficiente (tal vez)

Primera implementación:

  1. Escriba un contrato Registerque pueda implementar otros contratos con su propia dirección como argumento del constructor;
  2. Escriba todos los demás contratos: contratos "actualizables" con constructores que requieran Registerla dirección;
    • tal vez esos contratos deberían ser inhabilitables o tener un método de subvención
  3. Implemente Registerla entrega de datos a su constructor: todos los demás contratos del paso 2.

Mejora:

  1. Implementar una nueva versión del contrato "actualizable" con la misma dirección de Register;
    • O tal vez si Registerpuede implementar otros contratos, dárselos
  2. (opcional) deshabilitar/eliminar la versión anterior del contrato "actualizable";
  3. Dirección de registro de la nueva versión del contrato "actualizable" en el Register.
¡Excelente! Si alguien tiene ejemplos codificados existentes, agregue aquí :)
Estoy moviendo la marca de respuesta correcta, porque la otra respuesta contiene información actualizada sobre cómo hacer un contrato de relevo en Homestead.
Pregunta: En el caso de contratos múltiples, ¿no sería más eficiente registrar primero todos los contratos, guardar todas sus direcciones y luego implementar el contrato de registro (pasando todas las direcciones guardadas como argumentos de constructor)? ¿Podría ahorrar muchas llamadas a funciones de esa manera y el gas correspondiente?
@BharatMallapur, sí, por supuesto

El código del contrato es inmutable, el almacenamiento es mutable, pero no puede ejecutar el código colocado en el almacenamiento, al menos por ahora.

Corrección de errores en los contratos

En cuanto a las correcciones de errores, el patrón común es tener contratos de proxy o de búsqueda para ser una puerta de entrada al real, que en caso de un cambio o corrección de errores sería reemplazado. Reemplazarlo también significa perder el antiguo contenido de almacenamiento.

mantenimiento de almacenamiento

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.

Acceso al almacenamiento después de la autodestrucción

A día de hoy, no se ha implementado una poda real, incluso en el caso de la autodestrucción, pero eso definitivamente debería ocurrir en el futuro. Hay varios EIP que discuten esto.

Incluso si se implementa la poda, no debería ocurrir en un instante y debería poder leer el almacenamiento desde el último estado. También se planea tener nodos de archivo para mantener los estados indefinidamente; no estoy seguro de que sea factible sin limitaciones simplemente juzgando el crecimiento de la cadena de bloques.

Reimplementación en la misma dirección

En resumen: prácticamente esto no es posible. Las direcciones de contrato se calculan a partir del remitente y el nonce. El nonce es secuencial, no puede haber huecos y no puede haber duplicados.

En teoría, es posible llegar al mismo hash con una combinación diferente de dirección y nonce, pero la probabilidad es pequeña.

Los contratos implementados en una cadena de bloques son inmutables, por lo que esto significa:

  • la dirección y el código de un contrato implementado no se pueden cambiar
  • implementar un contrato más nuevo (o incluso idéntico) creará una nueva dirección
  • el código no se puede agregar a un contrato implementado

Si los problemas del contrato quieren tener una forma de actualizar el código del contrato, para que los datos de la cuenta y otras cosas se transfieran, ¿qué medios proporciona Ethereum para esto?

Una forma simple de extender un contrato C1 es asegurarse de que C1 tenga funciones/accesorios que devuelvan todos los datos que tiene. Se puede escribir un nuevo contrato C2, que llama a las funciones C1 y hace lógica adicional o corregida. (Tenga en cuenta que si C1 y C2 tienen foo, donde foo de C1 tiene errores y foo de C2 está corregido, no hay forma de desactivar la llamada de foo de C1).

Se puede usar un registro, como se describe en la respuesta de @Alexander, para que otros DApps y contratos consulten el registro para la dirección de contractC, de modo que cuando C1 sea "reemplazado" por C2, no sea necesario cambiar el código DApp. El uso de un registro de esta manera evita codificar la dirección de C1 (para que C2, C3, C4 puedan ocupar su lugar cuando sea necesario), pero la DApp necesita codificar la dirección del registro.


EDITAR: El ENS, Ethereum Name Service, acaba de implementarse en la red de prueba (Ropsten).

Consulte la wiki de ENS para obtener un inicio rápido y otros detalles. Aquí hay una introducción:

ENS es el Servicio de nombres de Ethereum, un sistema de nombres extensible y distribuido basado en la cadena de bloques de Ethereum.

ENS se puede utilizar para resolver una amplia variedad de recursos. El estándar inicial para ENS define la resolución para las direcciones de Ethereum, pero el sistema es extensible por diseño, lo que permite resolver más tipos de recursos en el futuro sin que los componentes centrales de ENS requieran actualizaciones.

ENS se implementa en la red de prueba de Ropsten en 0x112234455c3a32fd11230c42e7bccd4a84e02010.

Discusión inicial aquí .

Y el registro, como cualquier sistema indirecto, tiene sus propios errores, problemas de seguridad y problemas. Si desea actualizar el contrato debido a un riesgo de seguridad, esto es algo a tener en cuenta. Si The DAO tuviera un sistema de actualización, sin duda se habría utilizado para un hack...
@bortzmeyer De acuerdo, cualquier mecanismo de actualización tiene riesgos que podrían explotarse y esos riesgos deben considerarse.

La respuesta más votada es utilizar delegatecally es muy complicado acertar.

https://blog.trailofbits.com/2018/09/05/contract-upgrade-anti-patterns describe algunos métodos de actualización, así como consideraciones críticas para que no introduzca más errores o un método de actualización con errores que no trabajo

Recomendaciones de patrones de proxy

Compruebe la existencia del contrato de destino antes de llamar a la llamada delegada. Solidity no realizará esta verificación en su nombre. Descuidar la verificación puede provocar un comportamiento no deseado y problemas de seguridad. Usted es responsable de estas comprobaciones si confía en una funcionalidad de bajo nivel.

Si está utilizando el patrón de proxy, debe:

Tener una comprensión detallada de las partes internas de Ethereum , incluida la mecánica precisa de la llamada de delegado y un conocimiento detallado de las partes internas de Solidity y EVM.

Considere cuidadosamente el orden de herencia , ya que afecta el diseño de la memoria.

Considere cuidadosamente el orden en que se declaran las variables. Por ejemplo, el sombreado de variables o incluso los cambios de tipo (como se indica a continuación) pueden afectar la intención del programador cuando interactúa con la llamada del delegado.

Tenga en cuenta que el compilador puede usar relleno y/o empaquetar variables juntas. Por ejemplo, si dos uint256 consecutivos se cambian a dos uint8, el compilador puede almacenar las dos variables en una ranura en lugar de dos.

Confirme que se respete el diseño de la memoria de las variables si se usa una versión diferente de solc o si se habilitan diferentes optimizaciones. Las diferentes versiones de solc calculan las compensaciones de almacenamiento de diferentes maneras. El orden de almacenamiento de las variables puede afectar los costos de gas, el diseño de la memoria y, por lo tanto, el resultado de la llamada delegada.

Considere cuidadosamente la inicialización del contrato. De acuerdo con la variante proxy, es posible que las variables de estado no se puedan inicializar durante la construcción. Como resultado, existe una posible condición de carrera durante la inicialización que debe mitigarse.

Considere cuidadosamente los nombres de las funciones en el proxy para evitar la colisión de nombres de funciones. En su lugar, se llamarán las funciones de proxy con el mismo hash de Keccak que la función prevista, lo que podría conducir a un comportamiento impredecible o malicioso.

Gracias por el aviso. Como esta es la respuesta más reciente y más informativa, aunque el enlace solo responda, la marcaré como correcta porque la audiencia necesita comprender en detalle todos los aspectos de la capacidad de actualización.
¿Podemos agregar nuevas funciones en el contrato actualizado? @eth
@alper Una forma más sencilla que delegatecallusar ENS: ethereum.stackexchange.com/questions/77520/…
Muevo el marcador de respuesta correcto, ya que OpenZeppelin ahora proporciona una amplia documentación y herramientas al respecto.
@MikkoOhtamaa No hay problema. El cartel de la respuesta que ha aceptado lo publicó como respuesta a un par de otras preguntas también.

@Nick Johnson tiene un contrato base para contratos actualizables.

Como él dice , antes de usar uno se debe "comprender completamente las limitaciones y los inconvenientes".

/**
 * Base contract that all upgradeable contracts should use.
 * 
 * Contracts implementing this interface are all called using delegatecall from
 * a dispatcher. As a result, the _sizes and _dest variables are shared with the
 * dispatcher contract, which allows the called contract to update these at will.
 * 
 * _sizes is a map of function signatures to return value sizes. Due to EVM
 * limitations, these need to be populated by the target contract, so the
 * dispatcher knows how many bytes of data to return from called functions.
 * Unfortunately, this makes variable-length return values impossible.
 * 
 * _dest is the address of the contract currently implementing all the
 * functionality of the composite contract. Contracts should update this by
 * calling the internal function `replace`, which updates _dest and calls
 * `initialize()` on the new contract.
 * 
 * When upgrading a contract, restrictions on permissible changes to the set of
 * storage variables must be observed. New variables may be added, but existing
 * ones may not be deleted or replaced. Changing variable names is acceptable.
 * Structs in arrays may not be modified, but structs in maps can be, following
 * the same rules described above.
 */
contract Upgradeable {
    mapping(bytes4=>uint32) _sizes;
    address _dest;

    /**
     * This function is called using delegatecall from the dispatcher when the
     * target contract is first initialized. It should use this opportunity to
     * insert any return data sizes in _sizes, and perform any other upgrades
     * necessary to change over from the old contract implementation (if any).
     * 
     * Implementers of this function should either perform strictly harmless,
     * idempotent operations like setting return sizes, or use some form of
     * access control, to prevent outside callers.
     */
    function initialize();

    /**
     * Performs a handover to a new implementing contract.
     */
    function replace(address target) internal {
        _dest = target;
        target.delegatecall(bytes4(sha3("initialize()")));
    }
}

/**
 * The dispatcher is a minimal 'shim' that dispatches calls to a targeted
 * contract. Calls are made using 'delegatecall', meaning all storage and value
 * is kept on the dispatcher. As a result, when the target is updated, the new
 * contract inherits all the stored data and value from the old contract.
 */
contract Dispatcher is Upgradeable {
    function Dispatcher(address target) {
        replace(target);
    }

    function initialize() {
        // Should only be called by on target contracts, not on the dispatcher
        throw;
    }

    function() {
        bytes4 sig;
        assembly { sig := calldataload(0) }
        var len = _sizes[sig];
        var target = _dest;

        assembly {
            // return _dest.delegatecall(msg.data)
            calldatacopy(0x0, 0x0, calldatasize)
            delegatecall(sub(gas, 10000), target, 0x0, calldatasize, 0, len)
            return(0, len)
        }
    }
}

contract Example is Upgradeable {
    uint _value;

    function initialize() {
        _sizes[bytes4(sha3("getUint()"))] = 32;
    }

    function getUint() returns (uint) {
        return _value;
    }

    function setUint(uint value) {
        _value = value;
    }
}
Esta es mi solución favorita. ¡La respuesta debería ser votada más! Con la última bifurcación, tampoco hay necesidad de especificar los tamaños de los valores devueltos ( _sizes[bytes4(sha3("getUint()"))] = 32).
Cubrí este tema aquí: youtube.com/watch?v=KBqDYF5jw-0

Llegar a uno de los principios básicos en Ethereum que es un contrato inteligente no se puede modificar después de la implementación.

PERO , aún puede tener contratos inteligentes actualizables si tiene en cuenta lo siguiente

Esto tiene que ser planeado desde el principio. El punto clave es el número 4. Pero todos los demás son esenciales para tener una actualización de contrato inteligente real y sin problemas.

Por lo tanto, deberá diseñar su contrato inteligente teniendo en cuenta los siguientes 5 puntos:

  1. Mantenga sus contratos inteligentes modulares y reglas y lógica bastante separadas de la estructura de datos. Entonces, si necesita cambiar algo, cambiará solo el contrato relacionado y no necesitará cambiar muchos o todos los contratos.
  2. Debe estar preparado con una parada de emergencia o un disyuntor para poder detener todas las operaciones durante cualquier migración. Porque no desea estar en una situación en la que las personas aún puedan actualizar/insertar datos en la versión anterior del contrato inteligente mientras realiza la migración y posteriormente.
  3. Previamente , debe proporcionar la capacidad de leer todos los datos de su contrato inteligente . Por supuesto, puede hacer una lectura autorizada restringiendo la lectura de todos los datos al propietario o a cualquier otro usuario de confianza o incluso a otro contrato inteligente. Deberá leer la versión anterior de su contrato inteligente e insertar la nueva versión.
  4. Utilizará una de las siguientes estrategias para comunicarse con su contrato inteligente. Los copié de Smart Contact Best Practices :

Actualización de contratos rotos

Será necesario cambiar el código si se descubren errores o si es necesario realizar mejoras. No es bueno descubrir un error, pero no tener forma de lidiar con él.

...

Sin embargo, hay dos enfoques básicos que se utilizan con mayor frecuencia. El más simple de los dos es tener un contrato de registro que contenga la dirección de la última versión del contrato. Un enfoque más sencillo para los usuarios de contratos es tener un contrato que reenvíe llamadas y datos a la última versión del contrato.

Ejemplo 1: usar un contrato de registro para almacenar la última versión de un contrato

En este ejemplo, las llamadas no se reenvían, por lo que los usuarios deben buscar la dirección actual cada vez antes de interactuar con ella.

contract SomeRegister {
    address backendContract;
    address[] previousBackends;
    address owner;

    function SomeRegister() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner)
        _;
    }

    function changeBackend(address newBackend) public
    onlyOwner()
    returns (bool)
    {
        if(newBackend != backendContract) {
            previousBackends.push(backendContract);
            backendContract = newBackend;
            return true;
        }

        return false;
    }
}

Hay dos desventajas principales en este enfoque:

  1. Los usuarios siempre deben buscar la dirección actual, y cualquier persona que no lo haga corre el riesgo de utilizar una versión anterior del contrato.

  2. Deberá pensar detenidamente sobre cómo tratar los datos del contrato cuando reemplace el contrato.

El enfoque alternativo es tener un contrato de reenvío de llamadas y datos a la última versión del contrato:

Ejemplo 2: Usar DELEGATECALL para desviar datos y llamadas

contract Relay {
    address public currentVersion;
    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    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() {
        require(currentVersion.delegatecall(msg.data));
    }
}

Este enfoque evita los problemas anteriores pero tiene sus propios problemas. Debe tener mucho cuidado con la forma en que almacena los datos en este contrato. Si su nuevo contrato tiene un diseño de almacenamiento diferente al primero, sus datos pueden terminar corruptos. Además, esta versión simple del patrón no puede devolver valores de funciones, solo reenviarlos, lo que limita su aplicabilidad. ( Implementaciones más complejas intentan resolver esto con código ensamblador en línea y un registro de tamaños de retorno).

Independientemente de su enfoque, es importante tener alguna forma de actualizar sus contratos, o se volverán inutilizables cuando se descubran los errores inevitables en ellos.

Sin embargo, también recomiendo consultar las bibliotecas de proxy en Solidity publicadas por Zeppelin Solutions y Aragon. Hay una planificación para hacer un estándar de la industria para este asunto.

  1. Tienes que tener un buen ensayo de estrategias y tácticas . Porque el costo de actualizar su contrato inteligente realmente puede arruinar su vida.

Creé una historia en Medium para esto con el título: Consideración de diseño esencial para Ethereum dApps (1): Contratos inteligentes actualizables y proporcioné una muestra para cada punto de los 5 anteriores.

Nosotros (mi equipo y yo) hemos trabajado recientemente en el problema de los contratos actualizables después de consultar la publicación de colony.io sobre los contratos actualizables . Entonces, encontramos una solución en la que tenemos diferentes capas de contrato en lugar de tener un solo contrato.

Si lo describo brevemente, entonces es necesario que la parte de almacenamiento sea muy genérica para que, una vez que la cree, pueda almacenar todo tipo de datos en ella (con la ayuda de los métodos setter) y acceder a ella (con la ayuda de los métodos getter) . Eso hace que su almacenamiento de datos sea eterno, que no necesita cambiar en el futuro.

Mire este contrato de almacén de datos para comprenderlo mejor: https://goo.gl/aLmvJ5

La segunda capa debe ser el contrato principal con sus funciones, que se puede actualizar en un momento posterior y, para usar el antiguo almacén de datos, debe hacer el contrato de manera que pueda apuntar su contrato recién implementado al existente (antiguo) almacén de datos y luego puede eliminar el contrato anterior, después de que el nuevo contrato se comunique correctamente con el almacén de datos anterior.

Mire nuestra base de código para comprender cómo hemos implementado el contrato actualizable: https://goo.gl/p5zGEv

Nota: en el repositorio de GitHub anterior, estamos usando tres capas de contratos debido a nuestro caso de uso. Sin embargo, es posible hacer que el contrato sea actualizable solo con dos capas.

Espero que esto ayude.

Este es un repositorio útil. ¿podría compartir con la última versión de solidity?

zos presentó un marco para que implementemos fácilmente un contrato inteligente actualizable

PTAL: https://docs.zeppelinos.org/docs/start.html

Le permite tener un contrato con una dirección estable, pero con un comportamiento totalmente controlable y actualizable.

https://github.com/u2/ether-enrutador

https://github.com/ConsenSys/smart-contract-best-practices#upgrading-broken-contracts

Si bien esto puede responder teóricamente a la pregunta, sería preferible incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia.

En Blend , usamos ZeppelinOS para hacer que nuestros contratos inteligentes regulares de Ethereum sean actualizables. Aquí está nuestra guía paso a paso y código de muestra .

El problema real en el contrato inteligente actualizable es migrar los valores almacenados del contrato.

Una manera mucho mejor de crear un contrato inteligente actualizable es diferenciar su almacenamiento y lógica en diferentes contratos.

Guarde todos los datos de su contrato en un contrato inteligente que solo acepta llamadas de su contrato lógico.

Siga cambiando las lógicas de su contrato lógico. Sin embargo, debe ser muy visionario al definir las variables del contrato de almacenamiento.