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_types::{
18 base_types::{IotaAddress, ObjectID},
19 clock::Clock,
20 committee::{Committee, CommitteeWithNetworkMetadata, EpochId, ProtocolVersion},
21 crypto::DefaultHash,
22 deny_list_v1::get_deny_list_root_object,
23 effects::{TransactionEffects, TransactionEvents},
24 error::IotaResult,
25 iota_system_state::{
26 IotaSystemState, IotaSystemStateTrait, IotaSystemStateWrapper, IotaValidatorGenesis,
27 get_iota_system_state, get_iota_system_state_wrapper,
28 },
29 messages_checkpoint::{
30 CertifiedCheckpointSummary, CheckpointContents, CheckpointSummary, VerifiedCheckpoint,
31 },
32 object::Object,
33 storage::ObjectStore,
34 transaction::Transaction,
35};
36use serde::{Deserialize, Deserializer, Serialize, Serializer};
37use tracing::trace;
38
39#[derive(Clone, Debug)]
40pub struct Genesis {
41 checkpoint: CertifiedCheckpointSummary,
42 checkpoint_contents: CheckpointContents,
43 transaction: Transaction,
44 effects: TransactionEffects,
45 events: TransactionEvents,
46 objects: Vec<Object>,
47}
48
49#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
50pub struct UnsignedGenesis {
51 pub checkpoint: CheckpointSummary,
52 pub checkpoint_contents: CheckpointContents,
53 pub transaction: Transaction,
54 pub effects: TransactionEffects,
55 pub events: TransactionEvents,
56 pub objects: Vec<Object>,
57}
58
59impl PartialEq for Genesis {
62 fn eq(&self, other: &Self) -> bool {
63 self.checkpoint.data() == other.checkpoint.data()
64 && {
65 let this = self.checkpoint.auth_sig();
66 let other = other.checkpoint.auth_sig();
67
68 this.epoch == other.epoch
69 && this.signature.as_ref() == other.signature.as_ref()
70 && this.signers_map == other.signers_map
71 }
72 && self.checkpoint_contents == other.checkpoint_contents
73 && self.transaction == other.transaction
74 && self.effects == other.effects
75 && self.objects == other.objects
76 }
77}
78
79impl Eq for Genesis {}
80
81impl Genesis {
82 pub fn new(
83 checkpoint: CertifiedCheckpointSummary,
84 checkpoint_contents: CheckpointContents,
85 transaction: Transaction,
86 effects: TransactionEffects,
87 events: TransactionEvents,
88 objects: Vec<Object>,
89 ) -> Self {
90 Self {
91 checkpoint,
92 checkpoint_contents,
93 transaction,
94 effects,
95 events,
96 objects,
97 }
98 }
99
100 pub fn into_objects(self) -> Vec<Object> {
101 self.objects
102 }
103
104 pub fn objects(&self) -> &[Object] {
105 &self.objects
106 }
107
108 pub fn object(&self, id: ObjectID) -> Option<Object> {
109 self.objects.iter().find(|o| o.id() == id).cloned()
110 }
111
112 pub fn transaction(&self) -> &Transaction {
113 &self.transaction
114 }
115
116 pub fn effects(&self) -> &TransactionEffects {
117 &self.effects
118 }
119 pub fn events(&self) -> &TransactionEvents {
120 &self.events
121 }
122
123 pub fn checkpoint(&self) -> VerifiedCheckpoint {
124 self.checkpoint
125 .clone()
126 .try_into_verified(&self.committee().unwrap())
127 .unwrap()
128 }
129
130 pub fn checkpoint_contents(&self) -> &CheckpointContents {
131 &self.checkpoint_contents
132 }
133
134 pub fn epoch(&self) -> EpochId {
135 0
136 }
137
138 pub fn validator_set_for_tooling(&self) -> Vec<IotaValidatorGenesis> {
139 self.iota_system_object()
140 .into_genesis_version_for_tooling()
141 .validators
142 .active_validators
143 }
144
145 pub fn committee_with_network(&self) -> CommitteeWithNetworkMetadata {
146 self.iota_system_object().get_current_epoch_committee()
147 }
148
149 pub fn reference_gas_price(&self) -> u64 {
150 self.iota_system_object().reference_gas_price()
151 }
152
153 pub fn committee(&self) -> IotaResult<Committee> {
155 Ok(self.committee_with_network().committee().clone())
156 }
157
158 pub fn iota_system_wrapper_object(&self) -> IotaSystemStateWrapper {
159 get_iota_system_state_wrapper(&self.objects())
160 .expect("IOTA System State Wrapper object must always exist")
161 }
162
163 pub fn contains_migrations(&self) -> bool {
164 self.checkpoint_contents.size() > 1
165 }
166
167 pub fn iota_system_object(&self) -> IotaSystemState {
168 get_iota_system_state(&self.objects()).expect("IOTA System State object must always exist")
169 }
170
171 pub fn clock(&self) -> Clock {
172 let clock = self
173 .objects()
174 .iter()
175 .find(|o| o.id() == ObjectID::CLOCK)
176 .expect("clock must always exist")
177 .data
178 .as_struct_opt()
179 .expect("clock must be a Move object");
180 bcs::from_bytes::<Clock>(clock.contents())
181 .expect("clock object deserialization cannot fail")
182 }
183
184 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, anyhow::Error> {
185 let path = path.as_ref();
186 trace!("reading Genesis from {}", path.display());
187 let read = File::open(path)
188 .with_context(|| format!("unable to load Genesis from {}", path.display()))?;
189 bcs::from_reader(BufReader::new(read))
190 .with_context(|| format!("unable to parse Genesis from {}", path.display()))
191 }
192
193 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), anyhow::Error> {
194 let path = path.as_ref();
195 trace!("writing Genesis to {}", path.display());
196 let mut write = BufWriter::new(File::create(path)?);
197 bcs::serialize_into(&mut write, &self)
198 .with_context(|| format!("unable to save Genesis to {}", path.display()))?;
199 Ok(())
200 }
201
202 pub fn to_bytes(&self) -> Vec<u8> {
203 bcs::to_bytes(self).expect("failed to serialize genesis")
204 }
205
206 pub fn hash(&self) -> [u8; 32] {
207 use std::io::Write;
208
209 let mut digest = DefaultHash::default();
210 digest.write_all(&self.to_bytes()).unwrap();
211 let hash = digest.finalize();
212 hash.into()
213 }
214}
215
216impl Serialize for Genesis {
217 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
218 where
219 S: Serializer,
220 {
221 use serde::ser::Error;
222
223 #[derive(Serialize)]
224 struct RawGenesis<'a> {
225 checkpoint: &'a CertifiedCheckpointSummary,
226 checkpoint_contents: &'a CheckpointContents,
227 transaction: &'a Transaction,
228 effects: &'a TransactionEffects,
229 events: &'a TransactionEvents,
230 objects: &'a [Object],
231 }
232
233 let raw_genesis = RawGenesis {
234 checkpoint: &self.checkpoint,
235 checkpoint_contents: &self.checkpoint_contents,
236 transaction: &self.transaction,
237 effects: &self.effects,
238 events: &self.events,
239 objects: &self.objects,
240 };
241
242 if serializer.is_human_readable() {
243 let bytes = bcs::to_bytes(&raw_genesis).map_err(|e| Error::custom(e.to_string()))?;
244 let s = Base64::encode(bytes);
245 serializer.serialize_str(&s)
246 } else {
247 raw_genesis.serialize(serializer)
248 }
249 }
250}
251
252impl<'de> Deserialize<'de> for Genesis {
253 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
254 where
255 D: Deserializer<'de>,
256 {
257 use serde::de::Error;
258
259 #[derive(Deserialize)]
260 struct RawGenesis {
261 checkpoint: CertifiedCheckpointSummary,
262 checkpoint_contents: CheckpointContents,
263 transaction: Transaction,
264 effects: TransactionEffects,
265 events: TransactionEvents,
266 objects: Vec<Object>,
267 }
268
269 let raw_genesis = if deserializer.is_human_readable() {
270 let s = String::deserialize(deserializer)?;
271 let bytes = Base64::decode(&s).map_err(|e| Error::custom(e.to_string()))?;
272 bcs::from_bytes(&bytes).map_err(|e| Error::custom(e.to_string()))?
273 } else {
274 RawGenesis::deserialize(deserializer)?
275 };
276
277 Ok(Genesis {
278 checkpoint: raw_genesis.checkpoint,
279 checkpoint_contents: raw_genesis.checkpoint_contents,
280 transaction: raw_genesis.transaction,
281 effects: raw_genesis.effects,
282 events: raw_genesis.events,
283 objects: raw_genesis.objects,
284 })
285 }
286}
287
288impl UnsignedGenesis {
289 pub fn objects(&self) -> &[Object] {
290 &self.objects
291 }
292
293 pub fn object(&self, id: ObjectID) -> Option<Object> {
294 self.objects.iter().find(|o| o.id() == id).cloned()
295 }
296
297 pub fn transaction(&self) -> &Transaction {
298 &self.transaction
299 }
300
301 pub fn effects(&self) -> &TransactionEffects {
302 &self.effects
303 }
304 pub fn events(&self) -> &TransactionEvents {
305 &self.events
306 }
307
308 pub fn checkpoint(&self) -> &CheckpointSummary {
309 &self.checkpoint
310 }
311
312 pub fn checkpoint_contents(&self) -> &CheckpointContents {
313 &self.checkpoint_contents
314 }
315
316 pub fn epoch(&self) -> EpochId {
317 0
318 }
319
320 pub fn iota_system_wrapper_object(&self) -> IotaSystemStateWrapper {
321 get_iota_system_state_wrapper(&self.objects())
322 .expect("IOTA System State Wrapper object must always exist")
323 }
324
325 pub fn iota_system_object(&self) -> IotaSystemState {
326 get_iota_system_state(&self.objects()).expect("IOTA System State object must always exist")
327 }
328
329 pub fn has_randomness_state_object(&self) -> bool {
330 self.objects()
331 .get_object(&ObjectID::RANDOMNESS_STATE)
332 .is_some()
333 }
334
335 pub fn has_bridge_object(&self) -> bool {
336 self.objects()
337 .get_object(&ObjectID::GENESIS_BRIDGE)
338 .is_some()
339 }
340
341 pub fn has_coin_deny_list_object(&self) -> bool {
342 get_deny_list_root_object(&self.objects()).is_some()
343 }
344}
345
346#[derive(Clone, Debug, Serialize, Deserialize)]
347#[serde(rename_all = "kebab-case")]
348pub struct GenesisChainParameters {
349 pub protocol_version: u64,
350 pub chain_start_timestamp_ms: u64,
351 pub epoch_duration_ms: u64,
352
353 pub max_validator_count: u64,
355 pub min_validator_joining_stake: u64,
356 pub validator_low_stake_threshold: u64,
357 pub validator_very_low_stake_threshold: u64,
358 pub validator_low_stake_grace_period: u64,
359}
360
361#[derive(Serialize, Deserialize)]
363pub struct GenesisCeremonyParameters {
364 #[serde(default = "GenesisCeremonyParameters::default_timestamp_ms")]
365 pub chain_start_timestamp_ms: u64,
366
367 #[serde(default = "ProtocolVersion::max")]
369 pub protocol_version: ProtocolVersion,
370
371 #[serde(default = "GenesisCeremonyParameters::default_allow_insertion_of_extra_objects")]
372 pub allow_insertion_of_extra_objects: bool,
373
374 #[serde(default = "GenesisCeremonyParameters::default_epoch_duration_ms")]
376 pub epoch_duration_ms: u64,
377}
378
379impl GenesisCeremonyParameters {
380 pub fn new() -> Self {
381 Self {
382 chain_start_timestamp_ms: Self::default_timestamp_ms(),
383 protocol_version: ProtocolVersion::MAX,
384 allow_insertion_of_extra_objects: true,
385 epoch_duration_ms: Self::default_epoch_duration_ms(),
386 }
387 }
388
389 fn default_timestamp_ms() -> u64 {
390 std::time::SystemTime::now()
391 .duration_since(std::time::UNIX_EPOCH)
392 .unwrap()
393 .as_millis() as u64
394 }
395
396 fn default_allow_insertion_of_extra_objects() -> bool {
397 true
398 }
399
400 fn default_epoch_duration_ms() -> u64 {
401 24 * 60 * 60 * 1000
403 }
404
405 pub fn to_genesis_chain_parameters(&self) -> GenesisChainParameters {
406 GenesisChainParameters {
407 protocol_version: self.protocol_version.as_u64(),
408 chain_start_timestamp_ms: self.chain_start_timestamp_ms,
409 epoch_duration_ms: self.epoch_duration_ms,
410 max_validator_count: iota_types::governance::MAX_VALIDATOR_COUNT,
411 min_validator_joining_stake: iota_types::governance::MIN_VALIDATOR_JOINING_STAKE_NANOS,
412 validator_low_stake_threshold:
413 iota_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_NANOS,
414 validator_very_low_stake_threshold:
415 iota_types::governance::VALIDATOR_VERY_LOW_STAKE_THRESHOLD_NANOS,
416 validator_low_stake_grace_period:
417 iota_types::governance::VALIDATOR_LOW_STAKE_GRACE_PERIOD,
418 }
419 }
420}
421
422impl Default for GenesisCeremonyParameters {
423 fn default() -> Self {
424 Self::new()
425 }
426}
427
428#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
429#[serde(rename_all = "kebab-case")]
430pub struct TokenDistributionSchedule {
431 pub pre_minted_supply: u64,
432 pub allocations: Vec<TokenAllocation>,
433}
434
435impl TokenDistributionSchedule {
436 pub fn contains_timelocked_stake(&self) -> bool {
437 self.allocations
438 .iter()
439 .find_map(|allocation| allocation.staked_with_timelock_expiration)
440 .is_some()
441 }
442
443 pub fn validate(&self) {
444 let mut total_nanos = self.pre_minted_supply;
445
446 for allocation in &self.allocations {
447 total_nanos = total_nanos
448 .checked_add(allocation.amount_nanos)
449 .expect("TokenDistributionSchedule allocates more than the maximum supply which equals u64::MAX");
450 }
451 }
452
453 pub fn check_minimum_stake_for_validators<I: IntoIterator<Item = IotaAddress>>(
454 &self,
455 validators: I,
456 ) -> Result<()> {
457 let mut validators: HashMap<IotaAddress, u64> =
458 validators.into_iter().map(|a| (a, 0)).collect();
459
460 for allocation in &self.allocations {
463 if let Some(staked_with_validator) = &allocation.staked_with_validator {
464 *validators
465 .get_mut(staked_with_validator)
466 .expect("allocation must be staked with valid validator") +=
467 allocation.amount_nanos;
468 }
469 }
470
471 let minimum_required_stake = iota_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_NANOS;
474 for (validator, stake) in validators {
475 if stake < minimum_required_stake {
476 anyhow::bail!(
477 "validator {validator} has '{stake}' stake and does not meet the minimum required stake threshold of '{minimum_required_stake}'"
478 );
479 }
480 }
481 Ok(())
482 }
483
484 pub fn new_for_validators_with_default_allocation<I: IntoIterator<Item = IotaAddress>>(
485 validators: I,
486 ) -> Self {
487 let default_allocation = iota_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_NANOS;
488
489 let allocations = validators
490 .into_iter()
491 .map(|a| TokenAllocation {
492 recipient_address: a,
493 amount_nanos: default_allocation,
494 staked_with_validator: Some(a),
495 staked_with_timelock_expiration: None,
496 })
497 .collect();
498
499 let schedule = Self {
500 pre_minted_supply: 0,
501 allocations,
502 };
503
504 schedule.validate();
505 schedule
506 }
507
508 pub fn from_csv<R: std::io::Read>(reader: R) -> Result<Self> {
516 let mut reader = csv_reader_with_comments(reader);
517 let mut allocations: Vec<TokenAllocation> =
518 reader.deserialize().collect::<Result<_, _>>()?;
519
520 let pre_minted_supply = allocations.pop().unwrap();
521 assert_eq!(
522 IotaAddress::ZERO,
523 pre_minted_supply.recipient_address,
524 "final allocation must be for the pre-minted supply amount",
525 );
526 assert!(
527 pre_minted_supply.staked_with_validator.is_none(),
528 "cannot stake the pre-minted supply amount",
529 );
530
531 let schedule = Self {
532 pre_minted_supply: pre_minted_supply.amount_nanos,
533 allocations,
534 };
535
536 schedule.validate();
537 Ok(schedule)
538 }
539
540 pub fn to_csv<W: std::io::Write>(&self, writer: W) -> Result<()> {
541 let mut writer = csv::Writer::from_writer(writer);
542
543 for allocation in &self.allocations {
544 writer.serialize(allocation)?;
545 }
546
547 writer.serialize(TokenAllocation {
548 recipient_address: IotaAddress::ZERO,
549 amount_nanos: self.pre_minted_supply,
550 staked_with_validator: None,
551 staked_with_timelock_expiration: None,
552 })?;
553
554 Ok(())
555 }
556}
557
558#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
559#[serde(rename_all = "kebab-case")]
560pub struct TokenAllocation {
561 pub recipient_address: IotaAddress,
567 pub amount_nanos: u64,
573
574 pub staked_with_validator: Option<IotaAddress>,
577 pub staked_with_timelock_expiration: Option<u64>,
580}
581
582#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
583pub struct TokenDistributionScheduleBuilder {
584 pre_minted_supply: u64,
585 allocations: Vec<TokenAllocation>,
586}
587
588impl TokenDistributionScheduleBuilder {
589 #[expect(clippy::new_without_default)]
590 pub fn new() -> Self {
591 Self {
592 pre_minted_supply: 0,
593 allocations: vec![],
594 }
595 }
596
597 pub fn set_pre_minted_supply(&mut self, pre_minted_supply: u64) {
598 self.pre_minted_supply = pre_minted_supply;
599 }
600
601 pub fn default_allocation_for_validators<I: IntoIterator<Item = IotaAddress>>(
602 &mut self,
603 validators: I,
604 ) {
605 let default_allocation = iota_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_NANOS;
606
607 for validator in validators {
608 self.add_allocation(TokenAllocation {
609 recipient_address: validator,
610 amount_nanos: default_allocation,
611 staked_with_validator: Some(validator),
612 staked_with_timelock_expiration: None,
613 });
614 }
615 }
616
617 pub fn add_allocation(&mut self, allocation: TokenAllocation) {
618 self.allocations.push(allocation);
619 }
620
621 pub fn build(&self) -> TokenDistributionSchedule {
622 let schedule = TokenDistributionSchedule {
623 pre_minted_supply: self.pre_minted_supply,
624 allocations: self.allocations.clone(),
625 };
626
627 schedule.validate();
628 schedule
629 }
630}
631
632#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
634#[serde(rename_all = "kebab-case")]
635pub struct ValidatorAllocation {
636 pub validator: IotaAddress,
638 pub amount_nanos_to_stake: u64,
640 pub amount_nanos_to_pay_gas: u64,
642}
643
644#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
648#[serde(rename_all = "kebab-case")]
649pub struct Delegation {
650 pub delegator: IotaAddress,
652 #[serde(flatten)]
654 pub validator_allocation: ValidatorAllocation,
655}
656
657#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
664#[serde(rename_all = "kebab-case")]
665pub struct Delegations {
666 pub allocations: BTreeMap<IotaAddress, Vec<ValidatorAllocation>>,
667}
668
669impl Delegations {
670 pub fn new_for_validators_with_default_allocation(
671 validators: impl IntoIterator<Item = IotaAddress>,
672 delegator: IotaAddress,
673 ) -> Self {
674 let validator_allocations = validators
675 .into_iter()
676 .map(|address| ValidatorAllocation {
677 validator: address,
678 amount_nanos_to_stake: iota_types::governance::MIN_VALIDATOR_JOINING_STAKE_NANOS,
679 amount_nanos_to_pay_gas: 0,
680 })
681 .collect();
682
683 let mut allocations = BTreeMap::new();
684 allocations.insert(delegator, validator_allocations);
685
686 Self { allocations }
687 }
688
689 pub fn from_csv<R: std::io::Read>(reader: R) -> Result<Self> {
702 let mut reader = csv_reader_with_comments(reader);
703
704 let mut delegations = Self::default();
705 for delegation in reader.deserialize::<Delegation>() {
706 let delegation = delegation?;
707 delegations
708 .allocations
709 .entry(delegation.delegator)
710 .or_default()
711 .push(delegation.validator_allocation);
712 }
713
714 Ok(delegations)
715 }
716
717 pub fn to_csv<W: std::io::Write>(&self, writer: W) -> Result<()> {
725 let mut writer = csv::Writer::from_writer(writer);
726
727 writer.write_record([
728 "delegator",
729 "validator",
730 "amount-nanos-to-stake",
731 "amount-nanos-to-pay-gas",
732 ])?;
733
734 for (&delegator, validator_allocations) in &self.allocations {
735 for validator_allocation in validator_allocations {
736 writer.write_record(&[
737 delegator.to_string(),
738 validator_allocation.validator.to_string(),
739 validator_allocation.amount_nanos_to_stake.to_string(),
740 validator_allocation.amount_nanos_to_pay_gas.to_string(),
741 ])?;
742 }
743 }
744
745 Ok(())
746 }
747}
748
749pub fn csv_reader_with_comments<R: std::io::Read>(reader: R) -> csv::Reader<R> {
752 csv::ReaderBuilder::new()
753 .comment(Some(b'#'))
754 .from_reader(reader)
755}