22 minutos de lectura
Resumen
Una regla inquebrantable a la hora de desarrollar en blockchains EVM es que los contratos inteligentes una vez desplegados no pueden modificarse. A menudo se dice que los contratos inteligentes son «inmutables», lo que garantiza que el código con el que interactúan los desarrolladores sea a prueba de manipulaciones y transparente. Esta filosofía beneficia a quienes interactúan con los contratos inteligentes, pero no siempre a quienes los escriben. Los desarrolladores que crean contratos inteligentes deben asegurarse siempre de que sean exhaustivos, estén libres de errores y cubran todos los casos extremos. Esto suele ser así, pero no siempre, y ahí es donde surge la necesidad de contar con contratos inteligentes actualizables.
Gracias al enfoque de los contratos inteligentes actualizables, si se produce un error, hay una lógica defectuosa o falta alguna funcionalidad en tu contrato, un desarrollador tiene la opción de actualizar dicho contrato inteligente e implementar uno nuevo para utilizarlo en su lugar.
En este tutorial, mostraremos exactamente cómo se hace esto creando e implementando desde cero un contrato inteligente actualizable con OpenZeppelin y Hardhat.
Qué vamos a hacer
- Crea un contrato inteligente actualizable utilizando el complemento «Hardhat Upgrades» de OpenZeppelin
- Compilar e implementar el contrato en Polygon PoS (red de pruebas Amoy) utilizando Hardhat 3
- Comprueba el proxy y la implementación en Polygonscan (Amoy)
- Actualiza el contrato y comprueba los resultados
Lo que necesitarás
- Node.js v22+ y un gestor de paquetes (npm, yarn o pnpm)
- Se ha añadido MetaMask con Polygon PoS (Amoy) (consulta la guía de MetaMask de Polygon)
- POL de la red de pruebas de Amoy procedente de un grifo (por ejemplo, el grifo de Quicknode Polygon Amoy)
- Un punto de conexión de Quicknode para Polygon PoS → Amoy (crea uno desde la página de Quicknode dedicada a Polygon PoS)
- Experiencia previa con Solidity
- Conocimientos sobre proxies actualizables (véase «Introducción a los contratos inteligentes actualizables»)
Configuración del entorno de desarrollo
Necesitaremos una nueva carpeta en nuestro equipo local donde se ubicará nuestro proyecto para este tutorial. La nuestra se llamará «UpgradeableContracts», pero puedes llamarla como quieras. Ejecuta estos comandos en tu terminal para crear la carpeta y acceder a ella:
mkdir ContratosActualizables
cd ContratosActualizables
¡Genial! Ahora que tenemos un lienzo en blanco sobre el que trabajar, pongámonos manos a la obra. Introduce el siguiente comando para crear tu proyecto Hardhat:
npx hardhat --init
Se te plantearán algunas preguntas:
- Si se te pregunta qué versión de Hardhat deseas utilizar, selecciona la opción «Hardhat 3», que viene seleccionada por defecto.
- Cuando se te solicite
¿Qué tipo de proyecto te gustaría poner en marcha?, selecciona elmocha-ethersopción. - Se te pedirá que instales las dependencias. Escribe
Yy pulsa Intro. - Si sigues las instrucciones que aparecen en pantalla, tu espacio de trabajo debería tener un aspecto similar a este:
Configuración de Hardhat
¡Enhorabuena! ¡Acabas de instalar e inicializar Hardhat con éxito!
Una vez completada la instalación, ya deberías tener todo lo necesario para desarrollar, probar e implementar contratos inteligentes en la cadena de bloques. Dado que vamos a trabajar con contratos inteligentes actualizables, tendremos que instalar algunas dependencias más. Ejecuta las siguientes líneas en tu terminal:
npm i -D @nomicfoundation/hardhat-ethers @nomicfoundation/hardhat-verify
npm i @openzeppelin/contracts dotenv
@openzeppelin/contracts es el paquete que nos permite implementar nuestros contratos inteligentes de forma que puedan actualizarse. @nomicfoundation/hardhat-verify es un complemento de Hardhat que nos permite verificar nuestros contratos en la cadena de bloques. Esto permite que cualquiera pueda interactuar con los contratos implementados y aporta transparencia. Utilizar el complemento de Hardhat es la forma más cómoda de verificar nuestros contratos. @nomicfoundation/hardhat-ethers es un complemento que integra la biblioteca ethers.js en Hardhat, lo que nos permite interactuar con la cadena de bloques.
Se utilizará el paquete dotenv para la gestión de variables de entorno. Crea un archivo .env en el directorio raíz de tu proyecto. Este archivo se utilizará para almacenar datos confidenciales, como tu clave privada y las claves de API. Asegúrate de añadir .env a tu archivo .gitignore para que no lo subas accidentalmente a un repositorio público si utilizas Git. Así es como debería quedar tu archivo .env:
RPC_URL="TU_URL_RPC_DE_QUICKNODE"
PRIVATE_KEY="TU_CLAVE_PRIVADA_DE_METAMASK"
ETHERSCAN_API_KEY="TU_CLAVE_API_DE_ETHERSCAN"
Enhorabuena si has conseguido seguir el tutorial hasta aquí. Acabas de configurar un entorno de desarrollo de contratos inteligentes con Hardhat y has instalado las dependencias adicionales que nos permitirán implementar y verificar contratos inteligentes actualizables.
Cómo acceder a un nodo de la red de pruebas Amoy de Polygon
Tendremos que desplegar nuestros contratos en la red de pruebas Polygon Amoy. Para ello, basta con registrarse aquí para obtener una prueba gratuita de Quicknode y crear un punto final de la red de pruebas Polygon Amoy.
Página «Puntos finales de Quicknode»
Copia la URL HTTPS y pégala en la variable RPC_URL de tu archivo .env. Si no tienes un archivo .env, crea uno en el directorio raíz de tu proyecto. Este archivo se utilizará para almacenar datos confidenciales, como tu clave privada y las claves de API.
A continuación, ve a tu perfil en Etherscan y accede a la pestaña «API KEYS». Si no tienes una cuenta, créate una aquí. Aquí crearás una clave API que te ayudará a verificar tus contratos inteligentes en la cadena de bloques. Copia la clave API y pégala en la variable ETHERSCAN_API_KEY de tu archivo .env.
Nota: Etherscan ha actualizado su API a la versión 2. Cuando creas una clave API en Etherscan, esta también se puede utilizar para varias cadenas compatibles con Etherscan, incluida Polygon. Por lo tanto, no es necesario crear una clave API independiente para PolygonScan.
Por último, entra en MetaMask y copia la clave privada de una de tus cuentas. Para saber cómo acceder a tu clave privada, consulta esta breve guía. Pega esta clave privada en la variable PRIVATE_KEY de tu archivo .env.
Además, necesitarás tener algunos MATIC de Amoy Testnet en tu cuenta para desplegar tus contratos. Puedes conseguir algunos en este grifo.
Por último, abre el archivo «hardhat.config.ts» y sustituye todo el código por este:
import type { HardhatUserConfig } from "hardhat/config";
import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import hardhatVerify from "@nomicfoundation/hardhat-verify";
import 'dotenv/config';
const config: HardhatUserConfig = {
plugins: [
hardhatToolboxMochaEthersPlugin,
hardhatVerify,
],
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts",
},
solidity: {
profiles: {
default: {
version: "0.8.22",
},
},
},
verify: {
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
}
},
networks: {
amoy: {
type: "http",
url: process.env.RPC_URL!,
accounts: [process.env.PRIVATE_KEY!],
},
},
};
export default config;
En las primeras líneas hemos importado varias bibliotecas que vamos a necesitar.
Para comprobar que todo funciona correctamente, guarda todos tus archivos y vuelve a compilar los contratos ejecutando el comando:
npx hardhat compile
Si has seguido todos los pasos correctamente, Hardhat compilará tus contratos y te mostrará un mensaje de confirmación. Ya estamos listos para implementar nuestros contratos.
Creación de nuestros contratos inteligentes
En esta sección, crearemos cuatro contratos inteligentes. Los dos primeros serán nuestros contratos de implementación, a los que llamaremos V1.sol y V2.sol. El tercer contrato será el TransparentUpgradeableProxy.sol, que es el contrato proxy que apuntará a nuestros contratos de implementación. El cuarto contrato será el ProxyAdmin.sol, que es el propietario del proxy y se utilizará para gestionar el contrato proxy.
Entra en la carpeta «contracts» y elimina el archivo «Counter.sol» que ya existe. Se trata de una plantilla de contrato inteligente predeterminada proporcionada por Hardhat y no la necesitamos. Crea cuatro archivos nuevos en la carpeta «contracts» y asígnales los nombres «V1.sol», «V2.sol», «TransparentUpgradeableProxy.sol » y «ProxyAdmin.sol». Pega el siguiente código en cada uno de los archivos, respectivamente.
cd contratos
touch V1.sol V2.sol TransparentUpgradeableProxy.sol ProxyAdmin.sol
- V1.sol
- V2.sol
- TransparentUpgradeableProxy.sol
- ProxyAdmin.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
contract V1 {
uint public number;
function initialValue(uint _num) external {
number=_num;
}
function increase() external {
number += 1;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
contract V2 {
uint public number;
function initialValue(uint _num) external {
number=_num;
}
function increase() external {
number += 1;
}
function decrease() external {
number -= 1;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
pragma solidity ^0.8.22;
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {IERC1967} from "@openzeppelin/contracts/interfaces/IERC1967.sol";
import {ProxyAdmin} from "./ProxyAdmin.sol";
/**
* @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
* does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch
* mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
* include them in the ABI so this interface must be used to interact with it.
*/
interface ITransparentUpgradeableProxy is IERC1967 {
/// @dev See {UUPSUpgradeable-upgradeToAndCall}
function upgradeToAndCall(address newImplementation, bytes calldata data) external payable;
}
/**
* @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance.
*
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand:
*
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself.
* 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to
* the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating
* the proxy admin cannot fallback to the target implementation.
*
* These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a
* dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to
* call a function from the proxy implementation. For this reason, the proxy deploys an instance of {ProxyAdmin} and
* allows upgrades only if they come through it. You should think of the `ProxyAdmin` instance as the administrative
* interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership.
*
* NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
* inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch
* mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
* fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
* implementation.
*
* NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a
* meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract.
*
* IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an
* immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be
* overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an
* undesirable state where the admin slot is different from the actual admin. Relying on the value of the admin slot
* is generally fine if the implementation is trusted.
*
* WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the
* compiler will not check that there are no selector conflicts, due to the note above. A selector clash between any new
* function and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This
* could render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency.
*/
contract TransparentUpgradeableProxy is ERC1967Proxy {
// An immutable address for the admin to avoid unnecessary SLOADs before each call
// at the expense of removing the ability to change the admin once it's set.
// This is acceptable if the admin is always a ProxyAdmin instance or similar contract
// with its own ability to transfer the permissions to another account.
address private immutable _admin;
/**
* @dev The proxy caller is the current admin, and can't fallback to the proxy target.
*/
error ProxyDeniedAdminAccess();
/**
* @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`,
* backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in
* {ERC1967Proxy-constructor}.
*/
constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
_admin = address(new ProxyAdmin(initialOwner));
// Set the storage value and emit an event for ERC-1967 compatibility
ERC1967Utils.changeAdmin(_proxyAdmin());
}
/**
* @dev Returns the admin of this proxy.
*/
function _proxyAdmin() internal view virtual returns (address) {
return _admin;
}
/**
* @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior.
*/
function _fallback() internal virtual override {
if (msg.sender == _proxyAdmin()) {
if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
revert ProxyDeniedAdminAccess();
} else {
_dispatchUpgradeToAndCall();
}
} else {
super._fallback();
}
}
/**
* @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}.
*
* Requirements:
*
* - If `data` is empty, `msg.value` must be zero.
*/
function _dispatchUpgradeToAndCall() private {
(address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
ERC1967Utils.upgradeToAndCall(newImplementation, data);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/transparent/ProxyAdmin.sol)
pragma solidity ^0.8.22;
import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
* explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
*/
contract ProxyAdmin is Ownable {
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address,address)`
* and `upgradeAndCall(address,address,bytes)` are present, and `upgrade` must be used if no function should be called,
* while `upgradeAndCall` will invoke the `receive` function if the third argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeAndCall(address,address,bytes)` is present, and the third argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev Sets the initial owner who can perform upgrades.
*/
constructor(address initialOwner) Ownable(initialOwner) {}
/**
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation.
* See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
* - If `data` is empty, `msg.value` must be zero.
*/
function upgradeAndCall(
ITransparentUpgradeableProxy proxy,
address implementation,
bytes memory data
) public payable virtual onlyOwner {
proxy.upgradeToAndCall{value: msg.value}(implementation, data);
}
}
Nota: Los contratos «TransparentUpgradeableProxy.sol» y «ProxyAdmin.sol» se han extraído directamente del repositorio de GitHub de OpenZeppelin. Puedes encontrar estos contratos aquí.
El contrato V1 es bastante sencillo. Tiene una variable de estado de tipo entero sin signo y dos funciones. La función initialValue() simplemente establece el valor inicial de la variable, mientras que la función increase() incrementa su valor en 1.
En un principio, nuestro proxy apunta a la implementación V1. Vamos a actualizarlo a la implementación V2. En el contrato V2, simplemente añadimos una función decrease(), que reducirá el valor de la variable en 1.
Guarda los archivos en los que has estado trabajando y vuelve a la terminal. Comprueba que te encuentras en el directorio del proyecto (por ejemplo, UpgradeableContracts) y, a continuación, ejecuta este comando en la terminal:
npx hardhat compile
Si lo has hecho todo correctamente, el terminal debería indicarte que ha compilado con éxito dos archivos Solidity. Ya estamos listos para configurar nuestras herramientas de implementación. En la siguiente sección aprenderás las mejores prácticas a la hora de implementar tus contratos.
Módulos de encendido Hardhat
Utilizaremos módulos de Hardhat Ignition para preparar el despliegue de nuestros contratos. Los módulos facilitan la configuración y la agrupación de las instancias de contratos inteligentes que deseamos desplegar. Crearemos un módulo llamado ProxyModule.
El ProxyModule describirá las implementaciones de la V1 y V2 contratos de ejecución, el Proxy transparente y actualizable contrato de representación, y el ProxyAdmin contrato. También fijaremos un valorInicial de 10 para el V1 contrato en este módulo.
En el encendido/módulos carpeta, elimina los archivos que ya existan Counter.ts archivo. Crea un archivo llamado proxyModule.ts y pega el siguiente código:
- proxyModule.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
const proxyModule = buildModule("ProxyModule", (m) => {
const proxyAdminOwner = m.getAccount(0);
const v1 = m.contract("V1");
const v2 = m.contract("V2");
const proxy = m.contract("TransparentUpgradeableProxy", [
v1,
proxyAdminOwner,
"0x",
]);
const proxyAdminAddress = m.readEventArgument(
proxy,
"AdminChanged",
"newAdmin"
);
const proxyAdmin = m.contractAt("ProxyAdmin", proxyAdminAddress);
return { implementation: v1, proxyAdmin, proxy };
});
const v1Module = buildModule("V1Module", (m) => {
const { implementation, proxy, proxyAdmin } = m.useModule(proxyModule);
const v1 = m.contractAt("V1", proxy);
m.call(v1, "initialValue", [10]);
return { implementation, v1, proxy, proxyAdmin };
});
export default v1Module;
Ahora que ya hemos creado nuestros módulos, ¡pasaremos a implementar nuestros contratos!
Implementación de los contratos
¡Ya estamos listos para implementar nuestro contrato inteligente actualizable! Ejecuta el siguiente comando para iniciar el proceso de implementación:
npx hardhat ignition deploy ignition/modules/ProxyModule.ts --red amoy --verify
El --red El parámetro «flag» especifica la red en la que queremos desplegar nuestros contratos. En este caso, los estamos desplegando en la red de pruebas Amoy de Polygon.
El --verificar La bandera verificará automáticamente nuestros contratos en Polygonscan una vez que se hayan implementado.
Nota: Si ya has realizado la implementación o ha surgido algún problema, añade el
--restablecerAñade este parámetro al comando anterior para forzar una nueva implementación.
Salida del terminal tras la implementación de nuestro módulo
Desglose de la producción
Si has seguido los pasos correctamente, deberías ver algo similar a la imagen de arriba en tu terminal. La salida del terminal muestra que el V1, V2, Proxy transparente y actualizable, y ProxyAdmin Los contratos se han implementado correctamente con sus respectivas direcciones.
| Nombre del contrato | Descripción |
|---|---|
| ProxyModule#V1 | La primera versión de nuestro contrato de implementación con un valor inicial y un incremento. Recordemos que en el módulo fijamos el valor inicial en 10. |
| ProxyModule#V2 | La segunda versión de nuestro contrato de implementación, con una función de reducción añadida. |
| ProxyModule#ProxyTransparenteActualizable | El contrato de proxy que apunta a nuestro contrato de implementación actual. Actualmente, apunta a la versión V1. |
| ProxyModule#ProxyAdmin | El contrato que permite actualizar el proxy. Solo el propietario de ProxyAdmin puede actualizar la implementación de TransparentUpgradeableProxy. |
IMPORTANTE: Asegúrate de tomar nota de la
ProxyModule#TransparentUpgradeableProxy, ProxyModule#V2, ProxyModule#ProxyAdmindirecciones en el marco de laDirecciones implementadasen la terminal, ya que los necesitaremos más adelante.
Quizá te estés preguntando qué está pasando exactamente entre bastidores. Hagamos una pausa para averiguarlo.
Echando un vistazo bajo el capó
Analizaremos con más detalle los contratos que hemos desplegado. En primer lugar, ve a MetaMask y copia la dirección pública de la cuenta que utilizaste para desplegar los contratos inteligentes. Abre el explorador de Amoy Testnet y busca la dirección de tu cuenta.
Verás que en tu cuenta se han realizado 4 transacciones:
- Las dos primeras operaciones consisten en la puesta en marcha de la
V1yV2contratos de ejecución. - La tercera operación consiste en la puesta en marcha de la
Proxy transparente y actualizableyProxyAdmincontratos - La cuarta transacción consiste en establecer el valor inicial de 10 en nuestro contrato proxy.
Página de transacciones de la cuenta de PolygonScan
Para ver cada contrato por separado, puedes hacer clic en el enlace «Creación de contratos » que aparece debajo del campo «Para » en la pestaña «Transacciones ».
Echemos un vistazo al Proxy transparente y actualizable contrato. Haz clic en el A enlace de la tercera transacción (resaltado arriba). Esto te llevará a la página del contrato. A continuación, dirígete a la Contrato pestaña con una marca de verificación. Deberías ver algo como esto:
Pestaña «Proxy de contrato» de PolygonScan
En esta página podemos ver el código fuente de nuestro Proxy transparente y actualizable contrato, lo que significa que se ha verificado correctamente. Pero, ¿cómo sabemos si la implementación de este contrato proxy es, efectivamente, la nuestra? V1 ¿Contrato? Haz clic en el Más opciones el menú desplegable situado justo encima del código fuente (como se muestra arriba). Haz clic en ¿Es esto un proxy?
Verificación del proxy de Polygonscan
A continuación, se te pedirá que verifiques el contrato en calidad de representante. Haz clic en Verificar. Aparecerá una ventana emergente en la que se mostrará la dirección de implementación. Verás que es la misma dirección que la de nuestro V1 ¡Contrato! Haz clic en Guardar luego vuelve a nuestra Proxy transparente y actualizable página del contrato. ¡Ahora deberías ver dos pestañas nuevas! Leer en nombre de y Escribir en nombre de otra persona.

Haz clic en el número función bajo el Leer en nombre de pestaña. Deberías ver el valor 10, que es el valor inicial que hemos establecido anteriormente. Si haces clic en Escribir en nombre de otra persona, verás el V1 funciones del contrato. Esto confirma que nuestro contrato proxy apunta efectivamente a nuestro V1 contrato de ejecución.
Desglose del contrato de representación
El ProxyModule se describieron varios contratos inteligentes que se van a implementar, a saber, el Proxy transparente y actualizable y ProxyAdmin contratos. Proxy transparente y actualizable es el contrato principal en este caso. Este contrato contiene todos los cambios en las variables de estado de nuestro contrato de implementación. Esto significa que el contrato de implementación no mantiene su propio estado y, de hecho, depende del contrato proxy para su almacenamiento.
A modo de ejemplo, busca el V1 la dirección del contrato que has anotado antes en el explorador de bloques Amoy. Si te diriges a la Contrato pestaña y haz clic en Leer el contrato, verás que el valor de la variable de estado número ¡es 0! Recordemos que vimos que el valor de número como 10 cuando lo leemos en el contrato proxy. Esto demuestra que el contrato de implementación no mantiene su propio estado, sino que actúa únicamente como un contrato lógico.
En este caso, el contrato de representación (Proxy transparente y actualizable) es el envoltorio de nuestro contrato de implementación (V1), y en caso de que necesitemos actualizar nuestro contrato inteligente (a través de ProxyAdmin), simplemente implementamos otro contrato y hacemos que nuestro contrato proxy apunte a ese contrato, actualizando así su estado y sus funcionalidades futuras. ¡Qué guay!
Al final, en realidad no modificamos el código de ninguno de nuestros contratos inteligentes, pero, desde el punto de vista del usuario, el contrato principal se ha actualizado. Este diagrama de flujo te ayudará a entenderlo mejor:
Diagrama de flujo de los contratos inteligentes actualizables
Actualización del contrato de la versión 1 a la versión 2
Ahora que ya tenemos una idea clara de lo que ocurre en el backend, ¡volvamos a nuestro código y actualicemos nuestro contrato! En la carpeta «scripts», crea un nuevo archivo llamado «upgrade.ts». En su interior, pega el siguiente código:
- upgrade.ts
import hre from "hardhat";
async function main() {
const { ethers } = await hre.network.connect({
network: "amoy",
});
const [currentAccount] = await ethers.getSigners();
console.log("Current account:", currentAccount.address);
const proxy = "<INSERT_YOUR_PROXY_ADDRESS_HERE>";
const proxyAdmin = "<INSERT_YOUR_PROXY_ADMIN_ADDRESS_HERE>";
const v2Implementation = "<INSERT_YOUR_V2_IMPLEMENTATION_ADDRESS_HERE>";
const proxyAdminContract = await ethers.getContractAt("ProxyAdmin", proxyAdmin);
const v2 = await ethers.getContractAt("V2", v2Implementation);
const encodedFunctionCall = v2.interface.encodeFunctionData("decrease");
console.log("Upgrading to V2...");
const upgradeTx = await proxyAdminContract.connect(currentAccount).upgradeAndCall(proxy, v2Implementation, encodedFunctionCall);
await upgradeTx.wait();
console.log("Upgraded to V2");
const v2Proxy = await ethers.getContractAt("V2", proxy);
const currentValue = await v2Proxy.number();
console.log("Current value after upgrade and call:", currentValue.toString());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
IMPORTANTE: Antes de ampliar nuestro contrato, no olvides pegar tu
Proxy transparente y actualizable,ProxyAdmin, yV2direcciones de contrato en las variables correspondientes del código anterior.
Ahora, ejecutemos este script en la terminal:
npx hardhat run scripts/upgrade.ts --red amoy
Desglose de la actualización
Básicamente, lo que ha pasado aquí es que hemos llamado a la actualizarYLlamar función dentro de la ProxyAdmin contrato: primero actualiza el contrato de implementación y, a continuación, llama al disminución función. Si compruebas la salida de la terminal, verás que el valor actual es ahora 9, lo que significa que el disminución La función se ha llamado correctamente tras la actualización. Para confirmar aún más que la llamada a la función «decrease» se ha realizado con éxito, si vuelves a la Leer en nombre de pestaña de la Proxy transparente y actualizable Si consultas el contrato en el explorador de bloques de Amoy, verás que el valor de número ¡Ya tiene 9 años!
- Salida del terminal
- Polygonscan se lee como proxy
> npx hardhat ejecutar scripts/actualizar.ts --red amoy
Compilación de tus contratos Solidity...
Compilado 4 archivos de Solidity con solc 0.8.22 (destinado a EVM: Shanghái)
Cuenta corriente: 0x779fE50d4A9d0Eb760dD0d315b91d6A5CF265FCC
Actualización a V2...
Actualización a V2
Valor actual tras la actualización y la llamada: 9

Ten en cuenta que solo la cuenta que ha implementado los contratos proxy puede llamar a la función de actualización, y ello por razones obvias. El Proxy transparente y actualizable El contrato ahora apunta a la dirección del contrato V2 recién implementado. Echa un vistazo al diagrama de flujo que aparece a continuación:
Diagrama de flujo de los contratos inteligentes actualizables
Ten en cuenta que la dirección del usuario que llama a una función concreta (msg.sender) es fundamental. La dirección determina todo el flujo lógico.
Si msg.sender es cualquier otro usuario que no sea el administrador, el contrato proxy simplemente delegará la llamada al contrato de implementación y se ejecutará la función correspondiente. Así, el contrato proxy llama a la función adecuada del contrato de implementación en nombre de msg.sender, el usuario final. Como se ha explicado anteriormente, el estado del contrato de implementación carece de importancia, ya que no cambia. Lo que sí cambia es el estado del contrato proxy, que se determina en función de lo que devuelve el contrato de implementación cuando se ejecuta la función requerida.
Esto significa que, si quien realiza la llamada no es un administrador, el contrato proxy ni siquiera considerará la posibilidad de ejecutar ningún tipo de función de actualización. Si quien realiza la llamada no es un administrador, la llamada se reenvía o delegado al contrato de ejecución sin más demora. A esto se le denomina un llamada delegada y es un concepto importante que hay que comprender. Sin embargo, si quien realiza la llamada es el administrador, en este caso, nuestro ProxyAdmin En este contrato, la llamada no se delega automáticamente, y se puede ejecutar cualquiera de las funciones del contrato proxy, incluida la función de actualización.
Comprobación de la actualización
Ahora, vuelve al explorador de bloques de Amoy y busca tu Proxy transparente y actualizable dirección del contrato. Ve a la Contrato pestaña y haz clic en Escribir en nombre de otra persona. Ahora deberías ver el nuevo V2 la dirección del contrato de implementación seguida de la anterior V1 dirección de implementación. También verás el disminución función disponible para su uso. ¡Esto nos indica que el contrato proxy ahora apunta al nuevo contrato de implementación! Aquí tienes una comparación del antes y el después:
- Antes de la actualización
- Tras la actualización


Y ya está. Acabas de implementar un contrato inteligente actualizable y, a continuación, lo has actualizado para incluir una nueva función. ¡Ahora sube el código a GitHub y presúmelo! Una última advertencia: ¿recuerdas que utilizamos un archivo .env para almacenar nuestros datos confidenciales? El objetivo de ese archivo era evitar que nuestros datos confidenciales se publicaran, lo que habría puesto en peligro nuestros activos en la cadena de bloques. Una vez que hayas comprobado que el nombre del archivo .env aparece en tu .gitignore, podrás subir tu código a GitHub sin preocupaciones, ya que no hay datos privados en tu archivo hardhat.config.ts.
Conclusión
Daos una palmadita en la espalda. Os lo habéis ganado. Este ha sido un tutorial bastante avanzado y, si lo habéis seguido al pie de la letra, ahora ya sabéis cómo implementar un contrato básico actualizable utilizando la biblioteca OpenZeppelin.
Suscríbete a nuestro boletín para recibir más artículos y guías sobre Polygon. Si tienes algún comentario, no dudes en ponerte en contacto con nosotros a través de X. Siempre puedes charlar con nosotros en nuestro servidor de Discord, donde encontrarás a algunos de los desarrolladores más geniales que jamás hayas conocido 😊
¡Nos encanta recibir comentarios!
Si tienes algún comentario o pregunta sobre esta documentación, háznoslo saber. ¡Nos encantaría saber tu opinión!