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}