sol_interface!() { /* proc-macro */ }Expand description
Facilitates calls to other contracts.
This macro defines a struct for each of the Solidity interfaces provided.
sol_interface! {
interface IService {
function makePayment(address user) external payable returns (string);
function getConstant() external pure returns (bytes32);
}
interface ITree {
// other interface methods
}
}The above will define IService and ITree for calling the methods of the two contracts.
For example, IService will have a make_payment method that accepts an Address and
returns a B256.
Currently only functions are supported, and any other items in the interface will cause an
error. Additionally, each function must be marked external. Inheritance is not supported.
use alloy_primitives::Address;
use stylus_sdk::{
prelude::*,
stylus_core::{calls::errors::*, host::*},
};
pub fn do_call(host: &impl Host, account: IService, user: Address) -> Result<String, Error> {
let config = Call::new()
.gas(host.evm_gas_left() / 2) // limit to half the gas left
.value(host.msg_value()); // set the callvalue
account.make_payment(host, config, user) // note the snake case
}Observe the casing change. sol_interface! computes the selector based on the exact name
passed in, which should almost always be camelCase. For aesthetics, the rust functions will
instead use snake_case.
Note that structs may be used, as return types for example. Trying to reference structs using
the Solidity path separator (module.MyStruct) is supported and paths will be converted to
Rust syntax (module::MyStruct).
§Reentrant calls
Cross-contract calls automatically flush or clear the StorageCache to safeguard
state. This happens via the type system – no special feature flags are required.
sol_interface! {
interface IMethods {
function pureFoo() external pure;
function viewFoo() external view;
function writeFoo() external;
}
}
#[entrypoint]
#[storage]
struct Contract {}
#[public]
impl Contract {
pub fn call_pure(&self, methods: IMethods) -> Result<(), Vec<u8>> {
let cfg = Call::new();
Ok(methods.pure_foo(self.vm(), cfg)?) // `pure` methods might lie about not being `view`
}
pub fn call_view(&self, methods: IMethods) -> Result<(), Vec<u8>> {
let cfg = Call::new();
Ok(methods.view_foo(self.vm(), cfg)?)
}
pub fn call_write(&mut self, methods: IMethods) -> Result<(), Vec<u8>> {
let cfg = Call::new_mutating(self);
Ok(methods.write_foo(self.vm(), cfg)?)
}
}Another example of making a mutable, payable call to a contract using the sol_interface!
macro.
use alloy_primitives::Address;
use stylus_sdk::{
prelude::*,
stylus_core::{calls::errors::*, host::*},
};
pub fn do_call(
host: &impl Host,
account: IService, // serializes as an Address
user: Address,
) -> Result<String, Error> {
let config = Call::new()
.gas(evm::gas_left() / 2) // limit to half the gas left
.value(msg::value()); // set the callvalue
account.make_payment(host, config, user) // note the snake case
}Note that in the context of a #[public] call, the &mut impl argument will
correctly distinguish the method as being write or payable.