Summary
BasicBlocklist and BasicAllowlist evaluate their predicate against the native account of the transfer. When a faucet mints and sends its own asset, the faucet is the native account, so the faucet is filtered by its own list. This produces two mirrored footguns:
- Blocklist:
block_account on the faucet's own ID disables the faucet's minting. Nothing rejects it - not BlocklistConfigNote, not the MASM manager, not the low-level helper.
- Allowlist: the faucet must
allow_account its own ID before it can mint at all, and disallow_account on its own ID silently takes minting away again.
Neither behaviour is useful: a faucet filtering itself gains nothing that Pausable (freeze) or the mint policy (stop issuance) does not already provide, and the failure surfaces as a confusing panic in the minting path rather than at the point where the list entry is written.
Proposal: short-circuit the predicate in check_policy when the asset's issuing faucet is the native account, so the two lists only ever govern third-party transfers.
Why the faucet ends up filtering itself
The predicates read the native account, not the counterparty:
When a faucet mints into an output note it is itself the native account, the minted asset carries the callback flag, and the kernel invokes on_before_asset_added_to_note with no exemption for the issuer. The policy then looks the faucet's own ID up in its own list.
A self-block is recoverable (config notes carry no assets, so no transfer callback fires and an unblock_account note still executes), but it is a silent, self-inflicted outage in the meantime.
Why filtering the issuer is not useful
- It does not freeze the token. A transfer between two user accounts never consults the faucet's own entry, so self-filtering leaves user-to-user transfers untouched.
- Freezing all transfers is what
Pausable is for. The pause check already runs in invoke_transfer_policy before the policy is dispatched.
- Stopping issuance is what the mint policy is for. Disabling minting through the transfer list is an obscure side channel with a misleading error message.
- The faucet is not a party the list can meaningfully constrain: it owns the list and can rewrite it at will, and it must authorize its own transaction anyway. Exempting it grants no capability it did not already have, so this is not a way for a blocked account to launder a transfer through the faucet.
Alternative considered: reject self-entries
Reject BlockAccount { account } == target in BlocklistConfigNote::new plus an assertion in
blocklist::block_account. Rejected as the primary fix because:
- It needs guards in two places (Rust for fail-fast, MASM for the paths that bypass Rust) and gets the mirror case wrong: for the allowlist the dangerous action is
disallow_account, and the faucet still has to remember to allowlist itself at deploy time.
- It treats the symptom. The root cause is that the predicate is asked a question about an account it should never be filtering.
Rejecting self-entries could still be added on top of the short-circuit as a usability guard, since
such an entry would then be dead state, but it is not needed for correctness.
Summary
BasicBlocklistandBasicAllowlistevaluate their predicate against the native account of the transfer. When a faucet mints and sends its own asset, the faucet is the native account, so the faucet is filtered by its own list. This produces two mirrored footguns:block_accounton the faucet's own ID disables the faucet's minting. Nothing rejects it - notBlocklistConfigNote, not the MASM manager, not the low-level helper.allow_accountits own ID before it can mint at all, anddisallow_accounton its own ID silently takes minting away again.Neither behaviour is useful: a faucet filtering itself gains nothing that
Pausable(freeze) or the mint policy (stop issuance) does not already provide, and the failure surfaces as a confusing panic in the minting path rather than at the point where the list entry is written.Proposal: short-circuit the predicate in
check_policywhen the asset's issuing faucet is the native account, so the two lists only ever govern third-party transfers.Why the faucet ends up filtering itself
The predicates read the native account, not the counterparty:
basic_blocklist::check_policynative_account::get_idthenblocklist::assert_not_blocked.basic_allowlist::check_policynative_account::get_idthenallowlist::assert_allowed.When a faucet mints into an output note it is itself the native account, the minted asset carries the callback flag, and the kernel invokes
on_before_asset_added_to_notewith no exemption for the issuer. The policy then looks the faucet's own ID up in its own list.A self-block is recoverable (config notes carry no assets, so no transfer callback fires and an
unblock_accountnote still executes), but it is a silent, self-inflicted outage in the meantime.Why filtering the issuer is not useful
Pausableis for. The pause check already runs ininvoke_transfer_policybefore the policy is dispatched.Alternative considered: reject self-entries
Reject
BlockAccount { account } == targetinBlocklistConfigNote::newplus an assertion inblocklist::block_account. Rejected as the primary fix because:disallow_account, and the faucet still has to remember to allowlist itself at deploy time.Rejecting self-entries could still be added on top of the short-circuit as a usability guard, since
such an entry would then be dead state, but it is not needed for correctness.