Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 57 additions & 83 deletions docs/stylus/how-tos/exporting-abi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ interface IMyContract {

function setValue(uint256 new_value) external;

error Unauthorized(address caller);
error Unauthorized(address);
}
```

Expand All @@ -91,39 +91,16 @@ Generate JSON format ABI (requires `solc` installed):
cargo stylus export-abi --json > abi.json
```

Output:
The JSON output is produced by `solc`, so it includes `solc`'s header lines before the ABI array:

```json
[
{
"type": "function",
"name": "getValue",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "setValue",
"inputs": [
{
"name": "new_value",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}
]
```
======= <stdin>:IMyContract =======
Contract JSON ABI
[{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"new_value","type":"uint256"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]
```

Strip the two header lines before feeding the array to front-end tooling.

## Writing ABI-compatible contracts

### Basic contract structure
Expand Down Expand Up @@ -255,6 +232,7 @@ interface IMyContract {
Define custom errors with parameters:

```rust
use stylus_sdk::alloy_sol_types::sol;
use stylus_sdk::prelude::*;

sol! {
Expand All @@ -263,32 +241,39 @@ sol! {
error InvalidAmount();
}

#[derive(SolidityError)]
pub enum TokenError {
InsufficientBalance(InsufficientBalance),
Unauthorized(Unauthorized),
InvalidAmount(InvalidAmount),
}

#[public]
impl Token {
pub fn transfer(&mut self, to: Address, amount: U256) -> Result<(), InsufficientBalance> {
pub fn transfer(&mut self, to: Address, amount: U256) -> Result<(), TokenError> {
let sender = self.vm().msg_sender();
let balance = self.balances.get(sender);
if balance < amount {
return Err(InsufficientBalance {
return Err(TokenError::InsufficientBalance(InsufficientBalance {
account: sender,
requested: amount,
available: balance,
});
}));
}
// Transfer logic
Ok(())
}
}
```

Generated interface includes errors:
Generated interface includes errors. Note that the exported Solidity drops the error parameter names, keeping only their types:

```solidity
interface IToken {
function transfer(address to, uint256 amount) external;

error InsufficientBalance(address account, uint256 requested, uint256 available);
error Unauthorized(address caller);
error InsufficientBalance(address, uint256, uint256);
error Unauthorized(address);
error InvalidAmount();
}
```
Expand All @@ -298,6 +283,7 @@ interface IToken {
Events are automatically included in the ABI:

```rust
use stylus_sdk::alloy_sol_types::sol;
use stylus_sdk::prelude::*;

sol! {
Expand Down Expand Up @@ -435,7 +421,7 @@ sol_storage! {
#[public]
impl MyContract {
#[constructor]
pub fn new(owner: Address, initial_value: U256) {
pub fn new(&mut self, owner: Address, initial_value: U256) {
self.owner.set(owner);
self.initial_value.set(initial_value);
}
Expand All @@ -444,10 +430,10 @@ impl MyContract {
}
```

Export constructor signature:
Export constructor signature with the top-level `constructor` command (the constructor is not part of `export-abi` output):

```shell
cargo stylus export-abi constructor
cargo stylus constructor
```

Output:
Expand Down Expand Up @@ -478,29 +464,6 @@ constructor(address owner) payable

## Export configuration

### Custom license

Specify a custom SPDX license identifier:

```shell
cargo stylus export-abi --license=GPL-3.0
```

Output includes:

```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
```

### Custom pragma

Specify a custom Solidity version pragma:

```shell
cargo stylus export-abi --pragma="pragma solidity ^0.8.20;"
```

### Rust features

Export ABI with specific Rust features enabled:
Expand Down Expand Up @@ -708,17 +671,22 @@ This is the main function:
The `#[public]` macro generates ABI code:

```rust
// From stylus-proc/src/macros/public/export_abi.rs
// The macro generates an implementation roughly like this:
impl GenerateAbi for MyContract {
const NAME: &'static str = "MyContract";

fn fmt_abi(f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "interface I{} {{", Self::NAME)?;
// Generate function signatures
// Generated function signatures
write!(f, "\n function getValue() external view returns (uint256);")?;
writeln!(f, "}}")?;
writeln!(f, "\n}}")?;
Ok(())
}

fn fmt_constructor_signature(f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// Emits the `constructor(...)` line printed by `cargo stylus constructor`
write!(f, "constructor()")
}
}
```

Expand Down Expand Up @@ -795,7 +763,7 @@ cp abi.json ../frontend/src/abis/
When adding or modifying constructors, regenerate and commit:

```shell
cargo stylus export-abi constructor > CONSTRUCTOR.txt
cargo stylus constructor > CONSTRUCTOR.txt
git add CONSTRUCTOR.txt
git commit -m "Update constructor signature"
```
Expand Down Expand Up @@ -895,33 +863,34 @@ fn main() {

**Error**: `the trait AbiType is not implemented for MyType`

**Solution**: Use supported types or implement `AbiType`:
**Solution**: Use supported types, or derive `AbiType` for a custom struct:

```rust
// ✅ Use supported types
pub fn process(&self, amount: U256) -> U256 { }

// ❌ Custom types need AbiType implementation
// ❌ Arbitrary Rust types have no ABI representation
pub fn process(&self, amount: MyCustomType) -> MyCustomType { }
```

For custom types, implement `AbiType`:
For custom struct types, derive `AbiType` inside a `sol!` block. The struct can then be used in public method parameters and return values:

```rust
use stylus_sdk::abi::AbiType;

#[derive(Clone)]
struct MyType(U256);

impl AbiType for MyType {
type SolType = alloy_sol_types::sol_data::Uint<256>;
use stylus_sdk::alloy_sol_types::sol;
use stylus_sdk::prelude::*;

fn encode(&self) -> Vec<u8> {
self.0.encode()
sol! {
#[derive(AbiType)]
struct Point {
uint256 x;
uint256 y;
}
}

fn decode(data: &[u8]) -> Result<Self, alloy_sol_types::Error> {
U256::decode(data).map(MyType)
#[public]
impl MyContract {
pub fn echo_point(&self, p: Point) -> Point {
p
}
}
```
Expand Down Expand Up @@ -958,11 +927,16 @@ impl AbiType for MyType {

Export ABIs for all contracts in a workspace:

The `--contract` value is a cargo package name. Repeat the flag to select several contracts; with no `--contract`, the workspace's default contracts are exported.

```shell
# Export specific contract
# Export specific contract by package name
cargo stylus export-abi --contract=my-token

# Export all contracts
# Select several contracts in one invocation
cargo stylus export-abi --contract=token --contract=staking

# Export each to its own file
for contract in token staking governance; do
cargo stylus export-abi --contract=$contract > interfaces/I${contract^}.sol
done
Expand Down
52 changes: 25 additions & 27 deletions docs/stylus/how-tos/trait-based-composition.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ use stylus_sdk::{
};

// Define traits for different functionality
#[public]
trait IErc20 {
fn name(&self) -> String;
fn symbol(&self) -> String;
Expand All @@ -72,6 +73,7 @@ trait IErc20 {
fn transfer(&mut self, to: Address, value: U256) -> bool;
}

#[public]
trait IOwnable {
fn owner(&self) -> Address;
fn transfer_ownership(&mut self, new_owner: Address) -> bool;
Expand Down Expand Up @@ -181,44 +183,41 @@ When using trait-based composition, you need to be careful about function select
</VanillaAdmonition>

<CustomDetails summary="Selector issue example: ERC-721">

```rust
// In Solidity, both these functions would have different selectors:
// function safeTransferFrom(address from, address to, uint256 tokenId)
// function safeTransferFrom(address from, address to, uint256 tokenId, bytes data)

// In Rust, we need to use different method names, but want the same selectors: #[public]
impl<T: Erc721Params> Erc721<T> {
// Use the #[selector] attribute to specify the correct Solidity-compatible name #[selector(name = "safeTransferFrom")]
pub fn safe_transfer_from_with_data<S: TopLevelStorage + BorrowMut<Self>>(
storage: &mut S,
from: Address,
to: Address,
token_id: U256,
data: Bytes,
) -> Result<(), Erc721Error> {
// Implementation
}
// In Solidity, these two functions share the name `safeTransferFrom` but have
// different selectors because their parameter lists differ:
// function safeTransferFrom(address from, address to, uint256 tokenId)
// function safeTransferFrom(address from, address to, uint256 tokenId, bytes data)

// Rust has no method overloading, so each variant needs a distinct Rust name.
// Use the #[selector(name = "...")] attribute to export a Solidity-compatible
// selector. Because the two variants take different parameters, both can keep
// the `safeTransferFrom` name on the ABI side without colliding.
#[public]
impl Erc721 {
#[selector(name = "safeTransferFrom")]
pub fn safe_transfer_from(&mut self, from: Address, to: Address, token_id: U256) {
// Implementation here
}

// This method also needs the same selector name
#[selector(name = "safeTransferFrom")]
pub fn safe_transfer_from<S: TopLevelStorage + BorrowMut<Self>>(
storage: &mut S,
pub fn safe_transfer_from_with_data(
&mut self,
from: Address,
to: Address,
token_id: U256,
) -> Result<(), Erc721Error> {
// Implementation
data: Bytes,
) {
// Implementation here
}

}

```

</CustomDetails>

<VanillaAdmonition type="info" title="ABI generation and inheritance">
The Stylus SDK generates ABIs based on the methods that are available at the entrypoint contract.
When using trait-based composition, make sure that all methods you want exposed in the ABI are
properly included through the #[implements] attribute.
The Stylus SDK generates ABIs based on the methods that are available at the entrypoint contract. When using trait-based composition, make sure that all methods you want exposed in the ABI are properly included through the #[implements] attribute.
</VanillaAdmonition>

## Methods search order
Expand All @@ -235,4 +234,3 @@ In a typical composition chain:
- If not found there, it looks in the first trait specified in the inheritance list
- If still not found, it searches in the next trait in the list
- This continues until the method is found or all possibilities are exhausted
```
Loading
Loading