OpenZeppelin / OpenZeppelin/stellar-contracts
🏗️ [Core Feature]: Route `mint` through the contract type via a `MintOverrides` trait
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 95
- Forks
- 68
- Avg merge
- 4d 42m
- Merged PRs (30d)
- 20
Description
What is the key feature we're aiming to implement?
Follow-up to #884 / #885 and the discussion around the capped extension.
Problem
mint is the only state-changing token operation that is not part of any trait, so there is no single dispatch point for it. Today ten contract types carry an inherent mint(e, &to, amount) (Base, TotalSupply, FungibleVotes, RWA, the three votes combinations and the three supply combinations), while AllowList, BlockList and AllowBlockList have none. Consequences:
Self::ContractType::mintcompiles for someComposelists and not for others. The examples disagree accordingly:fungible-blocklistcallsBase::mint,fungible-allowlistcalls<Self as FungibleToken>::ContractType::mint.- Calling
Base::minton a supply-tracking contract type silently drifts the total supply; a later burn then panics withMathOverflowand the tokens cannot be burned. - The cap cannot be enforced by the type system.
Capped::mintis a free-standing path the author has to remember to call, andCompose<(Vault, Capped, TotalSupply)>compiles while enforcing nothing.
Proposal
1. MintOverrides trait in fungible/overrides.rs, a sibling of BurnableOverrides:
pub trait MintOverrides {
fn mint(e: &Env, to: &Address, amount: i128);
}
- Required method, no default body. Forgetting the override on a supply-aware type is a liveness bug (burns panic), unlike the burn override where the failure is benign. One line per impl.
- Implemented for every contract type except
Vault.Vaulthas an inherent ERC-4626mintwith five arguments; an inherent item shadows a same-named trait item in path syntax, so a three-argument override onVaultwould be an arity error, and a free share mint is wrong for a vault anyway. - Not on
ContractOverrides, precisely becauseVaultimplements that trait.
2. Capped becomes a decorator contract type Capped<T>:
MintOverrides for Capped<T>:check_cap(e, amount, T::total_supply(e)), thenT::mint(e, to, amount).ContractOverrides,BurnableOverrides,TotalSupplyOverrides,AllowListContractType,BlockListContractTypedelegate to / forward fromT.Compose<(AllowList, Capped, TotalSupply)>resolves toCapped<TotalSupplyAllowList>; the fold wraps the finalized type, order-insensitively. The boundT: TotalSupplyOverrides + MintOverridesreplaces Capped'sNeedsSupply<Nil>contribution and its dedicated error rows.- New marker
CappedContractType, implemented forCapped<T>, andFungibleCapped: FungibleTotalSupply + FungibleToken<ContractType: CappedContractType>, socap()can only be exposed by a contract type that enforces it. Capped::mintis removed.set_cap,query_cap,check_capstay as free functions.
3. Contract author usage becomes uniform:
use stellar_tokens::fungible::MintOverrides;
#[only_owner]
pub fn mint(e: &Env, to: Address, amount: i128) {
<Self as FungibleToken>::ContractType::mint(e, &to, amount);
}
The import is required: a trait-only method on Self::ContractType does not resolve from an inherent impl without the trait in scope (verified). The overrides docs currently state "no need to import it"; that line goes, and the fungible-pausable example already imports ContractOverrides for the same reason.
4. Docs and examples: fungible-blocklist and fungible-capped switch to the contract-type path; Base::mint and total_supply module docs point to Self::ContractType::mint as the only recommended minting path.
Non-goals
minton the publicFungibleTokentrait. The entry-point signature legitimately varies per contract (RWAToken::minttakesoperator, vaults mint against assets); only the internal primitive is uniform.Capped<RWA>.RWA::batch_mintmints through its own path, so a cap on RWA would be bypassable; RWA keeps the explicitcheck_capin the body.- Capping vault deposits.
Vaulthas noMintOverrides, so any list withVaultandCappedis rejected at compile time.
Acceptance criteria
Self::ContractType::mintcompiles for every validComposelist withoutVault.Compose<(Capped, TotalSupply)>,(AllowList, Capped, TotalSupply),(BlockList, Capped, TotalSupply)and(AllowList, BlockList, Capped, TotalSupply)resolve toCapped<…>in any order and enforce the cap on mint (tests per combination).Compose<(Capped,)>,(FungibleVotes, Capped)and(Vault, Capped, TotalSupply)are rejected at compile time with the existing dedicated messages.FungibleCappedcannot be implemented on a contract type that is notCapped<_>.NeedsSupplyrows specific toCappedare removed;RWA/Vaulthandling is unchanged by this issue.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with fungible/overrides.rs and trace MintOverrides, Capped, Compose, and the existing contract-type implementations. Review the acceptance criteria and test each valid combination for cap enforcement, then verify the listed invalid combinations fail at compile time with the expected messages. Done means mint dispatch is uniform, capped compositions enforce limits, and the documented examples use the contract-type path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- blockchain
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100