mina_tree/account/
common.rs

1use crate::{
2    proofs::{
3        field::{Boolean, FieldWitness, ToBoolean},
4        numbers::{
5            currency::{CheckedAmount, CheckedBalance},
6            nat::{CheckedSlot, CheckedSlotSpan},
7        },
8        to_field_elements::ToFieldElements,
9    },
10    scan_state::currency::{Amount, Balance, Magnitude, Slot, SlotSpan},
11    ControlTag, ToInputs,
12};
13use ark_ff::{UniformRand, Zero};
14use mina_curves::pasta::Fp;
15use o1_utils::{field_helpers::FieldHelpersError, FieldHelpers};
16use serde::{Deserialize, Serialize};
17use std::str::FromStr;
18
19#[derive(Clone, Debug, Default, PartialEq, Eq)]
20pub struct VotingFor(pub Fp);
21
22impl VotingFor {
23    pub fn dummy() -> Self {
24        Self(Fp::zero())
25    }
26
27    pub fn parse_str(s: &str) -> Result<Self, FieldHelpersError> {
28        let b58check_hash = mina_p2p_messages::v2::StateHash::from_str(s).unwrap();
29        Ok(Self(
30            b58check_hash
31                .into_inner()
32                .0
33                .try_into()
34                .map_err(|_| FieldHelpersError::FromBigToField)?,
35        ))
36    }
37
38    pub fn to_base58check(&self) -> String {
39        let state_hash = mina_p2p_messages::v2::StateHash::from_fp(self.0);
40        state_hash.to_string()
41    }
42
43    pub fn to_base58check_graphql(&self) -> String {
44        // NOTE: See https://github.com/MinaProtocol/mina/blob/fb1c3c0a408c344810140bdbcedacc532a11be91/src/lib/mina_graphql/types.ml#L1528
45        let receipt_chain_hash = ReceiptChainHash(self.0);
46        let receipt_chain_hash = mina_p2p_messages::v2::ReceiptChainHash::from(receipt_chain_hash);
47        receipt_chain_hash.to_string()
48    }
49}
50
51#[test]
52fn test_voting_for_b58decode() {
53    let source = "3NK2tkzqqK5spR2sZ7tujjqPksL45M3UUrcA4WhCkeiPtnugyE2x";
54    let voting_for = VotingFor::parse_str(source).unwrap();
55    assert_eq!(&voting_for.to_base58check(), source);
56}
57
58impl ToFieldElements<Fp> for VotingFor {
59    fn to_field_elements(&self, fields: &mut Vec<Fp>) {
60        let Self(f) = self;
61        f.to_field_elements(fields)
62    }
63}
64
65impl ToInputs for VotingFor {
66    fn to_inputs(&self, inputs: &mut poseidon::hash::Inputs) {
67        inputs.append_field(self.0);
68    }
69}
70
71#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct ReceiptChainHash(pub Fp);
73
74impl ToInputs for ReceiptChainHash {
75    fn to_inputs(&self, inputs: &mut poseidon::hash::Inputs) {
76        inputs.append_field(self.0);
77    }
78}
79
80impl ReceiptChainHash {
81    pub fn empty_legacy() -> Self {
82        // Value of `Receipt.Chain_hash.empty` in Ocaml (`compatible` branch)
83        Self::from_hex("0b143c0645497a5987a7b88f66340e03db943f0a0df48b69a3a82921ce97b10a").unwrap()
84    }
85
86    pub fn empty() -> Self {
87        Self::empty_legacy()
88        // Self(hash_noinputs("CodaReceiptEmpty"))
89    }
90
91    pub fn from_hex(s: &str) -> Result<Self, FieldHelpersError> {
92        Fp::from_hex(s).map(Self)
93    }
94
95    pub fn parse_str(s: &str) -> Result<Self, FieldHelpersError> {
96        let b58check_hash = mina_p2p_messages::v2::PendingCoinbaseHash::from_str(s).unwrap();
97        Ok(Self(
98            b58check_hash
99                .into_inner()
100                .0
101                 .0
102                .try_into()
103                .map_err(|_| FieldHelpersError::FromBigToField)?,
104        ))
105    }
106
107    // TODO(tizoc): implement `to_string` and improve the test bellow
108
109    pub fn gen() -> Self {
110        Self(Fp::rand(&mut rand::thread_rng()))
111    }
112}
113
114#[test]
115fn test_receipt_chain_b58decode() {
116    let source = "2mzbV7WevxLuchs2dAMY4vQBS6XttnCUF8Hvks4XNBQ5qiSGGBQe";
117    ReceiptChainHash::parse_str(source).unwrap();
118
119    let source = "2n2K1aziimdYu5QCf8mU4gducZCB5u5s78sGnp56zT2tig4ugVHD";
120    ReceiptChainHash::parse_str(source).unwrap();
121}
122
123impl Default for ReceiptChainHash {
124    fn default() -> Self {
125        Self::empty_legacy()
126    }
127}
128
129// CodaReceiptEmpty
130
131/// A timed account is an account, which releases its balance to be spent
132/// gradually. The process of releasing frozen funds is defined as follows.
133/// Until the cliff_time global slot is reached, the initial_minimum_balance
134/// of mina is frozen and cannot be spent. At the cliff slot, cliff_amount
135/// is released and initial_minimum_balance is effectively lowered by that
136/// amount. Next, every vesting_period number of slots, vesting_increment
137/// is released, further decreasing the current minimum balance. At some
138/// point minimum balance drops to 0, and after that the account behaves
139/// like an untimed one. *)
140///
141/// <https://github.com/MinaProtocol/mina/blob/2ff0292b637684ce0372e7b8e23ec85404dc5091/src/lib/mina_base/account_timing.ml#L22>
142#[derive(Clone, Debug, PartialEq, Eq)]
143pub enum Timing {
144    Untimed,
145    Timed {
146        initial_minimum_balance: Balance,
147        cliff_time: Slot,
148        cliff_amount: Amount,
149        vesting_period: SlotSpan,
150        vesting_increment: Amount,
151    },
152}
153
154impl Timing {
155    pub fn is_timed(&self) -> bool {
156        match self {
157            Timing::Untimed => false,
158            Timing::Timed { .. } => true,
159        }
160    }
161
162    pub fn to_record(&self) -> TimingAsRecord {
163        match self.clone() {
164            Timing::Untimed => TimingAsRecord {
165                is_timed: false,
166                initial_minimum_balance: Balance::zero(),
167                cliff_time: Slot::zero(),
168                cliff_amount: Amount::zero(),
169                vesting_period: SlotSpan::from_u32(1),
170                vesting_increment: Amount::zero(),
171            },
172            Timing::Timed {
173                initial_minimum_balance,
174                cliff_time,
175                cliff_amount,
176                vesting_period,
177                vesting_increment,
178            } => TimingAsRecord {
179                is_timed: true,
180                initial_minimum_balance,
181                cliff_time,
182                cliff_amount,
183                vesting_period,
184                vesting_increment,
185            },
186        }
187    }
188
189    pub fn to_record_checked<F: FieldWitness>(&self) -> TimingAsRecordChecked<F> {
190        let TimingAsRecord {
191            is_timed,
192            initial_minimum_balance,
193            cliff_time,
194            cliff_amount,
195            vesting_period,
196            vesting_increment,
197        } = self.to_record();
198
199        TimingAsRecordChecked {
200            is_timed: is_timed.to_boolean(),
201            initial_minimum_balance: initial_minimum_balance.to_checked(),
202            cliff_time: cliff_time.to_checked(),
203            cliff_amount: cliff_amount.to_checked(),
204            vesting_period: vesting_period.to_checked(),
205            vesting_increment: vesting_increment.to_checked(),
206        }
207    }
208}
209
210pub struct TimingAsRecord {
211    pub is_timed: bool,
212    pub initial_minimum_balance: Balance,
213    pub cliff_time: Slot,
214    pub cliff_amount: Amount,
215    pub vesting_period: SlotSpan,
216    pub vesting_increment: Amount,
217}
218
219pub struct TimingAsRecordChecked<F: FieldWitness> {
220    pub is_timed: Boolean,
221    pub initial_minimum_balance: CheckedBalance<F>,
222    pub cliff_time: CheckedSlot<F>,
223    pub cliff_amount: CheckedAmount<F>,
224    pub vesting_period: CheckedSlotSpan<F>,
225    pub vesting_increment: CheckedAmount<F>,
226}
227
228// <https://github.com/MinaProtocol/mina/blob/develop/src/lib/mina_numbers/intf.ml#L155>
229// pub type Nonce = u32;
230
231// <https://github.com/MinaProtocol/mina/blob/develop/src/lib/mina_base/token_permissions.ml#L9>
232#[derive(Clone, Debug, PartialEq, Eq)]
233pub enum TokenPermissions {
234    TokenOwned { disable_new_accounts: bool },
235    NotOwned { account_disabled: bool },
236}
237
238impl Default for TokenPermissions {
239    fn default() -> Self {
240        Self::NotOwned {
241            account_disabled: false,
242        }
243    }
244}
245
246// <https://github.com/MinaProtocol/mina/blob/develop/src/lib/mina_base/permissions.mli#L10>
247#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, strum_macros::Display)]
248#[serde(rename_all = "lowercase")]
249#[derive(Default)]
250pub enum AuthRequired {
251    #[default]
252    None,
253    Either,
254    Proof,
255    Signature,
256    Impossible,
257    Both, // Legacy only
258}
259
260impl From<ControlTag> for AuthRequired {
261    /// <https://github.com/MinaProtocol/mina/blob/3753a8593cc1577bcf4da16620daf9946d88e8e5/src/lib/mina_base/permissions.ml#L68>
262    fn from(value: ControlTag) -> Self {
263        match value {
264            ControlTag::Proof => Self::Proof,
265            ControlTag::Signature => Self::Signature,
266            ControlTag::NoneGiven => Self::None,
267        }
268    }
269}
270
271#[derive(Copy, Clone, Debug)]
272pub struct AuthRequiredEncoded<Bool> {
273    pub constant: Bool,
274    pub signature_necessary: Bool,
275    pub signature_sufficient: Bool,
276}
277
278impl AuthRequired {
279    pub fn encode(self) -> AuthRequiredEncoded<bool> {
280        let (constant, signature_necessary, signature_sufficient) = match self {
281            AuthRequired::None => (true, false, true),
282            AuthRequired::Either => (false, false, true),
283            AuthRequired::Proof => (false, false, false),
284            AuthRequired::Signature => (false, true, true),
285            AuthRequired::Impossible => (true, true, false),
286            AuthRequired::Both => (false, true, false),
287        };
288
289        AuthRequiredEncoded {
290            constant,
291            signature_necessary,
292            signature_sufficient,
293        }
294    }
295
296    /// permissions such that [check permission (Proof _)] is true
297    ///
298    /// <https://github.com/MinaProtocol/mina/blob/3753a8593cc1577bcf4da16620daf9946d88e8e5/src/lib/mina_base/permissions.ml#L78>
299    pub fn gen_for_proof_authorization(rng: &mut rand::rngs::ThreadRng) -> Self {
300        use rand::seq::SliceRandom;
301
302        [Self::None, Self::Either, Self::Proof]
303            .choose(rng)
304            .cloned()
305            .unwrap()
306    }
307
308    /// permissions such that [check permission (Signature _)] is true
309    ///
310    /// <https://github.com/MinaProtocol/mina/blob/3753a8593cc1577bcf4da16620daf9946d88e8e5/src/lib/mina_base/permissions.ml#L82>
311    pub fn gen_for_signature_authorization(rng: &mut rand::rngs::ThreadRng) -> Self {
312        use rand::seq::SliceRandom;
313
314        [Self::None, Self::Either, Self::Signature]
315            .choose(rng)
316            .cloned()
317            .unwrap()
318    }
319
320    /// permissions such that [check permission None_given] is true
321    ///
322    /// <https://github.com/MinaProtocol/mina/blob/3753a8593cc1577bcf4da16620daf9946d88e8e5/src/lib/mina_base/permissions.ml#L86>
323    pub fn gen_for_none_given_authorization(_rng: &mut rand::rngs::ThreadRng) -> Self {
324        Self::None
325    }
326
327    pub fn verification_key_perm_fallback_to_signature_with_older_version(&self) -> Self {
328        use AuthRequired::*;
329
330        match self {
331            Impossible | Proof => Signature,
332            x => *x,
333        }
334    }
335}
336
337impl AuthRequiredEncoded<bool> {
338    pub fn decode(self) -> AuthRequired {
339        match (
340            self.constant,
341            self.signature_necessary,
342            self.signature_sufficient,
343        ) {
344            (true, _, false) => AuthRequired::Impossible,
345            (true, _, true) => AuthRequired::None,
346            (false, false, false) => AuthRequired::Proof,
347            (false, true, true) => AuthRequired::Signature,
348            (false, false, true) => AuthRequired::Either,
349            (false, true, false) => AuthRequired::Both,
350        }
351    }
352
353    pub fn to_bits(self) -> [bool; 3] {
354        [
355            self.constant,
356            self.signature_necessary,
357            self.signature_sufficient,
358        ]
359    }
360}