A defining feature of programmable blockchains is the ability to call other contracts. Let’s explore how to do this in Starknet using Cairo.
We start by declaring an interface of the called contract by using the #[abi]
attribute:
1
2
3
4
#[abi]
trait IMintable {
fn mint_to(receiver: ContractAddress, amount: u128);
}
When compiled, the #[abi]
block is expanded into code that looks something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
trait IMintableDispatcherTrait<T> {
fn mint_to(self: T, receiver: ContractAddress, amount: u128);
}
#[derive(Copy, Drop)]
struct IMintableDispatcher {
contract_address: starknet::ContractAddress,
}
impl IMintableDispatcherImpl of IMintableDispatcherTrait::<IMintableDispatcher> {
fn mint_to(self: IMintableDispatcher, receiver: ContractAddress, amount: u128) {
// starknet::call_contract_syscall is called
// in the body of the function
}
}
#[derive(Copy, Drop)]
struct IMintableLibraryDispatcher {
class_hash: starknet::ClassHash,
}
impl IMintableLibraryDispatcherImpl of IMintableDispatcherTrait::<IMintableLibraryDispatcher> {
fn mint_to(self: IMintableLibraryDispatcher, receiver: ContractAddress, amount: u128) {
// starknet::syscalls::library_call_syscall is called
// in the body of the function
}
}
Behold the power of Cairo plugins ✨
When the compiler encounters the #[abi]
attribute, it executes the dispatcher plugin that generates all the boilerplate code for us. The generated code contains a new trait, two new structs and their implementation of this trait. We didn’t have to write anything else besides the initial interface declaration.
To do a regular contract call, we only need to instantiate the IMintableDispatcher
struct. It takes a contract_address
value, the address of an external contract which we want to interact with. We can now call any of the interface functions on this struct. Note if we declared IMintable
in a different scope, we’ll need to import it via use
.
Here’s a full example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#[abi]
trait IMintable {
fn mint_to(receiver: starknet::ContractAddress, amount: u128);
}
#[contract]
mod Altruist {
// importing IMintableDispacher and the trait into the module scope
use super::IMintableDispatcher;
use super::IMintableDispatcherTrait;
use starknet::contract_address_const;
use starknet::ContractAddress;
#[external]
fn mint_some(receiver: ContractAddress, amount: u128) {
// address of the contract we want to call
let token_addr: ContractAddress = contract_address_const::<0xc0ffee>();
// create a dispatcher using the token address
let token = IMintableDispatcher { contract_address: token_addr };
// call a function from the IMintable interface
token.mint_to(receiver, amount);
}
}
If we need to do a library call, we can do it in a similar manner using IMintableLibraryDispatcher
struct.
1
2
let token = IMintableLibraryDispatcher { class_hash: token_class_hash };
token.mint_to(...);