use miden::protocol::active_account
use miden::protocol::active_note
use miden::protocol::faucet
use miden::protocol::output_note

# CONSTANTS
# =================================================================================================

const PRIVATE_NOTE=2

# ERRORS
# =================================================================================================
const ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED="distribute would cause the maximum supply to be exceeded"

const ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS="burn requires exactly 1 note asset"

# CONSTANTS
# =================================================================================================

# The standard slot where fungible faucet metadata like token symbol or decimals are stored.
const METADATA_SLOT=word("miden::standards::fungible_faucets::metadata")

#! Distributes freshly minted fungible assets to the provided recipient by creating a note.
#!
#! 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), input_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 total issuance after minting is greater than the maximum allowed supply.
#!
#! Invocation: exec
pub proc distribute
    # get max supply of this faucet. We assume it is stored at pos 3 of slot 0
    push.METADATA_SLOT[0..2] exec.active_account::get_item drop drop drop
    # => [max_supply, amount, tag, note_type, RECIPIENT]

    # get total issuance of this faucet so far and add amount to be minted
    exec.faucet::get_total_issuance
    # => [total_issuance, max_supply, amount, tag, note_type, RECIPIENT]

    # compute maximum amount that can be minted, max_mint_amount = max_supply - total_issuance
    sub
    # => [max_supply - total_issuance, amount, tag, note_type, RECIPIENT]

    # check that amount =< max_supply - total_issuance, fails if otherwise
    dup.1 gte assert.err=ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED
    # => [amount, tag, note_type, RECIPIENT]

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

    # mint the asset; this is needed to satisfy asset preservation logic.
    exec.faucet::mint
    # => [ASSET, tag, note_type, RECIPIENT]

    movdn.9 movdn.9 movdn.9 movdn.9
    # => [tag, note_type, RECIPIENT, 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]

    exec.output_note::add_asset
    # => [note_idx]
end

#! Burns the fungible asset from the active note.
#!
#! This procedure retrieves the asset from the active note and burns it. The note must contain
#! exactly one asset, which must be a fungible asset issued by this faucet.
#!
#! Inputs:  [pad(16)]
#! Outputs: [pad(16)]
#!
#! Where:
#! - ASSET is the fungible asset that was burned.
#!
#! Panics if:
#! - the procedure is not called from a note context (active_note::get_assets will fail).
#! - 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 about to be burned is greater than the outstanding supply of the asset.
#!
#! Invocation: call
pub proc burn
    # Get the assets from the note. This will fail if not called from a note context.
    push.0 exec.active_note::get_assets
    # => [num_assets, dest_ptr, pad(16)]

    # Verify we have exactly one asset
    assert.err=ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS
    # => [dest_ptr, pad(16)]

    mem_loadw_be
    # => [ASSET, pad(16)]

    # burning the asset
    exec.faucet::burn
    # => [ASSET, pad(16)]

    dropw
    # => [pad(16)]
end
