Arbitrum Stylus logo

Stylus by Example

Interfaces

Exporting a Solidity interface

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.