Actualizando el bytecode de Wasm para un contrato desplegado
Introducción
Actualizar un contrato inteligente te permite mejorar o modificar tu contrato sin cambiar su dirección. Esta guía te llevará a través del proceso de actualizar un contrato de bytecode de WebAssembly (Wasm) usando el Soroban SDK.
Requisitos previos:
- Comprensión básica del lenguaje de programación Rust. Para ponerte al día con Rust, revisa Rustlings o El libro de Rust.
- Familiaridad con contratos inteligentes de Stellar
- Tener instalado Stellar CLI y Soroban SDK
Descargar el ejemplo de contrato actualizable
El ejemplo de contrato actualizable demuestra cómo actualizar un contrato de Wasm.
[][oigp]
Código
El ejemplo contiene tanto un contrato "viejo" como uno "nuevo", donde actualizamos de "viejo" a "nuevo". El código a continuación es para el contrato "viejo".
#![no_std]
use soroban_sdk::{contractimpl, contracterror, contracttype, Address, BytesN, Env};
#[contracttype]
#[derive(Clone)]
enum DataKey {
Admin,
}
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Error {
AlreadyInitialized = 1,
}
#[contract]
pub struct UpgradeableContract;
#[contractimpl]
impl UpgradeableContract {
pub fn init(e: Env, admin: Address) {
if e.storage().instance().has(&DataKey::Admin) {
return Err(Error::AlreadyInitialized);
}
e.storage().instance().set(&DataKey::Admin, &admin);
Ok(())
}
pub fn version() -> u32 {
1
}
pub fn upgrade(e: Env, new_wasm_hash: BytesN<32>) {
let admin: Address = e.storage().instance().get(&DataKey::Admin).unwrap();
admin.require_auth();
e.deployer().update_current_contract_wasm(new_wasm_hash);
}
}
Cómo funciona
Al actualizar un contrato, la función clave utilizada es e.deployer().update_current_contract_wasm
, que toma el hash de Wasm del nuevo contrato como parámetro. Aquí tienes un desglose paso a paso de cómo funciona este proceso:
- No hay cambio en la ID del contrato: La ID del contrato permanece igual incluso después de la actualización. Esto asegura que todas las referencias al contrato permanezcan intactas.
- Autorización del administrador: Antes de actualizar, el contrato verifica si la acción está autorizada por un administrador. Esto es crucial para prevenir actualizaciones no autorizadas. Solo alguien con derechos de administrador puede realizar la actualización.
- La función de actualización: A continuación se muestra la función que maneja el proceso de actualización:
pub fn upgrade(e: Env, new_wasm_hash: BytesN<32>) {
let admin: Address = e.storage().instance().get(&DataKey::Admin).unwrap();
admin.require_auth();
e.deployer().update_current_contract_wasm(new_wasm_hash);
}
e: Env
: El objeto de entorno que representa el estado actual de la blockchain.new_wasm_hash: BytesN<32>
: El hash del nuevo código Wasm para el contrato. El bytecode de Wasm debe ya estar instalado/presente en el ledger.- La función primero recupera la dirección del administrador del almacenamiento del contrato.
- Luego requiere la autorización del administrador (
admin.require_auth()
) para proceder. - Finalmente, actualiza el contrato con el nuevo código Wasm (
e.deployer().update_current_contract_wasm(new_wasm_hash)
).
- La función de host
update_current_contract_wasm
también emitirá un evento de contratoSYSTEM
que contiene la referencia de wasm antigua y nueva, permitiendo a los usuarios posteriores ser notificados cuando un contrato que utilizan se actualiza. La estructura del evento tendrátopics = ["executable_update", old_executable: ContractExecutable, old_executable: ContractExecutable]
ydata = []
.
Pruebas
Abre el archivo upgradeable_contract/old_contract/src/test.rs
para seguir.
#![cfg(test)]
use crate::Error;
use soroban_sdk::{testutils::Address as _, Address, BytesN, Env};
mod old_contract {
soroban_sdk::contractimport!(
file =
"target/wasm32-unknown-unknown/release/soroban_upgradeable_contract_old_contract.wasm"
);
}
mod new_contract {
soroban_sdk::contractimport!(
file = "../new_contract/target/wasm32-unknown-unknown/release/soroban_upgradeable_contract_new_contract.wasm"
);
}
fn install_new_wasm(e: &Env) -> BytesN<32> {
e.install_contract_wasm(new_contract::Wasm)
}
#[test]
fn test() {
let env = Env::default();
env.mock_all_auths();
// Note that we use register_contract_wasm instead of register_contract
// because the old contracts Wasm is expected to exist in storage.
let contract_id = env.register_contract_wasm(None, old_contract::Wasm);
let client = old_contract::Client::new(&env, &contract_id);
let admin = Address::random(&env);
client.init(&admin);
assert_eq!(1, client.version());
let new_wasm_hash = install_new_wasm(&env);
client.upgrade(&new_wasm_hash);
assert_eq!(2, client.version());
// new_v2_fn was added in the new contract, so the existing
// client is out of date. Generate a new one.
let client = new_contract::Client::new(&env, &contract_id);
assert_eq!(1010101, client.new_v2_fn());
}
#[test]
fn test_cannot_re_init() {
let env = Env::default();
env.mock_all_auths();
// Note that we use register_contract_wasm instead of register_contract
// because the old contracts WASM is expected to exist in storage.
let contract_id = env.register_contract_wasm(None, old_contract::WASM);
let client = old_contract::Client::new(&env, &contract_id);
let admin = Address::generate(&env);
client.init(&admin);
// `try_init` is expected to return an error. Since client is generated from Wasm,
// this is a generic SDK error.
let err: soroban_sdk::Error = client.try_init(&admin).err().unwrap().unwrap();
// Convert the SDK error to the contract error.
let contract_err: Error = err.try_into().unwrap();
// Make sure contract error has the expected value.
assert_eq!(contract_err, Error::AlreadyInitialized);
}
Primero importamos archivos de Wasm para ambos contratos:
mod old_contract {
soroban_sdk::contractimport!(
file =
"target/wasm32-unknown-unknown/release/soroban_upgradeable_contract_old_contract.wasm"
);
}
mod new_contract {
soroban_sdk::contractimport!(
file = "../new_contract/target/wasm32-unknown-unknown/release/soroban_upgradeable_contract_new_contract.wasm"
);
}
Registramos el contrato viejo, lo inicializamos con un administrador y verificamos la versión que devuelve. La nota en el código a continuación es importante:
// Note that we use register_contract_wasm instead of register_contract
// because the old contracts Wasm is expected to exist in storage.
let contract_id = env.register_contract_wasm(None, old_contract::Wasm);
let client = old_contract::Client::new(&env, &contract_id);
let admin = Address::random(&env);
client.init(&admin);
assert_eq!(1, client.version());
Instalamos el Wasm del nuevo contrato:
let new_wasm_hash = install_new_wasm(&env);
Luego ejecutamos la actualización y verificamos que la actualización funcionó:
client.upgrade(&new_wasm_hash);
assert_eq!(2, client.version());
Crear el contrato
Para crear los archivos .wasm
del contrato, ejecuta stellar contract build
en upgradeable_contract/old_contract
y upgradeable_contract/new_contract
en ese orden.
Ambos archivos .wasm
deberían encontrarse en ambos directorios target
del contrato después de construir ambos contratos:
target/wasm32-unknown-unknown/release/soroban_upgradeable_contract_old_contract.wasm
target/wasm32-unknown-unknown/release/soroban_upgradeable_contract_new_contract.wasm
Ejecutar el contrato
Si tienes stellar-cli
instalado, puedes invocar funciones del contrato. Despliega el contrato viejo e instala el Wasm para el nuevo contrato.
Navega a upgradeable_contract/old_contract
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/soroban_upgradeable_contract_old_contract.wasm \
--source alice \
--network testnet
- Cuando despliegas un contrato inteligente en una red, necesitas especificar una identidad que se utilizará para firmar las transacciones. Cambia
alice
por tu propia identidad.
Deberías ver una ID de contrato similar después de haber ejecutado el comando de despliegue:
CAS6FKBXGVXFGU2SPPPJJOIULJNPMPR6NVKWLOQP24SZJPMB76TGH7Y3
Navega a upgradeable_contract/new_contract
y ejecuta el siguiente comando:
stellar contract install \
--source-account alice \
--wasm target/wasm32-unknown-unknown/release/soroban_upgradeable_contract_new_contract.wasm \
--network testnet
Deberías ver este hash de Wasm del comando de instalación:
aa24c81289997ad815489b29db337b53f284cca5aba86e9a8ae5cef7d31842c2
También necesitas llamar al método init
para que se establezca la dirección del admin
. Esto requiere que configuremos algunas identidades.
Dado que hemos configurado previamente una identidad, para obtener la dirección de identidad, ejecutamos el siguiente comando:
stellar keys address alice
Ejemplo de salida:
GCJ2R5ST4UQP2D4F54Y3IIAQKPMLMEEZCNZ3PEDKY4AGDYEMYUC2MOO7
Navega a upgradeable_contract/old_contract
y ahora llama a init
con esta clave (asegúrate de sustituirla por la clave que generaste):
stellar contract invoke \
--id CAS6FKBXGVXFGU2SPPPJJOIULJNPMPR6NVKWLOQP24SZJPMB76TGH7Y3 \
--source alice \
--network testnet \
-- \
init \
--admin GCJ2R5ST4UQP2D4F54Y3IIAQKPMLMEEZCNZ3PEDKY4AGDYEMYUC2MOO7
Nuestra dirección de old_contract
desplegada es CAS6FKBXGVXFGU2SPPPJJOIULJNPMPR6NVKWLOQP24SZJPMB76TGH7Y3
. Es posible que necesites reemplazar este valor por el tuyo. Invoca la función version
del contrato:
stellar contract invoke \
--id CAS6FKBXGVXFGU2SPPPJJOIULJNPMPR6NVKWLOQP24SZJPMB76TGH7Y3 \
--source alice \
--network testnet \
-- \
version
La siguiente salida debería ocurrir usando el código anterior:
1
Ahora actualiza el contrato. Nota que --source
debe ser el nombre de la identidad que coincide con la dirección pasada a la función init
.
stellar contract invoke \
--id CAS6FKBXGVXFGU2SPPPJJOIULJNPMPR6NVKWLOQP24SZJPMB76TGH7Y3 \
--source alice \
--network testnet \
-- \
upgrade \
--new_wasm_hash aa24c81289997ad815489b29db337b53f284cca5aba86e9a8ae5cef7d31842c2
Invoca la función version
nuevamente.
stellar contract invoke \
--id CAS6FKBXGVXFGU2SPPPJJOIULJNPMPR6NVKWLOQP24SZJPMB76TGH7Y3 \
--source alice \
--network testnet \
-- \
version
Ahora que el contrato fue actualizado, verás una nueva versión.
2
¡Hurra, nuestro contrato ha sido actualizado!
Guías en esta categoría:
📄️ Usar __check_auth de maneras interesantes
Dos guías que explican cómo usar __check_auth
📄️ Hacer llamadas entre contratos
Llamar a un contrato inteligente desde otro contrato inteligente
📄️ Desplegar un contrato a partir de bytecode Wasm instalado usando un contrato desplegador
Desplegar un contrato a partir de bytecode Wasm instalado usando un contrato desplegador
📄️ Desplegar un SAC para un activo Stellar utilizando código
Desplegar un SAC para un activo Stellar utilizando el SDK de Javascript
📄️ Organizar errores de contrato con un tipo de enumeración de errores
Gestionar y comunicar errores de contrato utilizando una estructura de enumeración almacenada como valores de Estado
📄️ Extender el TTL de un contrato desplegado con código
Cómo extender el TTL del código Wasm de un contrato desplegado utilizando el SDK de JavaScript
📄️ Actualizando el bytecode de Wasm para un contrato desplegado
Actualizar el bytecode de Wasm para un contrato desplegado
📄️ Escribir metadatos para tu contrato
Usa el contractmeta! macro en Rust SDK para escribir metadatos en contratos Wasm