Saltar al contenido principal

Llamadas de contratos cruzados

El ejemplo de llamada de contrato cruzado demuestra cómo llamar a un contrato desde otro contrato.

Abrir en Gitpod

información

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

cross_contract/contract_a/src/lib.rs
#[contract]
pub struct ContractA;

#[contractimpl]
impl ContractA {
pub fn add(x: u32, y: u32) -> u32 {
x.checked_add(y).expect("no overflow")
}
}
cross_contract/contract_b/src/lib.rs
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].
consejo

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.

cross_contract/contract_a/src/lib.rs
#[contract]
pub struct ContractA;

#[contractimpl]
impl ContractA {
pub fn add(x: u32, y: u32) -> u32 {
x.checked_add(y).expect("no overflow")
}
}
consejo

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.

cross_contract_calls/src/a.rs
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.

cross_contract/contract_b/src/test.rs
#[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.

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.

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.