1. Hola Mundo
Una vez que hayas configurado tu entorno de desarrollo, estás listo para crear tu primer contrato inteligente.
Crear un nuevo proyecto
Crear un nuevo proyecto usando el comando init
para crear un proyecto soroban-hello-world
.
stellar contract init soroban-hello-world
El comando init
creará un proyecto de espacio de trabajo en Rust, usando la estructura recomendada para incluir contratos Soroban. Veamos la estructura del proyecto:
.
├── Cargo.lock
├── Cargo.toml
├── README.md
└── contracts
└── hello_world
├── Cargo.toml
└── src
├── lib.rs
└── test.rs
Cargo.toml
El archivo Cargo.toml
en la raíz del proyecto está configurado como Espacio de Trabajo en Rust, lo que nos permite incluir múltiples contratos inteligentes en un solo proyecto.
Espacio de Trabajo en Rust
El archivo Cargo.toml
establece los miembros del espacio de trabajo como todo el contenido del directorio contracts
y establece la versión de la dependencia soroban-sdk
del espacio de trabajo, incluyendo la característica testutils
, que permitirá generar utilidades de prueba para llamar al contrato en las pruebas.
[workspace]
resolver = "2"
members = [
"contracts/*",
]
[workspace.dependencies]
soroban-sdk = "20.3.2"
Los testutils
se habilitan automáticamente dentro de pruebas unitarias de Rust dentro del mismo crate que tu contrato. Si escribes pruebas desde otro crate, necesitarás requerir la característica testutils
para esas pruebas y habilitar la característica testutils
al ejecutar tus pruebas con cargo test --features testutils
para poder usar esas utilidades de prueba.
Perfil release
Configurar el perfil release
para optimizar la construcción del contrato es fundamental. Los contratos Soroban tienen un tamaño máximo de 64KB. Los programas en Rust, incluso los pequeños, sin estas configuraciones, casi siempre superan este tamaño.
El archivo Cargo.toml
tiene el siguiente perfil de lanzamiento configurado.
[profile.release]
opt-level = "z"
overflow-checks = true
debug = 0
strip = "symbols"
debug-assertions = false
panic = "abort"
codegen-units = 1
lto = true
Perfil release-with-logs
Configurar un perfil release-with-logs
puede ser útil si necesitas construir un archivo .wasm
que tenga habilitados los registros para imprimir registros de depuración al usar el stellar-cli
. Nota que esto no es necesario para acceder a registros de depuración en pruebas o para usar un depurador paso a paso.
[profile.release-with-logs]
inherits = "release"
debug-assertions = true
Consulta el ejemplo de registro para más información sobre cómo registrar.
Directorio de contratos
El directorio contracts
es donde vivirán los contratos Soroban, cada uno en su propio directorio. Ya hay un contrato hello_world
allí para ayudarte a comenzar.
Archivo Cargo.toml específico del contrato
Cada contrato debería tener su propio archivo Cargo.toml
, que depende del Cargo.toml
de nivel superior del que acabamos de hablar.
Aquí es donde podemos especificar información del paquete específica del contrato.
[package]
name = "hello-world"
version = "0.0.0"
edition = "2021"
publish = false
El crate-type
está configurado como cdylib
, que es necesario para construir contratos.
[lib]
crate-type = ["cdylib"]
doctest = false
También hemos incluido la dependencia soroban-sdk, configurada para usar la versión del Cargo.toml del espacio de trabajo.
[dependencies]
soroban-sdk = { workspace = true }
[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
Código fuente del contrato
Crear un contrato Soroban implica escribir código Rust en el archivo lib.rs
del proyecto.
Todos los contratos deben comenzar con #![no_std]
para asegurar que no se incluya la biblioteca estándar de Rust en la construcción. La biblioteca estándar de Rust es grande y no se adapta bien a ser desplegada en programas pequeños como los que se despliegan en blockchains.
#![no_std]
El contrato importa los tipos y macros que necesita del crate soroban-sdk
.
use soroban_sdk::{contract, contractimpl, symbol_short, vec, Env, Symbol, Vec};
Muchos de los tipos disponibles en programas típicos de Rust, como std::vec::Vec
, no están disponibles, ya que no hay asignador y no hay memoria en el heap en los contratos Soroban. El soroban-sdk
proporciona una variedad de tipos como Vec
, Map
, Bytes
, BytesN
, Symbol
, que todos utilizan la memoria y las capacidades nativas del entorno Soroban. Los valores primitivos como u128
, i128
, u64
, i64
, u32
, i32
, y bool
también se pueden usar. No se admiten flotantes y matemáticas de punto flotante.
Las entradas del contrato no deben ser referencias.
El atributo #[contract]
designa la estructura Contract como el tipo al que se asocian las funciones del contrato. Esto implica que la estructura tendrá funciones de contrato implementadas para ella.
#[contract]
pub struct HelloContract;
Las funciones del contrato se definen dentro de un bloque impl
para la estructura, que está anotado con #[contractimpl]
. Es importante notar que las funciones del contrato deben tener nombres con una longitud máxima de 32 caracteres. Además, si una función se pretende invocar desde fuera del contrato, debe estar marcada con el modificador de visibilidad pub
. Es común que el primer argumento de una función de contrato sea de tipo Env
, permitiendo acceder a una copia del entorno Soroban, que es típicamente necesario para varias operaciones dentro del contrato.
#[contractimpl]
impl HelloContract {
pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> {
vec![&env, symbol_short!("Hello"), to]
}
}
Uniendo esas piezas, un contrato simple se ve así.
#![no_std]
use soroban_sdk::{contract, contractimpl, symbol_short, vec, Env, Symbol, Vec};
#[contract]
pub struct HelloContract;
#[contractimpl]
impl HelloContract {
pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> {
vec![&env, symbol_short!("Hello"), to]
}
}
mod test;
Nota la línea mod test
al final, esto le dirá a Rust que compile y ejecute el código de prueba, que veremos a continuación.
Pruebas unitarias del contrato
Escribir pruebas para contratos Soroban implica escribir código Rust usando las instalaciones de prueba y la cadena de herramientas que usarías para probar cualquier código Rust.
Dado nuestro HelloContract, una prueba simple se verá así.
- contracts/hello_world/src/lib.rs
- contracts/hello_world/src/test.rs
#![no_std]
use soroban_sdk::{contract, contractimpl, symbol_short, vec, Env, Symbol, Vec};
#[contract]
pub struct HelloContract;
#[contractimpl]
impl HelloContract {
pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> {
vec![&env, symbol_short!("Hello"), to]
}
}
mod test;
#![cfg(test)]
use super::*;
use soroban_sdk::{symbol_short, vec, Env};
#[test]
fn test() {
let env = Env::default();
let contract_id = env.register_contract(None, HelloContract);
let client = HelloContractClient::new(&env, &contract_id);
let words = client.hello(&symbol_short!("Dev"));
assert_eq!(
words,
vec![&env, symbol_short!("Hello"), symbol_short!("Dev"),]
);
}
En cualquier prueba, lo primero que siempre se requiere 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 usando el tipo de contrato. Los contratos pueden especificar un ID de contrato fijo como el primer argumento, o proporcionar None
y se generará uno.
let contract_id = env.register_contract(None, Contract);
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 se nombrará igual que el tipo de contrato con Client
añadido. Por ejemplo, en nuestro contrato, el tipo de contrato es HelloContract
y el cliente se llama HelloContractClient
.
let client = HelloContractClient::new(&env, &contract_id);
let words = client.hello(&symbol_short!("Dev"));
Los valores devueltos por las funciones pueden ser afirmados:
assert_eq!(
words,
vec![&env, symbol_short!("Hello"), symbol_short!("Dev"),]
);
Ejecutar las pruebas
Ejecuta cargo test
y observa cómo se ejecuta la prueba unitaria. Deberías ver la siguiente salida:
cargo test
running 1 test
test test::test ... ok
Intenta cambiar los valores en la prueba para ver cómo funciona.
La primera vez que ejecutes las pruebas, es posible que veas la salida en el terminal de cargo compilando todas las dependencias antes de ejecutar las pruebas.
Construir el contrato
Para construir un contrato inteligente para desplegar o ejecutar, usa el comando stellar contract build
.
stellar contract build
Si obtienes un error como no se puede encontrar el crate para 'core'
, significa que no instalaste el objetivo wasm32 durante el paso de configuración. Puedes hacerlo ejecutando rustup target add wasm32-unknown-unknown
.
Este es un pequeño envoltorio alrededor de cargo build
que establece el objetivo en wasm32-unknown-unknown
y el perfil en release
. Puedes pensarlo como un acceso directo para el siguiente comando:
cargo build --target wasm32-unknown-unknown --release
Un archivo .wasm
se generará en el directorio target
, en target/wasm32-unknown-unknown/release/hello_world.wasm
. El archivo .wasm
es el contrato construido.
El archivo .wasm
contiene la lógica del contrato, así como las especificaciones / tipos de interfaz del contrato, que pueden ser importados a otros contratos que deseen llamarlo. Este es el único artefacto necesario para desplegar el contrato, compartir la interfaz con otros, o hacer pruebas de integración contra el contrato. Este es el único artefacto necesario para desplegar el contrato, compartir la interfaz con otros, o realizar pruebas de integración contra el contrato.
Optimizing Builds
Usa stellar contract optimize
para minimizar aún más el tamaño del .wasm
. Primero, reinstala stellar-cli con la característica opt
:
cargo install --locked stellar-cli --features opt
Luego construye un archivo .wasm
optimizado:
stellar contract optimize --wasm target/wasm32-unknown-unknown/release/hello_world.wasm
Esto optimizará y generará un nuevo archivo hello_world.optimized.wasm
en la misma ubicación que el archivo de entrada .wasm
.
Construir contratos optimizados solo es necesario al desplegar en una red con tarifas o al analizar y perfilar un contrato para hacerlo lo más pequeño posible. Si recién estás comenzando a escribir un contrato, estos pasos no son necesarios. Consulta Construir para obtener detalles sobre cómo construir para desarrollo.
Resumen
En esta sección, escribimos un contrato simple que se puede despliegar en una red Soroban.
A continuación, aprenderemos a desplegar el contrato HelloWorld en la red Testnet de Stellar e interactuar con él a través de RPC usando el CLI.