Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Increased `TokenSymbol` max allowed length from 6 to 12 uppercase characters ([#2420](https://github.com/0xMiden/miden-base/pull/2420)).
- Added `StandardNote::from_script_root()` and `StandardNote::name()` methods, and exposed `NoteType` `PUBLIC`/`PRIVATE` masks as public constants ([#2411](https://github.com/0xMiden/miden-base/pull/2411)).
- Resolve standard note scripts directly in `TransactionExecutorHost` instead of querying the data store ([#2417](https://github.com/0xMiden/miden-base/pull/2417)).
- Added `UnlimitedFungibleFaucet` and `TimedFungibleFaucet` components, implementing flexible supply management strategies ([#2386](https://github.com/0xMiden/miden-base/pull/2386)).

### Changes

Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only glanced through this PR, but I wonder if the approach should be more along the lines I described in OpenZeppelin/miden-confidential-contracts#39 (reply in thread). Basically, we have a single network fungible faucet component, but we can customize mint policies using procedures stored in account storage. These policies could be under miden::standards::faucet::network_fungible::mint_policies or something like that.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# The MASM code of the Timed Fungible Faucet Account Component.
#
# See the `TimedFungibleFaucet` Rust type's documentation for more details.

pub use ::miden::standards::faucets::timed_fungible::distribute
pub use ::miden::standards::faucets::timed_fungible::burn
pub use ::miden::standards::faucets::timed_fungible::transfer_ownership
pub use ::miden::standards::faucets::timed_fungible::renounce_ownership
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub use ::miden::standards::faucets::timed_unlimited_fungible::distribute
pub use ::miden::standards::faucets::timed_unlimited_fungible::burn
pub use ::miden::standards::faucets::timed_unlimited_fungible::transfer_ownership
pub use ::miden::standards::faucets::timed_unlimited_fungible::renounce_ownership
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# The MASM code of the Unlimited Fungible Faucet Account Component.
#
# See the `UnlimitedFungibleFaucet` Rust type's documentation for more details.

pub use ::miden::standards::faucets::unlimited_fungible::distribute
pub use ::miden::standards::faucets::unlimited_fungible::burn
pub use ::miden::standards::faucets::unlimited_fungible::transfer_ownership
pub use ::miden::standards::faucets::unlimited_fungible::renounce_ownership
102 changes: 24 additions & 78 deletions crates/miden-standards/asm/standards/faucets/mod.masm
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,14 @@ use miden::protocol::active_note
use miden::protocol::faucet
use miden::protocol::native_account
use miden::protocol::output_note
use ::miden::protocol::asset::FUNGIBLE_ASSET_MAX_AMOUNT
use miden::standards::supply::supply_limits

# CONSTANTS
# ERRORS
# =================================================================================================

const PRIVATE_NOTE=2

# ERRORS
# =================================================================================================

const ERR_FUNGIBLE_ASSET_TOKEN_SUPPLY_EXCEEDS_MAX_SUPPLY="token supply exceeds max supply"

const ERR_FUNGIBLE_ASSET_MAX_SUPPLY_EXCEEDS_FUNGIBLE_ASSET_MAX_AMOUNT="max supply exceeds maximum representable fungible asset amount"

const ERR_FUNGIBLE_ASSET_DISTRIBUTE_AMOUNT_EXCEEDS_MAX_SUPPLY="token_supply plus the amount passed to distribute would exceed the maximum supply"

const ERR_FAUCET_BURN_AMOUNT_EXCEEDS_TOKEN_SUPPLY="asset amount to burn exceeds the existing token supply"

const ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS="burn requires exactly 1 note asset"

# CONSTANTS
# STORAGE LAYOUT
# =================================================================================================

# The local memory address at which the metadata slot content is stored.
Expand Down Expand Up @@ -55,82 +42,44 @@ const METADATA_SLOT=word("miden::standards::fungible_faucets::metadata")
#! Invocation: exec
@locals(4)
pub proc distribute
# Get the configured max supply and the token supply (= current supply).
# ---------------------------------------------------------------------------------------------

push.METADATA_SLOT[0..2] exec.active_account::get_item
# => [token_symbol, decimals, max_supply, token_supply, amount, tag, note_type, RECIPIENT]

# store a copy of the current slot content for the token_supply update later
loc_storew_be.METADATA_SLOT_LOCAL
drop drop
# => [max_supply, token_supply, amount, tag, note_type, RECIPIENT]

# Assert that minting does not violate any supply constraints.
#
# To make sure we cannot mint more than intended, we need to check:
# 1) (max_supply - token_supply) <= max_supply, i.e. the subtraction does not wrap around
# 2) amount + token_supply does not exceed max_supply
# 3) amount + token_supply is less than FUNGIBLE_ASSET_MAX_AMOUNT
#
# This is done with the following concrete assertions:
# - assert token_supply <= max_supply which ensures 1)
# - assert max_supply <= FUNGIBLE_ASSET_MAX_AMOUNT to help ensure 3)
# - assert amount <= max_mint_amount to ensure 2) as well as 3)
# - this ensures 3) because token_supply + max_mint_amount at most ends up being equal to
# max_supply and we already asserted that max_supply does not exceed
# FUNGIBLE_ASSET_MAX_AMOUNT
# check_supply reads the metadata slot internally and validates:
# 1) token_supply <= max_supply
# 2) max_supply <= FUNGIBLE_ASSET_MAX_AMOUNT
# 3) token_supply + amount <= max_supply
# ---------------------------------------------------------------------------------------------

dup.1 dup.1
# => [max_supply, token_supply, max_supply, token_supply, amount, tag, note_type, RECIPIENT]

# assert that token_supply <= max_supply
lte assert.err=ERR_FUNGIBLE_ASSET_TOKEN_SUPPLY_EXCEEDS_MAX_SUPPLY
# => [max_supply, token_supply, amount, tag, note_type, RECIPIENT]

# assert max_supply <= FUNGIBLE_ASSET_MAX_AMOUNT
dup lte.FUNGIBLE_ASSET_MAX_AMOUNT
assert.err=ERR_FUNGIBLE_ASSET_MAX_SUPPLY_EXCEEDS_FUNGIBLE_ASSET_MAX_AMOUNT
# => [max_supply, token_supply, amount, tag, note_type, RECIPIENT]
exec.supply_limits::check_supply
# => [amount, tag, note_type, RECIPIENT]

dup.2 swap dup.2
# => [token_supply, max_supply, amount, token_supply, amount, tag, note_type, RECIPIENT]
# Read metadata to compute and store the updated token_supply.
# ---------------------------------------------------------------------------------------------

# compute maximum amount that can be minted, max_mint_amount = max_supply - token_supply
sub
# => [max_mint_amount, amount, token_supply, amount, tag, note_type, RECIPIENT]
push.METADATA_SLOT[0..2] exec.active_account::get_item
# => [token_symbol, decimals, max_supply, token_supply, amount, tag, note_type, RECIPIENT]

# assert amount <= max_mint_amount
lte assert.err=ERR_FUNGIBLE_ASSET_DISTRIBUTE_AMOUNT_EXCEEDS_MAX_SUPPLY
loc_storew_be.METADATA_SLOT_LOCAL
drop drop drop
# => [token_supply, amount, tag, note_type, RECIPIENT]

# Compute the new token_supply and update in storage.
# ---------------------------------------------------------------------------------------------

dup.1 add
# => [new_token_supply, amount, tag, note_type, RECIPIENT]

padw loc_loadw_be.METADATA_SLOT_LOCAL
# => [[token_symbol, decimals, max_supply, token_supply], new_token_supply, amount, tag, note_type, RECIPIENT]
# => [token_symbol, decimals, max_supply, old_token_supply, new_token_supply, amount, tag, note_type, RECIPIENT]

movup.3 drop
# => [[token_symbol, decimals, max_supply, new_token_supply], amount, tag, note_type, RECIPIENT]
# => [token_symbol, decimals, max_supply, new_token_supply, amount, tag, note_type, RECIPIENT]

# update the metadata slot with the new supply
push.METADATA_SLOT[0..2] exec.native_account::set_item dropw
# => [amount, tag, note_type, RECIPIENT]

# Mint the asset.
# ---------------------------------------------------------------------------------------------

# creating the asset
exec.faucet::create_fungible_asset
# => [ASSET, tag, note_type, RECIPIENT]

# mint the asset; this is needed to satisfy asset preservation logic.
# this ensures that the asset's faucet ID matches the native account's ID.
# this is ensured because create_fungible_asset creates the asset with the native account's ID
exec.faucet::mint
# => [ASSET, tag, note_type, RECIPIENT]

Expand All @@ -140,11 +89,9 @@ pub proc distribute
# Create a new note with the asset.
# ---------------------------------------------------------------------------------------------

# create a note
exec.output_note::create
# => [note_idx, ASSET]

# load the ASSET and add it to the note
dup movdn.5 movdn.5
# => [ASSET, note_idx, note_idx]

Expand Down Expand Up @@ -198,17 +145,17 @@ pub proc burn
exec.faucet::burn dropw
# => [amount, pad(16)]

# Subtract burnt amount from current token_supply in storage.
# Assert that the burn does not exceed the current token supply.
# check_burn reads the metadata slot internally.
# ---------------------------------------------------------------------------------------------

push.METADATA_SLOT[0..2] exec.active_account::get_item
# => [token_symbol, decimals, max_supply, token_supply, amount, pad(16)]
exec.supply_limits::check_burn
# => [amount, pad(16)]

dup.4 dup.4
# => [token_supply, amount, token_symbol, decimals, max_supply, token_supply, amount, pad(16)]
# Subtract burnt amount from current token_supply in storage.
# ---------------------------------------------------------------------------------------------

# assert that amount <= token_supply
lte assert.err=ERR_FAUCET_BURN_AMOUNT_EXCEEDS_TOKEN_SUPPLY
push.METADATA_SLOT[0..2] exec.active_account::get_item
# => [token_symbol, decimals, max_supply, token_supply, amount, pad(16)]

movup.3 movup.4
Expand All @@ -221,7 +168,6 @@ pub proc burn
movdn.3
# => [token_symbol, decimals, max_supply, new_token_supply, pad(16)]

# update the metadata slot with the new supply
push.METADATA_SLOT[0..2] exec.native_account::set_item dropw
# => [pad(16)]
end
70 changes: 70 additions & 0 deletions crates/miden-standards/asm/standards/faucets/timed_fungible.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# TIMED SUPPLY FUNGIBLE FAUCET CONTRACT
#
# A fungible faucet with time-based distribution controls.
# This faucet enforces a distribution period for minting.
# Burns are always allowed. Supply tracking is handled by faucets::distribute/burn.
# =================================================================================================

use miden::standards::faucets
use miden::standards::access::ownable
use miden::standards::supply::supply_limits

# OWNER MANAGEMENT
# =================================================================================================

pub use ownable::get_owner
pub use ownable::transfer_ownership
pub use ownable::renounce_ownership

# PROCEDURES
# =================================================================================================

#! Distributes freshly minted fungible assets to the provided recipient by creating a note.
#!
#! This procedure checks the distribution period before delegating to the shared distribute
#! procedure which handles supply tracking in the metadata slot.
#!
#! Inputs: [amount, tag, note_type, RECIPIENT]
#! Outputs: [note_idx]
#!
#! Where:
#! - amount is the amount to be minted and sent.
#! - tag is the tag to be included in the note.
#! - note_type is the type of the note that holds the asset.
#! - RECIPIENT is the recipient of the asset, i.e.,
#! hash(hash(hash(serial_num, [0; 4]), script_root), storage_commitment).
#! - note_idx is the index of the created note.
#!
#! Panics if:
#! - the transaction is being executed against an account that is not a fungible asset faucet.
#! - the note sender is not the owner of this faucet.
#! - the distribution period has ended.
#!
#! Invocation: exec
pub proc distribute
exec.ownable::verify_owner

exec.supply_limits::check_distribution_period
# => [amount, tag, note_type, RECIPIENT]

exec.faucets::distribute
# => [note_idx]
end

#! Burns the fungible asset from the active note.
#!
#! Burns are always allowed — no time or ownership restrictions.
#! Delegates to the shared burn procedure which handles supply tracking.
#!
#! Inputs: [pad(16)]
#! Outputs: [pad(16)]
#!
#! Panics if:
#! - the procedure is not called from a note context.
#! - the note does not contain exactly one asset.
#! - the transaction is executed against an account which is not a fungible asset faucet.
#! - the transaction is executed against a faucet which is not the origin of the specified asset.
#! - the amount to burn exceeds the current token supply.
#!
#! Invocation: call
pub use faucets::burn
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# TIMED UNLIMITED SUPPLY FUNGIBLE FAUCET CONTRACT
#
# A fungible faucet with time-based distribution controls but no supply cap.
# This faucet enforces a distribution period for minting, with max_supply set
# to FUNGIBLE_ASSET_MAX_AMOUNT (effectively unlimited).
# Burns are always allowed. Supply tracking is handled by faucets::distribute/burn.
# =================================================================================================

use miden::standards::faucets
use miden::standards::access::ownable
use miden::standards::supply::supply_limits

# OWNER MANAGEMENT
# =================================================================================================

pub use ownable::get_owner
pub use ownable::transfer_ownership
pub use ownable::renounce_ownership

# PROCEDURES
# =================================================================================================

#! Distributes freshly minted fungible assets to the provided recipient by creating a note.
#!
#! This procedure checks the distribution period before delegating to the shared distribute
#! procedure which handles supply tracking in the metadata slot.
#! No supply cap is enforced — max_supply is set to FUNGIBLE_ASSET_MAX_AMOUNT at creation.
#!
#! Inputs: [amount, tag, note_type, RECIPIENT]
#! Outputs: [note_idx]
#!
#! Where:
#! - amount is the amount to be minted and sent.
#! - tag is the tag to be included in the note.
#! - note_type is the type of the note that holds the asset.
#! - RECIPIENT is the recipient of the asset, i.e.,
#! hash(hash(hash(serial_num, [0; 4]), script_root), storage_commitment).
#! - note_idx is the index of the created note.
#!
#! Panics if:
#! - the transaction is being executed against an account that is not a fungible asset faucet.
#! - the note sender is not the owner of this faucet.
#! - the distribution period has ended.
#!
#! Invocation: exec
pub proc distribute
exec.ownable::verify_owner

exec.supply_limits::check_distribution_period
# => [amount, tag, note_type, RECIPIENT]

exec.faucets::distribute
# => [note_idx]
end

#! Burns the fungible asset from the active note.
#!
#! Burns are always allowed — no time or ownership restrictions.
#! Delegates to the shared burn procedure which handles supply tracking.
#!
#! Inputs: [pad(16)]
#! Outputs: [pad(16)]
#!
#! Panics if:
#! - the procedure is not called from a note context.
#! - the note does not contain exactly one asset.
#! - the transaction is executed against an account which is not a fungible asset faucet.
#! - the transaction is executed against a faucet which is not the origin of the specified asset.
#! - the amount to burn exceeds the current token supply.
#!
#! Invocation: call
pub use faucets::burn
Loading