Skip to content

[privileges_manager] Setter/getter asymmetry: setters ignore ephemeral registrations (cannot configure a just-registered account in one execution) #20

Description

@GideonBature

Summary

In PrivilegesManager, all set_or_update_* methods gate on is_account_permanently_registered (permanent state only), while the corresponding get_* methods layer reads as ephemeral delta → newly-registered (delta) → permanent. The net effect: you cannot register an account and adjust its privileges in the same execution — the setter rejects with AccountIsNotPermanentlyRegistered even though the account is registered (ephemerally) and the getter would return its body. This is asymmetric with both the getters in this file and with the Registry/StateManager read models.

Affected file(s)

  • src/inscriptive/privileges_manager/privileges_manager.rs

Location

Setters (all use is_account_permanently_registered):

  • set_or_update_account_liveness_flagprivileges_manager.rs:651-666
  • set_or_update_account_hierarchy:669-684
  • set_or_update_account_txfee_exemptions:687-702
  • set_or_update_account_reserved_flag_1:705-720
  • set_or_update_account_reserved_flag_2:723-738
  • set_or_update_account_can_deploy_liquidity:741-756
  • set_or_update_account_can_deploy_contract:759-774
  • set_or_update_contract_liveness_flag:777-792
  • set_or_update_contract_immutability:795-810
  • set_or_update_contract_tax_exemptions:813-828

Representative:

pub fn set_or_update_account_liveness_flag(
    &mut self,
    account_key: AccountKey,
    liveness_flag: LivenessFlag,
) -> Result<(), PMUpdateAccountError> {
    if !self.is_account_permanently_registered(account_key) {   // <-- permanent only
        return Err(PMUpdateAccountError::AccountIsNotPermanentlyRegistered(account_key));
    }
    self.delta.updated_account_liveness_flags.insert(account_key, liveness_flag);
    Ok(())
}

Getters (layer ephemeral → new-register → permanent), e.g. get_account_liveness_flag:478-491:

pub fn get_account_liveness_flag(&self, account_key: AccountKey) -> Option<LivenessFlag> {
    if let Some(value) = self.delta.updated_account_liveness_flags.get(&account_key) {
        return Some(value.clone());
    }
    if let Some(body) = self.delta.new_accounts_to_register.get(&account_key) {
        return Some(body.liveness_flag.clone());
    }
    self.in_memory_accounts.get(&account_key).map(|body| body.liveness_flag.clone())
}

Root cause / analysis

There are two reasonable intended behaviors, and the current code matches neither cleanly:

  • (A) "Privileges are set at registration only." Then the setters should not exist for newly-registered accounts, and the register path should require the full body — which it does (register_account takes a complete PrivilegesManagerAccountBody). Under this reading, the setters correctly reject ephemeral accounts. But then the getters' middle arm (delta.new_accounts_to_register) is redundant with reading the body, and the asymmetry is just confusing rather than wrong.
  • (B) "Privileges are mutable at any time." Then the setters should use is_account_registered (ephemeral or permanent), matching the registry's is_account_registered (registry.rs:534-537) and the getter layering. Under this reading, the current setters are buggy: they reject a legitimate same-execution register+configure flow.

The getter layering strongly implies (B) was intended, which makes the setters wrong. Either way, the contract should be made explicit and symmetric.

Note: Registry exposes both is_account_permanently_registered and is_account_registered (the union) precisely so callers can pick the semantics; PrivilegesManager exposes is_account_permanently_registered and is_account_ephemerally_registered separately but no union helper, which is why the setters reach for the permanent-only check.

Impact

  • Functional limitation: same-execution register + privilege-update is impossible; forces a two-transaction dance that may not match the protocol's execution model.
  • Confusing API: getters see ephemeral registrations, setters don't. A future maintainer will reasonably assume (B) and be surprised.
  • Possibly masking a real bug if a caller relies on the union semantics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions