sábado, septiembre 10, 2022

BlockChain & SmartContracts: Actualizar SmartContracts como los grandes protocolos

En un anterior artículo hablamos de cómo se pueden actualizar los SmartContracts por medio del patrón proxy y algunos problemas que pueden derivarse de su uso; hoy voy a mostraros como los grandes protocolos y DAOs (Decentralized Autonomous Organizations) implementan este patrón en SmartContracts de Solidity y más importante aún cómo mantener esa descentralización a pesar de que la lógica del contrato sea alterable.

Figura 1: BlockChain & SmartContracts - Actualizar
SmartContracts como los grandes protocolos.
“Photo of an Ethereum smart contract being replaced” por Dall·E.

Claro ahora podrías pensar:

“Oye, pero si la gracia de los SmartContracts estaba en que estos eran inmutables y no se puede alterar ni sus valores ni funcionamiento, ahora se pueden manipular a gusto propio".

Y sí, en parte tendrías razón, por eso actualizar un contrato mediante un proxy puede ser peligroso y no se aconseja más que en grandes protocolos que pueden tener problemas con su lógica en un futuro.

¿Descentralización y proxies es posible?   

Pero aun así la descentralización se puede mantener, hasta ahora el concepto de descentralización y seguridad que hemos visto que tienen los SmartContracts reside en el hecho de que estos son inmutables y se ejecutan en la Blockchain. Con los proxies pasamos esa seguridad a una entidad central encargada de actualizar los contratos, sin embargo ese poder se puede distribuir entre la comunidad de personas que los usan, relegando así a la comunidad usuaria de estos el derecho a actualizarlos mediante por ejemplo sistemas de votación con tokens, NFTs, reputación

Figura 2: Libro dedicado a "Bitcoin: La tecnología Blockchain y su investigación"
de Yaiza Rubio y Félix Brezo en 0xWord

En los grandes protocolos descentralizados se suele constituir una DAO a la que se le pasa el control de los contratos y ya será esta quien gobernada por la comunidad tomaŕa las decisiones sobre los contratos. Miremos el ejemplo de UniSwap uno de los mayores DEX (Exchange Descentralizado) que existen, su funcionamiento se divide en tres:

1. El exchange: que sirve para hacer swapping entre diferentes tokens y aportar liquidez.

Figura 3: Página de Swapping de UniSwap entre tokens

2. El sistema de gobernanza del protocolo: un conjunto de SmartContracts que junto con el token $UNI conforman el control del protocolo. El funcionamiento de este sistema de gobernanza es bastante complejo y daría para un artículo bastante largo, por lo que podemos dejarlo en que se vota y propone usando $UNI.

Figura 4: Web app de gobernanza de UniSwap

3. La DAO de la comunidad de Uniswap: aunque la mayoría de las acciones que se hacen son off-chain todo el sistema de proponer cambios se hace a través del sistema de votación on-chain.

Esta es una de las maneras en las que se puede mantener la “descentralización” en un protocolo pero hay otras muchas con sus ventajas e inconvenientes pero lo que sí que podéis tener claro es que si veis alguno que no tiene sistema de gobernanza pero sus SmartContracts son actualizables huid de él porque en cualquier momento pueden robar los fondos, manipular los datos…

¿Cómo implementan los proxies los protocolos y DAOs?

Al final el patrón proxy es el patrón más usado para actualizar la lógica de los SmartContracts, implementarlo a mano puede ser tedioso y complejo dado que hay que tener en cuenta cosas como las colisiones de almacenamiento…(en el anterior artículo lo tratamos en mayor profundidad) y por ello es que se han creado herramientas y librerías que nos facilitan la vida a los desarrolladores solucionando en parte problemas de los que hablamos antes.
 
Os voy a mostrar dos formas de hacerlo; una fácil desde el punto de vista de implementación que solo requerirá de un par de cambios en nuestro SmartContract y otra más complicada que exigirá más cambios. Os voy a mostrar las dos porque la primera de ellas nos va a dar un control muy vago de lo que está sucediendo y la última nos permite visualizar de manera más clara lo que estamos haciendo.

1.- Forma “fácil”:

Esta manera es “sencilla” de cara a que no tenemos que hacer nada más que cambiar un par de variables, todo nos lo va ha hacer un módulo de Openzeppelin montado sobre la herramienta de Hardhat, veamos un ejemplo simple.

Figura 5: Proyecto de Solidity usando Hardhat

Para el ejemplo crearemos un proyecto nuevo de JavaScript con Yarn, Npm, Pnpm… u otro gestor de paquetes de NodeJs, e instalamos las siguientes dependencias, dentro de la consola:
“””
yarn init -y
yarn add hardhat @openzeppelin/contracts @openzeppelin/hardhat-upgrades
yarn hardhat
“”
Ahora seleccionaremos las siguientes opciones en la consola:

Figura 6: Selección de opciones en HardHat
  • “Create a JavaScript project”
  • Le damos a “y” a todo lo que nos pregunte HardHat
A continuación añadimos el siguiente contrato que nos servirá de prueba dentro de la carpeta “contracts”.
“
pragma solidity ^0.8.0;

contract Box {
    uint256 private _value;
    event ValueChanged(uint256 value);

    function store(uint256 value) public {
        _value = value;
        emit ValueChanged(value);
    }

    function retrieve() public view returns (uint256) {
        return _value;
    }
}
“
A continuación creamos un script de JavaScript para desplegar el contrato, vamos a modificar un poco el típico script que nos da Hardhat por defecto, dentro del ejemplo que nos da Hardhat escribimos lo siguiente:
“
const { ethers, upgrades } = require("hardhat");

async function main() {
  const Box = await ethers.getContractFactory("Box");
  console.log("Deploying Box...");
  const box = await upgrades.deployProxy(Box, [42], { initializer: "store" });
  await box.deployed();
  console.log("Box deployed to:", box.address);
}

main();
“
Lanzamos el siguiente comando y copiamos el address que nos muestra la consola.
“
yarn hardhat node (en una nueva consola)
yarn hardhat run [nombreScritp].js --network localhost
”
Ahora tenemos el contrato desplegado mediante un Proxy sin que tengamos que haber hecho nada especial más que instalar un par de librerías, guay eh, pues ahora vamos a actualizar mediante ese Proxy el contrato, para ello creamos una nueva versión del SmartContract y del Script. Al nuevo SmartContract lo podemos llamar “Box2” ya que es la segunda versión y este ahora más que guardar un solo valor le vamos a permitir cambiar ese valor incrementándolo al llamar a una función “storeIncrement”.
“
pragma solidity ^0.8.0;
contract Box2{
    uint256 private _value;
    uint256 private _increments;
    event ValueChanged(uint256 value);
    function store(uint256 value) public {
        _value = value;
        emit ValueChanged(value);
    }
    function storeIncrement(uint256 value) public {
        _increments = value;
    }
    function retrieveIncrement() public view returns (uint256) {
        return _increments;
    }
    function retrieve() public view returns (uint256) {
        return _value;
    }
    function increment() public {
        _value = _value + 1;
        emit ValueChanged(_value);
    }
}
”
Y dentro del script nuevo insertamos:
“
const { ethers, upgrades } = require("hardhat");

async function main() {
  const BoxV2 = await ethers.getContractFactory("Box2");
  console.log("Upgrading Box...");
  await upgrades.upgradeProxy(
    "[addressContratoDesplegado]",
    BoxV2
  );
  console.log("Box upgraded");
}

main();
”
Simplemente tenemos que lanzar el siguiente comando y ya tendremos actualizado nuestro SmartContract.
“
yarn hardhat run upgrade_box.js --network localhost
”
Como veis es una forma relativamente sencilla de actualizar un SmartContract sin tener que cambiar nada de este y sin siquiera saber que es un Proxy en la EVM, pero esto lleva consigo unos contras como por ejemplo no llevar el control real del “layout” del almacenamiento del contrato lo que puede ser catastrófico a la hora de actualizar el contrato a una nueva versión.

2.- Forma difícil:

La anterior herramienta nos ofrece muchas facilidades para actualizar nuestros SmartContracts sin embargo en grandes proyectos necesitamos tener un mayor control y conocimiento sobre el SmartContract y no usar una herramienta que “hace magia” para actualizar los SmartContractsLa solución es utilizar librerías de Solidity que solo añaden código y no modifican nuestro SmartContract. Para después ser nosotros quien realice el despliegue y las actualizaciones para saber en todo momento qué es lo que ocurre.

Figura 7: Modelo Proxy en Smart Contracts

Recordemos que a la hora de actualizar SmartContracts mediante proxies tenemos que tener en cuenta que el contrato que despleguemos va a ser solo lógica y que esta va a interactuar para modificar el almacenamiento de otro contrato, visualmente se vería como la imagen de abajo. Si por ejemplo tuviéramos un SmartContract como el siguiente
“
pragma solidity ^0.8.0;

contract Box {
    uint256 private _value = 54;
// … rest go above
}
“
Podríamos observar que configuramos una variable con un valor por defecto. Lo que hace el compilador a la hora de transformar nuestro SmartContract en Bytecode es añadir una función, llamémosla “firstInit”, que se ejecutará al subir este contrato a la cadena de Blockchain actualizando el valor de las variables al que hayamos especificado, lo mismo sucede con las variables en las que definamos un valor en el constructor.

Ahora os pregunto, imaginando que ya tenemos una versión v1 de nuestro SmartContract desplegado mediante un proxy, si al actualizar el SmartContract (de la lógica, es decir, éste) subiendo uno nuevo que añade otros valores en el constructor, ¿estos nuevos valores tendrán efecto en el proxy que guarda todo el almacenamiento o en el contrato de la lógica en el que no sirve de nada guardar valores dado que estos no se van a usar?

La respuesta es la segunda, estos valores se guardarán en el contrato de la lógica lo que no tiene sentido hacer porque entonces nunca usaremos estas variables, es por ello que a la hora de escribir SmartContracts que van a desplegarse mediante proxy hay que eliminar todas las variables que iniciemos en el constructor o en el cuerpo del contrato. Podemos crear una función que actualice esos valores y llamarla a través del proxy una vez se haya actualizado el contrato para que este tenga efecto:
“
pragma solidity ^0.8.0;

contract Box {
    uint256 private _value;
    function initialize(){
	private_value = 54;
   }
}
“
Y lo mismo tenemos que hacer con los contratos grandes. Si usamos estándares ya definidos como el ERC20 o el ERC721 ya existen versiones de estos con los cambios necesarios para actualizarlos mediante proxies, las librerías de Openzeppelin son un buen punto de partida. Si queréis ver cómo se implementan estás aquí os dejo un tutorial.

Conclusión  

Como podéis ver,  el patrón proxy es un patrón complejo que aporta mucha utilidad, pero que para poder mantener esa descentralización es necesario que una comunidad respalde por detrás el proyecto. Nos vemos en los siguientes artículos, pero tienes muchos más artículos sobre este mundo Web3 aquí mismo:
Saludos,

AutorChema Garabito. Desarrollador Full-Stack. Ideas Locas Telefónica CDO.

No hay comentarios:

Entrada destacada

Tu Latch "Hack Your Innovation Contest": Haz un PoC & Hack por 1.000 €

El pasado Telefónica Innovation Day 2024 lanzamos oficialmente el " Tu Latch Hack Your Innovation Contest " en el que repartimos ...

Entradas populares