Skip to main content

dpp/data_contract/associated_token/token_configuration/v1/
mod.rs

1mod accessors;
2
3use crate::data_contract::associated_token::token_configuration::v0::{
4    default_change_control_rules, TokenConfigurationV0,
5};
6use crate::data_contract::change_control_rules::ChangeControlRules;
7#[cfg(feature = "json-conversion")]
8use crate::serialization::json_safe_fields;
9use bincode::{Decode, DecodeUntrusted, Encode};
10use serde::{Deserialize, Serialize};
11use std::fmt;
12
13/// Version 1 of the token configuration: everything `TokenConfigurationV0` carries, plus the
14/// per-token shielded pool opt-in, the pool's outgoing notes threshold and whether holders may
15/// move the token to anyone else.
16///
17/// The V0 fields are nested as `base` and flattened on the JSON / Value wire, so a V1
18/// configuration reads exactly like a V0 one with `$formatVersion: "1"` and the extra
19/// `hasShieldedPool`, `minimumPoolNotesForOutgoing`, `minimumPoolNotesForOutgoingChangeRules`
20/// and `transferable` keys. On the bincode wire it is the V0 bytes followed by the flag, the
21/// threshold, its rules and the transferable flag, under the enum's variant index 1, so stored
22/// V0 configurations decode unchanged.
23///
24/// `has_shielded_pool` is decided at token creation and is immutable afterwards: the pool
25/// subtree (an Orchard note commitment tree, nullifier set, anchors and a balance) is created
26/// together with the token's other trees, and a pool holding notes can never be removed.
27/// Enabling a pool on an existing token is not supported by this version. `transferable` is
28/// fixed at creation too: no `TokenConfigUpdate` item changes it.
29#[cfg_attr(feature = "json-conversion", json_safe_fields)]
30#[derive(Serialize, Deserialize, Decode, Encode, Debug, Clone, PartialEq, Eq, DecodeUntrusted)]
31// An unknown key is refused rather than dropped: what a token can do is fixed when it is
32// created, so a misspelled `hasShieldedPool` would otherwise leave the token permanently
33// without the pool it was meant to have, and say nothing. Version 0 carries the same refusal,
34// but it cannot reach a key arriving here: `serde(flatten)` never offers an unrecognized key to
35// the flattened struct, so the attribute has to sit on the version that owns the flatten.
36#[serde(rename_all = "camelCase", deny_unknown_fields)]
37pub struct TokenConfigurationV1 {
38    /// The V0 configuration this version extends.
39    #[serde(flatten)]
40    pub base: TokenConfigurationV0,
41    /// Whether this token has its own shielded pool. When `true`, holders may move balance
42    /// into the pool (`TokenShield`), transfer privately inside it (`TokenShieldedTransfer`)
43    /// and move balance back out to an identity (`TokenUnshield`).
44    #[serde(default)]
45    pub has_shielded_pool: bool,
46    /// The fewest notes the token's shielded pool must hold before tokens may leave it for a
47    /// visible destination (unshielding to an identity, burning from the pool, paying a
48    /// document's token cost from the pool). Transfers inside the pool are not limited.
49    /// `None` reads as 0, no threshold.
50    ///
51    /// The count is of note commitments, not of holders: one bundle carries several actions,
52    /// so a single depositor can reach a threshold alone. It tells holders how busy the pool
53    /// should be before they leave it and guarantees no anonymity set. A threshold above 0
54    /// also traps a new pool's first depositors until enough notes accumulate, so the default
55    /// is none. At most `max_token_pool_notes_for_outgoing`, so an issuer cannot set one no
56    /// pool reaches and strand every shielded balance. Changed through `TokenConfigUpdate`
57    /// under `minimum_pool_notes_for_outgoing_change_rules`.
58    #[serde(default)]
59    pub minimum_pool_notes_for_outgoing: Option<u64>,
60    /// Change control rules governing who can modify `minimum_pool_notes_for_outgoing`. No one
61    /// when absent.
62    #[serde(default = "default_change_control_rules")]
63    pub minimum_pool_notes_for_outgoing_change_rules: ChangeControlRules,
64    /// Whether holders may move this token to another identity. When `false` the token still
65    /// mints, is claimed, bought from the issuer, burned, frozen and destroyed as configured, but
66    /// a `TokenTransfer` is refused, a document type may charge it only by burning it (so only
67    /// its own contract's document types can use it, external burns being refused), and it
68    /// cannot have a shielded pool, whose notes change hands without an identity. `true` when
69    /// absent, as for every V0 configuration.
70    #[serde(default = "default_transferable")]
71    pub transferable: bool,
72}
73
74/// The value of an omitted `transferable`: every token is transferable unless its configuration
75/// says otherwise.
76pub fn default_transferable() -> bool {
77    true
78}
79
80impl TokenConfigurationV1 {
81    /// Wraps a V0 configuration, opting the token into a shielded pool when `has_shielded_pool`.
82    pub fn from_v0(base: TokenConfigurationV0, has_shielded_pool: bool) -> Self {
83        Self {
84            base,
85            has_shielded_pool,
86            minimum_pool_notes_for_outgoing: None,
87            minimum_pool_notes_for_outgoing_change_rules: default_change_control_rules(),
88            transferable: true,
89        }
90    }
91}
92
93impl fmt::Display for TokenConfigurationV1 {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(
96            f,
97            "TokenConfigurationV1 {{\n  base: {},\n  has_shielded_pool: {},\n  minimum_pool_notes_for_outgoing: {:?},\n  minimum_pool_notes_for_outgoing_change_rules: {:?},\n  transferable: {}\n}}",
98            self.base,
99            self.has_shielded_pool,
100            self.minimum_pool_notes_for_outgoing,
101            self.minimum_pool_notes_for_outgoing_change_rules,
102            self.transferable
103        )
104    }
105}