mina_tree/account/
common.rs

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