Tipos personalizados
El ejemplo de tipos personalizados demuestra cómo definir tus propias estructuras de datos que se pueden almacenar en el ledger, o utilizar como entradas y salidas para las invocaciones de contrato. Este ejemplo es una extensión del ejemplo de almacenamiento de datos.
Ejecutar el ejemplo
Primero pasa por el proceso de [Configuración] para tener tu entorno de desarrollo configurado, luego clona la etiqueta v21.6.0
del repositorio soroban-examples
:
git clone -b v21.6.0 https://github.com/stellar/soroban-examples
O, salta la configuración del entorno de desarrollo y abre este ejemplo en Gitpod.
Para ejecutar las pruebas del ejemplo, navega al directorio custom_types
, y usa cargo test
.
cd custom_types
cargo test
Deberías ver la salida:
running 1 test
test test::test ... ok
Código
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct State {
pub count: u32,
pub last_incr: u32,
}
const STATE: Symbol = symbol_short!("STATE");
#[contract]
pub struct IncrementContract;
#[contractimpl]
impl IncrementContract {
/// Increment increments an internal counter, and returns the value.
pub fn increment(env: Env, incr: u32) -> u32 {
// Get the current count.
let mut state = Self::get_state(env.clone());
// Increment the count.
state.count += incr;
state.last_incr = incr;
// Save the count.
env.storage().instance().set(&STATE, &state);
// Return the count to the caller.
state.count
}
/// Return the current state.
pub fn get_state(env: Env) -> State {
env.storage().instance().get(&STATE).unwrap_or(State {
count: 0,
last_incr: 0,
}) // If no value set, assume 0.
}
}
Ref: https://github.com/stellar/soroban-examples/tree/v21.6.0/custom_types
Cómo funciona
Los tipos personalizados se definen utilizando el atributo #[contracttype]
en una struct
o un enum
.
Abre el archivo custom_types/src/lib.rs
para seguir el ejemplo.
Tipo personalizado: Struct
Las structs se almacenan en el ledger como un mapa de pares clave-valor, donde la clave es una cadena de hasta 32 caracteres que representa el nombre del campo, y el valor es el valor codificado.
Los nombres de campo no deben tener más de 32 caracteres.
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct State {
pub count: u32,
pub last_incr: u32,
}
Tipo personalizado: Enum
El ejemplo no contiene enums, pero los enums también pueden ser tipos de contrato.
Los enums que contienen variantes unitarias y de tuplas se almacenan en el ledger como un vector de dos elementos, donde el primer elemento es el nombre de la variante del enum como una cadena de hasta 32 caracteres de longitud, y el valor es el valor si la variante tiene uno.
Solo se admiten variantes unitarias y variantes de un solo valor, como A
y B
a continuación.
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Enum {
A,
B(...),
}
Los enums que contienen valores enteros se almacenan en el ledger como el valor u32
.
#[contracttype]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum Enum {
A = 1,
B = 2,
}
Uso de tipos en funciones
Los tipos que han sido anotados con #[contracttype]
se pueden almacenar como datos de contrato y recuperar más tarde.
Los tipos también se pueden utilizar como entradas y salidas en las funciones del contrato.
pub fn increment(env: Env, incr: u32) -> u32 {
let mut state = Self::get_state(env.clone());
state.count += incr;
state.last_incr = incr;
env.storage().instance().set(&STATE, &state);
state.count
}
pub fn get_state(env: Env) -> State {
env.storage().instance().get(&STATE).unwrap_or(State {
count: 0,
last_incr: 0,
}) // If no value set, assume 0.
}
Pruebas
Abre el archivo custom_types/src/test.rs
para seguir el ejemplo.
#[test]
fn test() {
let env = Env::default();
let contract_id = env.register_contract(None, IncrementContract);
let client = IncrementContractClient::new(&env, &contract_id);
assert_eq!(client.increment(&1), 1);
assert_eq!(client.increment(&10), 11);
assert_eq!(
client.get_state(),
State {
count: 11,
last_incr: 10
}
);
}
En cualquier prueba, lo primero que siempre se necesita es un Env
, que es el entorno Soroban en el que se ejecutará el contrato.
let env = Env::default();
El contrato se registra con el entorno utilizando el tipo de contrato.
let contract_id = env.register_contract(None, IncrementContract);
Todas las funciones públicas dentro de un bloque impl
que está anotado con el atributo #[contractimpl]
tienen una función correspondiente generada en un tipo de cliente generado. El tipo de cliente tendrá el mismo nombre que el tipo de contrato con Client
añadido. Por ejemplo, en nuestro contrato, el tipo de contrato es IncrementContract
, y el cliente se llama IncrementContractClient
.
let client = IncrementContractClient::new(&env, &contract_id);
La prueba invoca la función increment
en el contrato registrado que hace que el tipo State
se almacene y se actualice un par de veces.
assert_eq!(client.increment(&1), 1);
assert_eq!(client.increment(&10), 11);
La prueba luego invoca la función get_state
para obtener el valor de State
que se almacenó, y puede verificar sus valores.
assert_eq!(
client.get_state(),
State {
count: 11,
last_incr: 10
}
);
Construir el contrato
Para crear el contrato, utiliza el comando stellar contract build
.
stellar contract build
Debería generarse un archivo .wasm
en el directorio target
:
target/wasm32-unknown-unknown/release/soroban_custom_types_contract.wasm
Ejecutar el contrato
Si tienes stellar-cli
instalado, puedes invocar funciones de contrato en el Wasm utilizando esto.
- macOS/Linux
- Windows (PowerShell)
stellar contract invoke \
--wasm target/wasm32-unknown-unknown/release/soroban_custom_types_contract.wasm \
--id 1 \
-- \
increment \
--incr 5
stellar contract invoke `
--wasm target/wasm32-unknown-unknown/release/soroban_custom_types_contract.wasm `
--id 1 `
-- `
increment `
--incr 5
La siguiente salida debería ocurrir utilizando el código anterior.
5
Ejecuta esto varias veces más con diferentes cantidades de incremento para observar cómo cambia el conteo.
Usa el stellar-cli
para inspeccionar qué es el contador después de algunas ejecuciones.
stellar contract read --id 1 --key STATE
STATE,"{""count"":25,""last_incr"":15}"