# miden::standards::faucets::policies::transfer::blocklist
#
# Per-faucet blocklist primitive: storage slot plus low-level `Invocation: exec` helpers.
# This is the core component — it does NOT implement the transfer policy itself; that lives
# in a sibling policy file (e.g. `basic_blocklist.masm`) that consumes these helpers.
#
# - Storage: `blocked_accounts` map keyed by account ID; entry word `[1, 0, 0, 0]` means blocked.
# - Helpers (`block_account` / `unblock_account` / `is_blocked` / `assert_not_blocked`) perform
#   NO authorization — they are low-level building blocks meant to be called by
#   `Invocation: call` wrappers in auth-checking account components (see `owner_controlled.masm`).
#   Tx scripts cannot invoke them directly — they go through the wrapper component installed on
#   the faucet.

use miden::core::word
use miden::protocol::active_account
use miden::protocol::native_account

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

# Slot holding the per-faucet map of blocked accounts.
# Map entries: [0, 0, account_id_suffix, account_id_prefix] => [is_blocked, 0, 0, 0]
const BLOCKED_ACCOUNTS_SLOT = word("miden::standards::faucets::policies::transfer::blocklist::blocked_accounts")

const BLOCKED_WORD = [1, 0, 0, 0]

const UNBLOCKED_WORD = [0, 0, 0, 0]

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

const ERR_ACCOUNT_IS_BLOCKED = "account is blocked"

# PUBLIC HELPERS
# ================================================================================================

#! Returns whether the given account is currently blocked on this faucet.
#!
#! Reads [`BLOCKED_ACCOUNTS_SLOT`] on the active account at the key derived from the account ID.
#! Returns `1` if the stored word is non-zero (blocked) or `0` if it is the zero word
#! (not blocked, including the default for never-set entries).
#!
#! Inputs:  [account_id_suffix, account_id_prefix]
#! Outputs: [is_blocked]
#!
#! Invocation: exec
pub proc is_blocked
    exec.build_blocked_accounts_map_key
    # => [KEY]

    push.BLOCKED_ACCOUNTS_SLOT[0..2]
    # => [slot_suffix, slot_prefix, KEY]

    exec.active_account::get_map_item
    # => [VALUE]

    exec.word::eqz not
    # => [is_blocked]
end

#! Adds the given account to the blocklist. If the account is already blocked, this is a noop.
#!
#! This procedure performs NO authorization. Wrappers running in `Invocation: call` mode add
#! the auth check (e.g. `ownable2step::assert_sender_is_owner`) before delegating here via
#! `exec.blocklist::block_account`.
#!
#! Inputs:  [account_id_suffix, account_id_prefix]
#! Outputs: []
#!
#! Invocation: exec
pub proc block_account
    exec.build_blocked_accounts_map_key
    # => [KEY]

    push.BLOCKED_WORD swapw
    # => [KEY, BLOCKED_WORD]

    push.BLOCKED_ACCOUNTS_SLOT[0..2]
    # => [slot_suffix, slot_prefix, KEY, BLOCKED_WORD]

    exec.native_account::set_map_item
    # => [OLD_VALUE]

    dropw
    # => []
end

#! Removes the given account from the blocklist. If the account is not currently blocked, this
#! is a noop. See [`block_account`] for the same authorization caveats.
#!
#! Inputs:  [account_id_suffix, account_id_prefix]
#! Outputs: []
#!
#! Invocation: exec
pub proc unblock_account
    exec.build_blocked_accounts_map_key
    # => [KEY]

    push.UNBLOCKED_WORD swapw
    # => [KEY, UNBLOCKED_WORD]

    push.BLOCKED_ACCOUNTS_SLOT[0..2]
    # => [slot_suffix, slot_prefix, KEY, UNBLOCKED_WORD]

    exec.native_account::set_map_item
    # => [OLD_VALUE]

    dropw
    # => []
end

#! Requires the given account to not be blocked.
#!
#! Delegates to [`is_blocked`] and inverts the result. If the account is blocked, panics
#! with [`ERR_ACCOUNT_IS_BLOCKED`].
#!
#! Use from other modules or transaction scripts to guard logic that must not run for blocked
#! accounts. In asset callback foreign context, the active account is the issuing faucet.
#!
#! Inputs:  [account_id_suffix, account_id_prefix]
#! Outputs: []
#!
#! Panics if:
#! - the stored entry for this account is not the zero word.
#!
#! Invocation: exec
pub proc assert_not_blocked
    exec.is_blocked
    # => [is_blocked]

    not
    # => [is_not_blocked]

    assert.err=ERR_ACCOUNT_IS_BLOCKED
    # => []
end

# HELPER PROCEDURES
# ================================================================================================

#! Builds the blocked-accounts map key for the given account ID.
#!
#! Inputs:  [account_id_suffix, account_id_prefix]
#! Outputs: [KEY]
#!
#! Where:
#! - KEY is [0, 0, account_id_suffix, account_id_prefix].
#!
#! Invocation: exec
proc build_blocked_accounts_map_key
    push.0.0
    # => [0, 0, account_id_suffix, account_id_prefix]
    # => [KEY]
end
