1use 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
61impl 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 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 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
365const 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#[derive(Serialize, Deserialize)]
378pub struct GenesisCeremonyParameters {
379 #[serde(default = "GenesisCeremonyParameters::default_timestamp_ms")]
380 pub chain_start_timestamp_ms: u64,
381
382 #[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 #[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 * 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 (0, 0, 0, 0, 0)
432 } else {
433 (
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 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 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 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 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 pub recipient_address: Address,
613 pub amount_nanos: u64,
619
620 pub staked_with_validator: Option<Address>,
623 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
685#[serde(rename_all = "kebab-case")]
686pub struct ValidatorAllocation {
687 pub validator: Address,
689 pub amount_nanos_to_stake: u64,
691 pub amount_nanos_to_pay_gas: u64,
693}
694
695#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
699#[serde(rename_all = "kebab-case")]
700pub struct Delegation {
701 pub delegator: Address,
703 #[serde(flatten)]
705 pub validator_allocation: ValidatorAllocation,
706}
707
708#[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 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 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 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
807pub 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}