Skip to main content

iota_config/
genesis.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    collections::{BTreeMap, HashMap},
7    fs::File,
8    io::{BufReader, BufWriter},
9    path::Path,
10};
11
12use anyhow::{Context, Result};
13use fastcrypto::{
14    encoding::{Base64, Encoding},
15    hash::HashFunction,
16};
17use iota_protocol_config::{Chain, ProtocolConfig};
18use iota_sdk_types::{
19    Address, ObjectId,
20    checkpoint::{CheckpointContents, CheckpointSummary},
21};
22use iota_types::{
23    clock::Clock,
24    committee::{Committee, CommitteeWithNetworkMetadata, EpochId, ProtocolVersion},
25    crypto::DefaultHash,
26    deny_list_v1::get_deny_list_root_object,
27    effects::{TransactionEffects, TransactionEvents},
28    error::IotaResult,
29    iota_system_state::{
30        IotaSystemState, IotaSystemStateTrait, IotaSystemStateWrapper, IotaValidatorGenesis,
31        get_iota_system_state, get_iota_system_state_wrapper,
32    },
33    messages_checkpoint::{CertifiedCheckpointSummary, VerifiedCheckpoint},
34    object::Object,
35    storage::ObjectStore,
36    transaction::Transaction,
37};
38use serde::{Deserialize, Deserializer, Serialize, Serializer};
39use tracing::trace;
40
41#[derive(Clone, Debug)]
42pub struct Genesis {
43    checkpoint: CertifiedCheckpointSummary,
44    checkpoint_contents: CheckpointContents,
45    transaction: Transaction,
46    effects: TransactionEffects,
47    events: TransactionEvents,
48    objects: Vec<Object>,
49}
50
51#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
52pub struct UnsignedGenesis {
53    pub checkpoint: CheckpointSummary,
54    pub checkpoint_contents: CheckpointContents,
55    pub transaction: Transaction,
56    pub effects: TransactionEffects,
57    pub events: TransactionEvents,
58    pub objects: Vec<Object>,
59}
60
61// Hand implement PartialEq in order to get around the fact that AuthSigs don't
62// impl Eq
63impl PartialEq for Genesis {
64    fn eq(&self, other: &Self) -> bool {
65        self.checkpoint.data() == other.checkpoint.data()
66            && {
67                let this = self.checkpoint.auth_sig();
68                let other = other.checkpoint.auth_sig();
69
70                this.epoch == other.epoch
71                    && this.signature.as_ref() == other.signature.as_ref()
72                    && this.signers_map == other.signers_map
73            }
74            && self.checkpoint_contents == other.checkpoint_contents
75            && self.transaction == other.transaction
76            && self.effects == other.effects
77            && self.objects == other.objects
78    }
79}
80
81impl Eq for Genesis {}
82
83impl Genesis {
84    pub fn new(
85        checkpoint: CertifiedCheckpointSummary,
86        checkpoint_contents: CheckpointContents,
87        transaction: Transaction,
88        effects: TransactionEffects,
89        events: TransactionEvents,
90        objects: Vec<Object>,
91    ) -> Self {
92        Self {
93            checkpoint,
94            checkpoint_contents,
95            transaction,
96            effects,
97            events,
98            objects,
99        }
100    }
101
102    pub fn into_objects(self) -> Vec<Object> {
103        self.objects
104    }
105
106    pub fn objects(&self) -> &[Object] {
107        &self.objects
108    }
109
110    pub fn object(&self, id: ObjectId) -> Option<Object> {
111        self.objects.iter().find(|o| o.id() == id).cloned()
112    }
113
114    pub fn transaction(&self) -> &Transaction {
115        &self.transaction
116    }
117
118    pub fn effects(&self) -> &TransactionEffects {
119        &self.effects
120    }
121    pub fn events(&self) -> &TransactionEvents {
122        &self.events
123    }
124
125    pub fn checkpoint(&self) -> VerifiedCheckpoint {
126        self.checkpoint
127            .clone()
128            .try_into_verified(&self.committee().unwrap())
129            .unwrap()
130    }
131
132    pub fn checkpoint_contents(&self) -> &CheckpointContents {
133        &self.checkpoint_contents
134    }
135
136    pub fn epoch(&self) -> EpochId {
137        0
138    }
139
140    pub fn validator_set_for_tooling(&self) -> Vec<IotaValidatorGenesis> {
141        self.iota_system_object()
142            .into_genesis_version_for_tooling()
143            .validators
144            .active_validators
145    }
146
147    pub fn committee_with_network(&self) -> CommitteeWithNetworkMetadata {
148        self.iota_system_object().get_current_epoch_committee()
149    }
150
151    pub fn reference_gas_price(&self) -> u64 {
152        self.iota_system_object().reference_gas_price()
153    }
154
155    // TODO: No need to return IotaResult. Also consider return &.
156    pub fn committee(&self) -> IotaResult<Committee> {
157        Ok(self.committee_with_network().committee().clone())
158    }
159
160    pub fn iota_system_wrapper_object(&self) -> IotaSystemStateWrapper {
161        get_iota_system_state_wrapper(&self.objects())
162            .expect("IOTA System State Wrapper object must always exist")
163    }
164
165    pub fn contains_migrations(&self) -> bool {
166        self.checkpoint_contents.len() > 1
167    }
168
169    pub fn iota_system_object(&self) -> IotaSystemState {
170        get_iota_system_state(&self.objects()).expect("IOTA System State object must always exist")
171    }
172
173    pub fn clock(&self) -> Clock {
174        let clock = self
175            .objects()
176            .iter()
177            .find(|o| o.id() == ObjectId::CLOCK)
178            .expect("clock must always exist")
179            .data
180            .as_opt_struct()
181            .expect("clock must be a Move object");
182        bcs::from_bytes::<Clock>(clock.contents())
183            .expect("clock object deserialization cannot fail")
184    }
185
186    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, anyhow::Error> {
187        let path = path.as_ref();
188        trace!("reading Genesis from {}", path.display());
189        let read = File::open(path)
190            .with_context(|| format!("unable to load Genesis from {}", path.display()))?;
191        bcs::from_reader(BufReader::new(read))
192            .with_context(|| format!("unable to parse Genesis from {}", path.display()))
193    }
194
195    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), anyhow::Error> {
196        let path = path.as_ref();
197        trace!("writing Genesis to {}", path.display());
198        let mut write = BufWriter::new(File::create(path)?);
199        bcs::serialize_into(&mut write, &self)
200            .with_context(|| format!("unable to save Genesis to {}", path.display()))?;
201        Ok(())
202    }
203
204    pub fn to_bytes(&self) -> Vec<u8> {
205        bcs::to_bytes(self).expect("failed to serialize genesis")
206    }
207
208    pub fn hash(&self) -> [u8; 32] {
209        use std::io::Write;
210
211        let mut digest = DefaultHash::default();
212        digest.write_all(&self.to_bytes()).unwrap();
213        let hash = digest.finalize();
214        hash.into()
215    }
216}
217
218impl Serialize for Genesis {
219    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
220    where
221        S: Serializer,
222    {
223        use serde::ser::Error;
224
225        #[derive(Serialize)]
226        struct RawGenesis<'a> {
227            checkpoint: &'a CertifiedCheckpointSummary,
228            checkpoint_contents: &'a CheckpointContents,
229            transaction: &'a Transaction,
230            effects: &'a TransactionEffects,
231            events: &'a TransactionEvents,
232            objects: &'a [Object],
233        }
234
235        let raw_genesis = RawGenesis {
236            checkpoint: &self.checkpoint,
237            checkpoint_contents: &self.checkpoint_contents,
238            transaction: &self.transaction,
239            effects: &self.effects,
240            events: &self.events,
241            objects: &self.objects,
242        };
243
244        if serializer.is_human_readable() {
245            let bytes = bcs::to_bytes(&raw_genesis).map_err(|e| Error::custom(e.to_string()))?;
246            let s = Base64::encode(bytes);
247            serializer.serialize_str(&s)
248        } else {
249            raw_genesis.serialize(serializer)
250        }
251    }
252}
253
254impl<'de> Deserialize<'de> for Genesis {
255    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
256    where
257        D: Deserializer<'de>,
258    {
259        use serde::de::Error;
260
261        #[derive(Deserialize)]
262        struct RawGenesis {
263            checkpoint: CertifiedCheckpointSummary,
264            checkpoint_contents: CheckpointContents,
265            transaction: Transaction,
266            effects: TransactionEffects,
267            events: TransactionEvents,
268            objects: Vec<Object>,
269        }
270
271        let raw_genesis = if deserializer.is_human_readable() {
272            let s = String::deserialize(deserializer)?;
273            let bytes = Base64::decode(&s).map_err(|e| Error::custom(e.to_string()))?;
274            bcs::from_bytes(&bytes).map_err(|e| Error::custom(e.to_string()))?
275        } else {
276            RawGenesis::deserialize(deserializer)?
277        };
278
279        Ok(Genesis {
280            checkpoint: raw_genesis.checkpoint,
281            checkpoint_contents: raw_genesis.checkpoint_contents,
282            transaction: raw_genesis.transaction,
283            effects: raw_genesis.effects,
284            events: raw_genesis.events,
285            objects: raw_genesis.objects,
286        })
287    }
288}
289
290impl UnsignedGenesis {
291    pub fn objects(&self) -> &[Object] {
292        &self.objects
293    }
294
295    pub fn object(&self, id: ObjectId) -> Option<Object> {
296        self.objects.iter().find(|o| o.id() == id).cloned()
297    }
298
299    pub fn transaction(&self) -> &Transaction {
300        &self.transaction
301    }
302
303    pub fn effects(&self) -> &TransactionEffects {
304        &self.effects
305    }
306    pub fn events(&self) -> &TransactionEvents {
307        &self.events
308    }
309
310    pub fn checkpoint(&self) -> &CheckpointSummary {
311        &self.checkpoint
312    }
313
314    pub fn checkpoint_contents(&self) -> &CheckpointContents {
315        &self.checkpoint_contents
316    }
317
318    pub fn epoch(&self) -> EpochId {
319        0
320    }
321
322    pub fn iota_system_wrapper_object(&self) -> IotaSystemStateWrapper {
323        get_iota_system_state_wrapper(&self.objects())
324            .expect("IOTA System State Wrapper object must always exist")
325    }
326
327    pub fn iota_system_object(&self) -> IotaSystemState {
328        get_iota_system_state(&self.objects()).expect("IOTA System State object must always exist")
329    }
330
331    pub fn has_randomness_state_object(&self) -> bool {
332        self.objects()
333            .get_object(&ObjectId::RANDOMNESS_STATE)
334            .is_some()
335    }
336
337    pub fn has_bridge_object(&self) -> bool {
338        self.objects()
339            .get_object(&ObjectId::GENESIS_BRIDGE)
340            .is_some()
341    }
342
343    pub fn has_coin_deny_list_object(&self) -> bool {
344        get_deny_list_root_object(&self.objects()).is_some()
345    }
346}
347
348#[derive(Clone, Debug, Serialize, Deserialize)]
349#[serde(rename_all = "kebab-case")]
350pub struct GenesisChainParameters {
351    pub protocol_version: u64,
352    pub chain_start_timestamp_ms: u64,
353    pub epoch_duration_ms: u64,
354
355    // The validator count limits and stake thresholds are enforced from the
356    // protocol config; the fields below are retained for layout
357    // compatibility only.
358    pub max_validator_count: u64,
359    pub min_validator_joining_stake: u64,
360    pub validator_low_stake_threshold: u64,
361    pub validator_very_low_stake_threshold: u64,
362    pub validator_low_stake_grace_period: u64,
363}
364
365// These constants exist solely so that tests pinning genesis to a protocol
366// version below 32 keep producing historically-correct genesis content: real
367// genesis ceremonies require protocol version >= 32 (see the CLI guard in
368// `iota-tool`), at which point these values are enforced through the protocol
369// config instead and genesis records zeros here.
370const PRE_V32_MIN_VALIDATOR_JOINING_STAKE: u64 = 2_000_000_000_000_000;
371pub const PRE_V32_VALIDATOR_LOW_STAKE_THRESHOLD: u64 = 1_500_000_000_000_000;
372const PRE_V32_VALIDATOR_VERY_LOW_STAKE_THRESHOLD: u64 = 1_000_000_000_000_000;
373const PRE_V32_VALIDATOR_LOW_STAKE_GRACE_PERIOD: u64 = 7;
374const PRE_V32_MAX_VALIDATOR_COUNT: u64 = 150;
375
376/// Initial set of parameters for a chain.
377#[derive(Serialize, Deserialize)]
378pub struct GenesisCeremonyParameters {
379    #[serde(default = "GenesisCeremonyParameters::default_timestamp_ms")]
380    pub chain_start_timestamp_ms: u64,
381
382    /// protocol version that the chain starts at.
383    #[serde(default = "ProtocolVersion::max")]
384    pub protocol_version: ProtocolVersion,
385
386    #[serde(default = "GenesisCeremonyParameters::default_allow_insertion_of_extra_objects")]
387    pub allow_insertion_of_extra_objects: bool,
388
389    /// The duration of an epoch, in milliseconds.
390    #[serde(default = "GenesisCeremonyParameters::default_epoch_duration_ms")]
391    pub epoch_duration_ms: u64,
392}
393
394impl GenesisCeremonyParameters {
395    pub fn new() -> Self {
396        Self {
397            chain_start_timestamp_ms: Self::default_timestamp_ms(),
398            protocol_version: ProtocolVersion::MAX,
399            allow_insertion_of_extra_objects: true,
400            epoch_duration_ms: Self::default_epoch_duration_ms(),
401        }
402    }
403
404    fn default_timestamp_ms() -> u64 {
405        std::time::SystemTime::now()
406            .duration_since(std::time::UNIX_EPOCH)
407            .unwrap()
408            .as_millis() as u64
409    }
410
411    fn default_allow_insertion_of_extra_objects() -> bool {
412        true
413    }
414
415    fn default_epoch_duration_ms() -> u64 {
416        // 24 hrs
417        24 * 60 * 60 * 1000
418    }
419
420    pub fn to_genesis_chain_parameters(&self) -> GenesisChainParameters {
421        let (
422            max_validator_count,
423            min_validator_joining_stake,
424            validator_low_stake_threshold,
425            validator_very_low_stake_threshold,
426            validator_low_stake_grace_period,
427        ) = if self.protocol_version.as_u64() >= 32 {
428            // The validator count limits and stake thresholds are enforced
429            // from the protocol config; the deprecated fields are recorded
430            // as zero.
431            (0, 0, 0, 0, 0)
432        } else {
433            // Real genesis ceremonies require protocol version >= 32; this branch
434            // exists so that tests pinning genesis to an older version still get
435            // the historical values that pre-version-32 framework snapshots read
436            // out of these fields.
437            (
438                PRE_V32_MAX_VALIDATOR_COUNT,
439                PRE_V32_MIN_VALIDATOR_JOINING_STAKE,
440                PRE_V32_VALIDATOR_LOW_STAKE_THRESHOLD,
441                PRE_V32_VALIDATOR_VERY_LOW_STAKE_THRESHOLD,
442                PRE_V32_VALIDATOR_LOW_STAKE_GRACE_PERIOD,
443            )
444        };
445        GenesisChainParameters {
446            protocol_version: self.protocol_version.as_u64(),
447            chain_start_timestamp_ms: self.chain_start_timestamp_ms,
448            epoch_duration_ms: self.epoch_duration_ms,
449            max_validator_count,
450            min_validator_joining_stake,
451            validator_low_stake_threshold,
452            validator_very_low_stake_threshold,
453            validator_low_stake_grace_period,
454        }
455    }
456}
457
458impl Default for GenesisCeremonyParameters {
459    fn default() -> Self {
460        Self::new()
461    }
462}
463
464#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
465#[serde(rename_all = "kebab-case")]
466pub struct TokenDistributionSchedule {
467    pub pre_minted_supply: u64,
468    pub allocations: Vec<TokenAllocation>,
469}
470
471impl TokenDistributionSchedule {
472    pub fn contains_timelocked_stake(&self) -> bool {
473        self.allocations
474            .iter()
475            .find_map(|allocation| allocation.staked_with_timelock_expiration)
476            .is_some()
477    }
478
479    pub fn validate(&self) {
480        let mut total_nanos = self.pre_minted_supply;
481
482        for allocation in &self.allocations {
483            total_nanos = total_nanos
484                .checked_add(allocation.amount_nanos)
485                .expect("TokenDistributionSchedule allocates more than the maximum supply which equals u64::MAX");
486        }
487    }
488
489    pub fn check_minimum_stake_for_validators<I: IntoIterator<Item = Address>>(
490        &self,
491        validators: I,
492        protocol_version: ProtocolVersion,
493    ) -> Result<()> {
494        let mut validators: HashMap<Address, u64> =
495            validators.into_iter().map(|a| (a, 0)).collect();
496
497        // Check that all allocations are for valid validators, while summing up all
498        // allocations for each validator
499        for allocation in &self.allocations {
500            if let Some(staked_with_validator) = &allocation.staked_with_validator {
501                *validators
502                    .get_mut(staked_with_validator)
503                    .expect("allocation must be staked with valid validator") +=
504                    allocation.amount_nanos;
505            }
506        }
507
508        // Check that all validators have sufficient stake allocated to ensure they meet
509        // the minimum stake threshold. Below protocol version 32 the threshold isn't
510        // present in the protocol config, so fall back to the historical genesis value.
511        let minimum_required_stake =
512            ProtocolConfig::get_for_version(protocol_version, Chain::Unknown)
513                .validator_low_stake_threshold_as_option()
514                .unwrap_or(PRE_V32_VALIDATOR_LOW_STAKE_THRESHOLD);
515        for (validator, stake) in validators {
516            if stake < minimum_required_stake {
517                anyhow::bail!(
518                    "validator {validator} has '{stake}' stake and does not meet the minimum required stake threshold of '{minimum_required_stake}'"
519                );
520            }
521        }
522        Ok(())
523    }
524
525    pub fn new_for_validators_with_default_allocation<I: IntoIterator<Item = Address>>(
526        validators: I,
527        protocol_version: ProtocolVersion,
528    ) -> Self {
529        // Below protocol version 32 the threshold isn't present in the protocol
530        // config, so fall back to the historical genesis value.
531        let default_allocation = ProtocolConfig::get_for_version(protocol_version, Chain::Unknown)
532            .validator_low_stake_threshold_as_option()
533            .unwrap_or(PRE_V32_VALIDATOR_LOW_STAKE_THRESHOLD);
534
535        let allocations = validators
536            .into_iter()
537            .map(|a| TokenAllocation {
538                recipient_address: a,
539                amount_nanos: default_allocation,
540                staked_with_validator: Some(a),
541                staked_with_timelock_expiration: None,
542            })
543            .collect();
544
545        let schedule = Self {
546            pre_minted_supply: 0,
547            allocations,
548        };
549
550        schedule.validate();
551        schedule
552    }
553
554    /// Helper to read a TokenDistributionSchedule from a csv file.
555    ///
556    /// The file is encoded such that the final entry in the CSV file is used to
557    /// denote the allocation to the stake subsidy fund.
558    ///
559    /// Comments are optional, and start with a `#` character.
560    /// Only entries that start with this character are treated as comments.
561    pub fn from_csv<R: std::io::Read>(reader: R) -> Result<Self> {
562        let mut reader = csv_reader_with_comments(reader);
563        let mut allocations: Vec<TokenAllocation> =
564            reader.deserialize().collect::<Result<_, _>>()?;
565
566        let pre_minted_supply = allocations.pop().unwrap();
567        assert_eq!(
568            Address::ZERO,
569            pre_minted_supply.recipient_address,
570            "final allocation must be for the pre-minted supply amount",
571        );
572        assert!(
573            pre_minted_supply.staked_with_validator.is_none(),
574            "cannot stake the pre-minted supply amount",
575        );
576
577        let schedule = Self {
578            pre_minted_supply: pre_minted_supply.amount_nanos,
579            allocations,
580        };
581
582        schedule.validate();
583        Ok(schedule)
584    }
585
586    pub fn to_csv<W: std::io::Write>(&self, writer: W) -> Result<()> {
587        let mut writer = csv::Writer::from_writer(writer);
588
589        for allocation in &self.allocations {
590            writer.serialize(allocation)?;
591        }
592
593        writer.serialize(TokenAllocation {
594            recipient_address: Address::ZERO,
595            amount_nanos: self.pre_minted_supply,
596            staked_with_validator: None,
597            staked_with_timelock_expiration: None,
598        })?;
599
600        Ok(())
601    }
602}
603
604#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
605#[serde(rename_all = "kebab-case")]
606pub struct TokenAllocation {
607    /// Indicates the address that owns the tokens. It means that this
608    /// `TokenAllocation` can serve to stake some funds to the
609    /// `staked_with_validator` during genesis, but it's the `recipient_address`
610    /// which will receive the associated StakedIota (or TimelockedStakedIota)
611    /// object.
612    pub recipient_address: Address,
613    /// Indicates an amount of nanos that is:
614    /// - minted for the `recipient_address` and staked to a validator, only in
615    ///   the case `staked_with_validator` is Some
616    /// - minted for the `recipient_address` and transferred that address,
617    ///   otherwise.
618    pub amount_nanos: u64,
619
620    /// Indicates if this allocation should be staked at genesis and with which
621    /// validator
622    pub staked_with_validator: Option<Address>,
623    /// Indicates if this allocation should be staked with timelock at genesis
624    /// and contains its timelock_expiration
625    pub staked_with_timelock_expiration: Option<u64>,
626}
627
628#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
629pub struct TokenDistributionScheduleBuilder {
630    pre_minted_supply: u64,
631    allocations: Vec<TokenAllocation>,
632}
633
634impl TokenDistributionScheduleBuilder {
635    #[expect(clippy::new_without_default)]
636    pub fn new() -> Self {
637        Self {
638            pre_minted_supply: 0,
639            allocations: vec![],
640        }
641    }
642
643    pub fn set_pre_minted_supply(&mut self, pre_minted_supply: u64) {
644        self.pre_minted_supply = pre_minted_supply;
645    }
646
647    pub fn default_allocation_for_validators<I: IntoIterator<Item = Address>>(
648        &mut self,
649        validators: I,
650        protocol_version: ProtocolVersion,
651    ) {
652        // Below protocol version 32 the threshold isn't present in the protocol
653        // config, so fall back to the historical genesis value.
654        let default_allocation = ProtocolConfig::get_for_version(protocol_version, Chain::Unknown)
655            .validator_low_stake_threshold_as_option()
656            .unwrap_or(PRE_V32_VALIDATOR_LOW_STAKE_THRESHOLD);
657
658        for validator in validators {
659            self.add_allocation(TokenAllocation {
660                recipient_address: validator,
661                amount_nanos: default_allocation,
662                staked_with_validator: Some(validator),
663                staked_with_timelock_expiration: None,
664            });
665        }
666    }
667
668    pub fn add_allocation(&mut self, allocation: TokenAllocation) {
669        self.allocations.push(allocation);
670    }
671
672    pub fn build(&self) -> TokenDistributionSchedule {
673        let schedule = TokenDistributionSchedule {
674            pre_minted_supply: self.pre_minted_supply,
675            allocations: self.allocations.clone(),
676        };
677
678        schedule.validate();
679        schedule
680    }
681}
682
683/// Represents the allocation of stake and gas payment to a validator.
684#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
685#[serde(rename_all = "kebab-case")]
686pub struct ValidatorAllocation {
687    /// The validator address receiving the stake and/or gas payment
688    pub validator: Address,
689    /// The amount of nanos to stake to the validator
690    pub amount_nanos_to_stake: u64,
691    /// The amount of nanos to transfer as gas payment to the validator
692    pub amount_nanos_to_pay_gas: u64,
693}
694
695/// Represents a delegation of stake and gas payment to a validator,
696/// coming from a delegator. This struct is used to serialize and deserialize
697/// delegations to and from a csv file.
698#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
699#[serde(rename_all = "kebab-case")]
700pub struct Delegation {
701    /// The address from which to take the nanos for staking/gas
702    pub delegator: Address,
703    /// The allocation to a validator receiving a stake and/or a gas payment
704    #[serde(flatten)]
705    pub validator_allocation: ValidatorAllocation,
706}
707
708/// Represents genesis delegations to validators.
709///
710/// This struct maps a delegator address to a list of validators and their
711/// stake and gas allocations. Each ValidatorAllocation contains the address of
712/// a validator that will receive an amount of nanos to stake and an amount as
713/// gas payment.
714#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
715#[serde(rename_all = "kebab-case")]
716pub struct Delegations {
717    pub allocations: BTreeMap<Address, Vec<ValidatorAllocation>>,
718}
719
720impl Delegations {
721    pub fn new_for_validators_with_default_allocation(
722        validators: impl IntoIterator<Item = Address>,
723        delegator: Address,
724        protocol_version: ProtocolVersion,
725    ) -> Self {
726        // Below protocol version 32 the threshold isn't present in the protocol
727        // config, so fall back to the historical genesis value.
728        let min_validator_joining_stake =
729            ProtocolConfig::get_for_version(protocol_version, Chain::Unknown)
730                .min_validator_joining_stake_as_option()
731                .unwrap_or(PRE_V32_MIN_VALIDATOR_JOINING_STAKE);
732        let validator_allocations = validators
733            .into_iter()
734            .map(|address| ValidatorAllocation {
735                validator: address,
736                amount_nanos_to_stake: min_validator_joining_stake,
737                amount_nanos_to_pay_gas: 0,
738            })
739            .collect();
740
741        let mut allocations = BTreeMap::new();
742        allocations.insert(delegator, validator_allocations);
743
744        Self { allocations }
745    }
746
747    /// Helper to read a Delegations struct from a csv file.
748    ///
749    /// The file is encoded such that the final entry in the CSV file is used to
750    /// denote the allocation coming from a delegator. It must be in the
751    /// following format:
752    /// `delegator,validator,amount-nanos-to-stake,amount-nanos-to-pay-gas
753    /// <delegator1-address>,<validator-1-address>,2000000000000000,5000000000
754    /// <delegator1-address>,<validator-2-address>,3000000000000000,5000000000
755    /// <delegator2-address>,<validator-3-address>,4500000000000000,5000000000`
756    ///
757    /// Comments are optional, and start with a `#` character.
758    /// Only entries that start with this character are treated as comments.
759    pub fn from_csv<R: std::io::Read>(reader: R) -> Result<Self> {
760        let mut reader = csv_reader_with_comments(reader);
761
762        let mut delegations = Self::default();
763        for delegation in reader.deserialize::<Delegation>() {
764            let delegation = delegation?;
765            delegations
766                .allocations
767                .entry(delegation.delegator)
768                .or_default()
769                .push(delegation.validator_allocation);
770        }
771
772        Ok(delegations)
773    }
774
775    /// Helper to write a Delegations struct into a csv file.
776    ///
777    /// It writes in the following format:
778    /// `delegator,validator,amount-nanos-to-stake,amount-nanos-to-pay-gas
779    /// <delegator1-address>,<validator-1-address>,2000000000000000,5000000000
780    /// <delegator1-address>,<validator-2-address>,3000000000000000,5000000000
781    /// <delegator2-address>,<validator-3-address>,4500000000000000,5000000000`
782    pub fn to_csv<W: std::io::Write>(&self, writer: W) -> Result<()> {
783        let mut writer = csv::Writer::from_writer(writer);
784
785        writer.write_record([
786            "delegator",
787            "validator",
788            "amount-nanos-to-stake",
789            "amount-nanos-to-pay-gas",
790        ])?;
791
792        for (&delegator, validator_allocations) in &self.allocations {
793            for validator_allocation in validator_allocations {
794                writer.write_record(&[
795                    delegator.to_string(),
796                    validator_allocation.validator.to_string(),
797                    validator_allocation.amount_nanos_to_stake.to_string(),
798                    validator_allocation.amount_nanos_to_pay_gas.to_string(),
799                ])?;
800            }
801        }
802
803        Ok(())
804    }
805}
806
807/// Helper function to create a CSV reader with custom settings.
808/// In this case, it sets the comment character to `#`.
809pub fn csv_reader_with_comments<R: std::io::Read>(reader: R) -> csv::Reader<R> {
810    csv::ReaderBuilder::new()
811        .comment(Some(b'#'))
812        .from_reader(reader)
813}