Recall that Stylus contracts are fully interoperable across all languages, including Solidity. The Stylus SDK provides tools for exporting a Solidity interface for your contract so that others can call it. This is usually done with the cargo stylus
[CLI tool][abi_export], but we’ll detail how to do it manually here.
The SDK does this automatically for you via a feature flag called export-abi
that causes the [#[external]
][external] and [#[entrypoint]
][entrypoint] macros to generate a main
function that prints the Solidity ABI to the console.
1cargo run --features export-abi --target <triple>
1cargo run --features export-abi --target <triple>
Note that because the above actually generates a main
function that you need to run, the target can’t be wasm32-unknown-unknown
like normal. Instead you’ll need to pass in your target triple, which cargo stylus
figures out for you. This main
function is also why the following commonly appears in the main.rs
file of Stylus contracts.
1#![cfg_attr(not(feature = "export-abi"), no_main)]
1#![cfg_attr(not(feature = "export-abi"), no_main)]
Here’s an example output. Observe that the method names change from Rust’s snake_case
to Solidity’s camelCase
. For compatibility reasons, onchain method selectors are always camelCase
. We’ll provide the ability to customize selectors very soon. Note too that you can use argument names like address
without fear. The SDK will prepend an _
when necessary.
1interface Erc20 {
2 function name() external pure returns (string memory);
3
4 function balanceOf(address _address) external view returns (uint256);
5}
6
7interface Weth is Erc20 {
8 function mint() external payable;
9
10 function burn(uint256 amount) external;
11}
1interface Erc20 {
2 function name() external pure returns (string memory);
3
4 function balanceOf(address _address) external view returns (uint256);
5}
6
7interface Weth is Erc20 {
8 function mint() external payable;
9
10 function burn(uint256 amount) external;
11}
sol_interface!
][sol_interface]This macro defines a struct
for each of the Solidity interfaces provided.
1sol_interface! {
2 interface IService {
3 function makePayment(address user) payable returns (string);
4 function getConstant() pure returns (bytes32)
5 }
6
7 interface ITree {
8 // other interface methods
9 }
10}
1sol_interface! {
2 interface IService {
3 function makePayment(address user) payable returns (string);
4 function getConstant() pure returns (bytes32)
5 }
6
7 interface ITree {
8 // other interface methods
9 }
10}
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
][Address] and returns a [B256
][B256].
1pub fn do_call(&mut self, account: IService, user: Address) -> Result<String, Error> {
2 account.make_payment(self, user) // note the snake case
3}
1pub fn do_call(&mut self, account: IService, user: Address) -> Result<String, Error> {
2 account.make_payment(self, user) // note the snake case
3}
Observe the casing change. [sol_interface!
][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
.