Saltar al contenido principal

3. Almacenar Datos

Ahora que hemos desarrollado un contrato básico de Hola Mundo, escribiremos un contrato simple que almacene y recupere datos. Esto te ayudará a ver los fundamentos del sistema de almacenamiento de Soroban.

Esto seguirá el ejemplo de incremento, que tiene una única función que incrementa un contador interno y devuelve el valor. Si quieres ver un ejemplo en funcionamiento, pruébalo en GitPod.

Este tutorial asume que ya has completado los pasos anteriores en Comenzar: Configuración, Hola Mundo, y Desplegar en Testnet.

Agregar el contrato de incremento

El comando stellar contract init nos permite inicializar un nuevo proyecto con cualquiera de los contratos de ejemplo del repositorio soroban-examples, usando el --with-example (o -w) flag.

No sobrescribirá archivos existentes, por lo que también podemos usar este comando para agregar un nuevo contrato a un proyecto existente. Ejecuta el comando nuevamente con un flag --with-example para agregar un contrato increment a nuestro proyecto. Desde dentro de nuestro directorio soroban-hello-world, ejecuta:

stellar contract init ./ --with-example increment

Esto creará un nuevo directorio contracts/increment con los siguientes archivos:

└── contracts
├── increment
├── Cargo.lock
├── Cargo.toml
└── src
├── lib.rs
└── test.rs

El siguiente código fue añadido a contracts/increment/src/lib.rs. Lo revisaremos en más detalle a continuación.

#![no_std]
use soroban_sdk::{contract, contractimpl, log, symbol_short, Env, Symbol};

const COUNTER: Symbol = symbol_short!("COUNTER");

#[contract]
pub struct IncrementorContract;

#[contractimpl]
impl IncrementorContract {
/// Increment an internal counter; return the new value.
pub fn increment(env: Env) -> u32 {
let mut count: u32 = env.storage().instance().get(&COUNTER).unwrap_or(0);

count += 1;

log!(&env, "count: {}", count);

env.storage().instance().set(&COUNTER, &count);

env.storage().instance().extend_ttl(100, 100);

count
}
}

mod test;

Importaciones

Este contrato comienza de manera similar a nuestro contrato de Hola Mundo, con una anotación para excluir la biblioteca estándar de Rust, e importaciones de los tipos y macros que necesitamos del crate soroban-sdk.

contracts/increment/src/lib.rs
#![no_std]
use soroban_sdk::{contract, contractimpl, log, symbol_short, Env, Symbol};

Claves de Datos del Contrato

const COUNTER: Symbol = symbol_short!("COUNTER");

Los datos del contrato están asociados con una clave, que se puede usar más tarde para buscar el valor.

Symbol es un tipo de cadena corto (de hasta 32 caracteres de longitud) con espacio de caracteres limitado (solo se permiten caracteres a-zA-Z0-9_). Los identificadores como nombres de funciones del contrato y claves de datos del contrato están representados por Symbols.

El macro symbol_short!() es una forma conveniente de pre-computar símbolos cortos de hasta 9 caracteres de longitud en el tiempo de compilación usando Symbol::short. Genera una constante de tiempo de compilación que se adhiere al conjunto de caracteres válidos de letras (a-zA-Z), números (0-9) y guiones bajos (_). Si un símbolo excede el límite de 9 caracteres, se debe utilizar Symbol::new para crear símbolos en tiempo de ejecución.

Acceso a Datos del Contrato

let mut count: u32 = env
.storage()
.instance()
.get(&COUNTER)
.unwrap_or(0); // If no value set, assume 0.

La función Env.storage() se utiliza para acceder y actualizar los datos del contrato. El contrato que se ejecuta es el único contrato que puede consultar o modificar los datos del contrato que ha almacenado. Los datos almacenados son visibles en el ledger donde sea que el ledger sea visible, pero los contratos que se ejecutan dentro del entorno Soroban están restringidos a sus propios datos.

La función get() obtiene el valor actual asociado con la clave del contador.

Si no hay un valor almacenado actualmente, se devuelve el valor dado a unwrap_or(...) en su lugar.

Los valores almacenados como datos del contrato y recuperados se transmiten desde el entorno y se expanden al tipo especificado. En este caso, un u32. Si el valor se puede expandir, el tipo devuelto será un u32. De lo contrario, si un desarrollador lo causó a ser de otro tipo, se produciría un pánico en el unwrap.

env.storage()
.instance()
.set(&COUNTER, &count);

La función set() almacena el nuevo valor del contador contra la clave, reemplazando el valor existente.

Gestionar TTLs de Datos del Contrato con extend_ttl()

env.storage().instance().extend_ttl(100, 100);

Todos los datos del contrato tienen un Tiempo de Vida (TTL), medido en ledgers, que debe ser extendido periódicamente. Si el TTL de una entrada no se extiende periódicamente, la entrada finalmente se convertirá en "archivada" Puedes aprender más sobre esto en el documento Archivado de Estado.

Por ahora, vale la pena saber que hay tres tipos de almacenamiento: Persistente, Temporal, y Instancia. Este contrato solo utiliza almacenamiento Instance: env.storage().instance(). Cada vez que se incrementa el contador, el TTL de este almacenamiento se extiende por 100 ledgers, o alrededor de 500 segundos. Cada vez que se incrementa el contador, el TTL de este almacenamiento se extiende por 100 ledgers, o aproximadamente 500 segundos.

Construir el contrato

Desde dentro de soroban-hello-world, ejecuta:

stellar contract build

Verifica que se haya construido:

ls target/wasm32-unknown-unknown/release/*.wasm

Deberías ver tanto hello_world.wasm como soroban_increment_contract.wasm.

Pruebas

La siguiente prueba ha sido añadida al archivo contracts/increment/src/test.rs.

contracts/incrementor/src/test.rs
use crate::{IncrementorContract, IncrementorContractClient};
use soroban_sdk::Env;

#[test]
fn increment() {
let env = Env::default();
let contract_id = env.register_contract(None, IncrementorContract);
let client = IncrementorContractClient::new(&env, &contract_id);

assert_eq!(client.increment(), 1);
assert_eq!(client.increment(), 2);
assert_eq!(client.increment(), 3);
}

Esto utiliza los mismos conceptos descritos en el ejemplo de Hola Mundo.

Asegúrate de que pase:

cargo test

Verás que esto ejecuta pruebas para todo el espacio de trabajo; tanto el contrato de Hola Mundo como el nuevo contrato de Incremento.

Si quieres ver la salida de la llamada a log!, ejecuta las pruebas con --nocapture:

cargo test -- --nocapture

Deberías ver la salida:

running 1 test
count: U32(0)
count: U32(1)
count: U32(2)
test test::incrementor ... ok

Llevarlo más lejos

¿Puedes averiguar cómo agregar la función get_current_value al contrato? ¿Qué tal funciones decrement o reset?

Resumen

En esta sección, agregamos un nuevo contrato a este proyecto, que utilizó las capacidades de almacenamiento de Soroban para almacenar y recuperar datos. También aprendimos sobre los diferentes tipos de almacenamiento y cómo gestionar sus TTLs.

A continuación aprenderemos un poco más sobre cómo desplegar contratos en la red Testnet de Soroban e interactuar con nuestro contrato incrementador usando la CLI.