Default Implementation Macro
Overview
The #[default_impl]
macro is a utility that simplifies the implementation of OpenZeppelin Stellar
contract traits by automatically generating default implementations for trait methods. This allows developers
to focus only on overriding the methods they need to customize, while the macro handles the rest.
Background
When using Soroban’s #[contractimpl]
macro, all methods (including default implementations) must be explicitly
included in the implementation block for them to be accessible to the generated client. This is due to how
Rust macros work - they cannot access default implementations of trait methods that are not in the scope of the macro.
The #[default_impl]
macro solves this problem by automatically generating the missing default implementations
for OpenZeppelin Stellar traits.
Supported Traits
The #[default_impl]
macro supports the following OpenZeppelin Stellar traits:
-
FungibleToken
-
FungibleBurnable
-
NonFungibleToken
-
NonFungibleBurnable
-
NonFungibleEnumerable
-
AccessControl
-
Ownable
The #[default_impl]
macro intentionally does not support the following traits:
-
FungibleAllowlist
-
FungibleBlocklist
-
NonFungibleRoyalties
This limitation is by design: authorization configurations require specific implementation tailored to each project’s security requirements. By requiring manual implementation of these traits, we ensure developers carefully consider and explicitly define their authorization logic rather than relying on generic defaults.
Usage
To use the [default_impl]
macro, place it above the [contractimpl]
macro when implementing one of the supported traits:
#[default_impl] // IMPORTANT: place this above `#[contractimpl]`
#[contractimpl]
impl NonFungibleToken for MyContract {
type ContractType = Base;
// Only override the methods you need to customize
// All other methods will be automatically implemented with their default behavior
}
How It Works
The #[default_impl]
macro:
-
Identifies which trait is being implemented
-
Determines which methods are explicitly defined by the user
-
Uses the user defined methods to overwrite the default implementations
-
Fills the rest of the methods (not defined by the user) with default implementations
-
Adds any necessary imports for the trait
This process ensures that all trait methods are available to the client generated by #[contractimpl]
, while allowing developers to only write the code they need to customize.
Examples
Fungible Token Example
use soroban_sdk::{contract, contractimpl, Address, Env};
use stellar_fungible::FungibleToken;
use stellar_default_impl::default_impl;
#[contract]
pub struct MyToken;
#[default_impl]
#[contractimpl]
impl FungibleToken for MyToken {
type ContractType = Base;
// Only override methods that need custom behavior
fn transfer(e: &Env, from: Address, to: Address, amount: i128) {
// custom transfer logic here
}
// All other FungibleToken methods will be automatically implemented
}