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 va a seguir junto con el ejemplo de incremento, que tiene una sola función que incrementa un contador interno y devuelve el valor. Si quieres ver un ejemplo funcional, 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
Además de crear un nuevo proyecto, el comando stellar contract init
también nos permite inicializar un nuevo espacio de trabajo de contrato dentro de un proyecto existente. En este ejemplo, vamos a inicializar un nuevo contrato y usar el indicador --name
para especificar el nombre de nuestro nuevo contrato, increment
.
Este comando no sobrescribirá archivos existentes a menos que pasemos explícitamente el indicador --overwrite
. Desde nuestro directorio soroban-hello-world
, ejecuta:
stellar contract init . --name increment
Esto crea un nuevo directorio contracts/increment
con código de marcador de posición en src/lib.rs
y src/test.rs
, que reemplazaremos con nuestro nuevo contrato de incremento y las pruebas correspondientes.
└── contracts
├── increment
├── Cargo.toml
├── Makefile
└── src
├── lib.rs
└── test.rs
Pasaremos por el código del contrato con más detalle a continuación, pero por ahora, reemplaza el código de marcador de posición en contracts/increment/src/lib.rs
con lo siguiente.
#![no_std]
use soroban_sdk::{contract, contractimpl, log, symbol_short, Env, Symbol};
const COUNTER: Symbol = symbol_short!("COUNTER");
#[contract]
pub struct IncrementContract;
#[contractimpl]
impl IncrementContract {
/// Increment increments an internal counter, and returns the value.
pub fn increment(env: Env) -> u32 {
let mut count: u32 = env.storage().instance().get(&COUNTER).unwrap_or(0);
log!(&env, "count: {}", count);
count += 1;
env.storage().instance().set(&COUNTER, &count);
env.storage().instance().extend_ttl(50, 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
.
#![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 Symbol
s.
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 increment.wasm
.
Pruebas
Reemplaza el código de marcador de posición en contracts/increment/src/test.rs
con el siguiente código de prueba de incremento.
#![cfg(test)]
use crate::{IncrementContract, IncrementContractClient};
use soroban_sdk::Env;
#[test]
fn test() {
let env = Env::default();
let contract_id = env.register(IncrementContract, ());
let client = IncrementContractClient::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 los eventos de registro de diagnóstico con los datos de conteo en la salida:
running 1 test
[Diagnostic Event] contract:CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM,
topics:[log],
data:["count: {}", 1]
[Diagnostic Event] contract:CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM,
topics:[log],
data:["count: {}", 2]
[Diagnostic Event] contract:CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM,
topics:[log],
data:["count: {}", 3]
test test::test ... 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.