Llamadas de contratos cruzados
El ejemplo de llamada de contrato cruzado demuestra cómo llamar a un contrato desde otro contrato.
En este ejemplo hay dos contratos que se compilan por separado, se despliegan por separado, y luego se prueban juntos. Hay varias maneras de desarrollar y probar contratos con dependencias en otros contratos, y el SDK y herramientas de Soroban aún están desarrollando las herramientas para admitir estos flujos de trabajo. Se agradece la retroalimentación aquí.
Ejecutar el ejemplo
Primero, pasa por el proceso de Configuración para configurar tu entorno de desarrollo, 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 cross_contract/contract_b
, y usa cargo test
.
cd cross_contract/contract_b
cargo test
Deberías ver la salida:
running 1 test
test test::test ... ok
Código
#[contract]
pub struct ContractA;
#[contractimpl]
impl ContractA {
pub fn add(x: u32, y: u32) -> u32 {
x.checked_add(y).expect("no overflow")
}
}
mod contract_a {
soroban_sdk::contractimport!(
file = "../contract_a/target/wasm32-unknown-unknown/release/soroban_cross_contract_a_contract.wasm"
);
}
#[contract]
pub struct ContractB;
#[contractimpl]
impl ContractB {
pub fn add_with(env: Env, contract: Address, x: u32, y: u32) -> u32 {
let client = contract_a::Client::new(&env, &contract);
client.add(&x, &y)
}
}
Ref: https://github.com/stellar/soroban-examples/tree/v21.6.0/cross_contract
Cómo funciona
Las llamadas de contratos cruzados se realizan invocando otro contrato por su ID de contrato.
Los contratos a invocar pueden ser importados a tu contrato utilizando contractimport!(file = "...")
. La importación generará código:
- Un tipo
ContractClient
que se puede usar para invocar funciones en el contrato. - Cualquier tipo en el contrato que haya sido anotado con
#[contracttype]
.
El macro contractimport!
generará los tipos en el módulo en el que se utiliza, por lo que es una buena idea usar el macro dentro de un bloque mod { ...
... }` o dentro de su propio archivo, para que los nombres de los tipos generados no colisionen con los nombres de los tipos en tu propio contrato.
Abre los archivos anteriores para seguir adelante.
Contrato A: El contrato a ser llamado
El contrato a ser llamado es el Contrato A. Es un contrato simple que acepta parámetros x
y y
, los suma y devuelve el resultado.
#[contract]
pub struct ContractA;
#[contractimpl]
impl ContractA {
pub fn add(x: u32, y: u32) -> u32 {
x.checked_add(y).expect("no overflow")
}
}
El contrato usa el método checked_add
para asegurarse de que no haya desbordamiento, y si hay desbordamiento, entra en pánico en lugar de devolver un valor desbordado. Los tipos de enteros primitivos de Rust tienen operaciones verificadas disponibles como funciones con el prefijo checked_
.
Contrato B: El contrato que realiza la llamada
El contrato que realiza la llamada es el Contrato B. Acepta un ID de contrato que llamará, así como los mismos parámetros a pasar. En muchos contratos, el contrato a llamar podría haberse almacenado como datos de contrato y ser recuperado, pero en este ejemplo simple se pasa como parámetro cada vez.
El contrato importa el Contrato A en el módulo contract_a
.
El contract_a::Client
se construye apuntando al ID de contrato pasado.
El cliente se usa para ejecutar la función add
con los parámetros x
y y
en el Contrato A.
mod contract_a {
soroban_sdk::contractimport!(
file = "../contract_a/target/wasm32-unknown-unknown/release/soroban_cross_contract_a_contract.wasm"
);
}
#[contract]
pub struct ContractB;
#[contractimpl]
impl ContractB {
pub fn add_with(env: Env, contract: Address, x: u32, y: u32) -> u32 {
let client = contract_a::Client::new(&env, &contract);
client.add(&x, &y)
}
}
Pruebas
Abre el archivo cross_contract/contract_b/src/test.rs
para seguir adelante.
#[test]
fn test() {
let env = Env::default();
// Register contract A using the imported Wasm.
let contract_a_id = env.register_contract_wasm(None, contract_a::Wasm);
// Register contract B defined in this crate.
let contract_b_id = env.register_contract(None, ContractB);
// Create a client for calling contract B.
let client = ContractBClient::new(&env, &contract_b_id);
// Invoke contract B via its client. Contract B will invoke contract A.
let sum = client.add_with(&contract_a_id, &5, &7);
assert_eq!(sum, 12);
}
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 A está registrado con el entorno utilizando el Wasm importado.
let contract_a_id = env.register_contract_wasm(None, contract_a::Wasm);
El Contrato B está registrado con el entorno utilizando el tipo de contrato.
let contract_b_id = env.register_contract(None, ContractB);
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 llevará el mismo nombre que el tipo de contrato con Client
añadido. Por ejemplo, en nuestro contrato el tipo de contrato es ContractB
, y el cliente se llama ContractBClient
. El cliente puede construirse y usarse de la misma manera en que se puede construir y usar el cliente generado para el Contrato A.
let client = ContractBClient::new(&env, &contract_b_id);
El cliente se usa para invocar la función add_with
en el Contrato B. El Contrato B invocará el Contrato A y el resultado será devuelto.
let sum = client.add_with(&contract_a_id, &5, &7);
La prueba verifica que el resultado devuelto sea el que esperamos.
assert_eq!(sum, 12);
Construir los contratos
Para construir el contrato en un archivo .wasm
, usa el comando stellar contract build
. Tanto contract_call/contract_a
como contract_call/contract_b
deben ser construidos, siendo primero contract_a
.
stellar contract build
Ambos archivos .wasm
deberían encontrarse en ambos directorios de target
del contrato después de construir ambos contratos:
target/wasm32-unknown-unknown/release/soroban_cross_contract_a_contract.wasm
target/wasm32-unknown-unknown/release/soroban_cross_contract_b_contract.wasm
Ejecutar el contrato
Si tienes stellar-cli
instalado, puedes invocar funciones de contrato. Ambos contratos deben ser desplegados.
- macOS/Linux
- Windows (PowerShell)
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/soroban_cross_contract_a_contract.wasm \
--id a
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/soroban_cross_contract_b_contract.wasm \
--id b
stellar contract deploy `
--wasm target/wasm32-unknown-unknown/release/soroban_cross_contract_a_contract.wasm `
--id a
stellar contract deploy `
--wasm target/wasm32-unknown-unknown/release/soroban_cross_contract_b_contract.wasm `
--id b
Invoca la función add_with
del Contrato B, pasando valores para x
y y
(por ejemplo, como 5
y 7
), y luego pasa el ID del contrato del Contrato A.
- macOS/Linux
- Windows (PowerShell)
stellar contract invoke \
--id b \
-- \
add_with \
--contract_id a \
--x 5 \
--y 7
stellar contract invoke `
--id b `
-- `
add_with `
--contract_id a `
--x 5 `
--y 7
La siguiente salida debería ocurrir usando el código anterior.
12
La función add_with
del Contrato B invocó la función add
del Contrato A para realizar la suma.