sui_config/
genesis.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use anyhow::{Context, Result};
5use fastcrypto::encoding::{Base64, Encoding};
6use fastcrypto::hash::HashFunction;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use std::{fs, path::Path};
9use sui_types::authenticator_state::{AuthenticatorStateInner, get_authenticator_state};
10use sui_types::base_types::{ObjectID, SuiAddress};
11use sui_types::clock::Clock;
12use sui_types::committee::CommitteeWithNetworkMetadata;
13use sui_types::crypto::DefaultHash;
14use sui_types::deny_list_v1::{PerTypeDenyList, get_coin_deny_list};
15use sui_types::effects::{TransactionEffects, TransactionEvents};
16use sui_types::gas_coin::TOTAL_SUPPLY_MIST;
17use sui_types::messages_checkpoint::{
18    CertifiedCheckpointSummary, CheckpointContents, CheckpointSummary, VerifiedCheckpoint,
19};
20use sui_types::storage::ObjectStore;
21use sui_types::sui_system_state::{
22    SuiSystemState, SuiSystemStateTrait, SuiSystemStateWrapper, SuiValidatorGenesis,
23    get_sui_system_state, get_sui_system_state_wrapper,
24};
25use sui_types::transaction::Transaction;
26use sui_types::{SUI_BRIDGE_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID};
27use sui_types::{
28    committee::{Committee, EpochId, ProtocolVersion},
29    object::Object,
30};
31use tracing::trace;
32
33#[derive(Clone, Debug)]
34pub struct Genesis {
35    checkpoint: CertifiedCheckpointSummary,
36    checkpoint_contents: CheckpointContents,
37    transaction: Transaction,
38    effects: TransactionEffects,
39    events: TransactionEvents,
40    objects: Vec<Object>,
41}
42
43#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
44pub struct UnsignedGenesis {
45    pub checkpoint: CheckpointSummary,
46    pub checkpoint_contents: CheckpointContents,
47    pub transaction: Transaction,
48    pub effects: TransactionEffects,
49    pub events: TransactionEvents,
50    pub objects: Vec<Object>,
51}
52
53// Hand implement PartialEq in order to get around the fact that AuthSigs don't impl Eq
54impl PartialEq for Genesis {
55    fn eq(&self, other: &Self) -> bool {
56        self.checkpoint.data() == other.checkpoint.data()
57            && {
58                let this = self.checkpoint.auth_sig();
59                let other = other.checkpoint.auth_sig();
60
61                this.epoch == other.epoch
62                    && this.signature.as_ref() == other.signature.as_ref()
63                    && this.signers_map == other.signers_map
64            }
65            && self.checkpoint_contents == other.checkpoint_contents
66            && self.transaction == other.transaction
67            && self.effects == other.effects
68            && self.objects == other.objects
69    }
70}
71
72impl Eq for Genesis {}
73
74impl Genesis {
75    pub fn new(
76        checkpoint: CertifiedCheckpointSummary,
77        checkpoint_contents: CheckpointContents,
78        transaction: Transaction,
79        effects: TransactionEffects,
80        events: TransactionEvents,
81        objects: Vec<Object>,
82    ) -> Self {
83        Self {
84            checkpoint,
85            checkpoint_contents,
86            transaction,
87            effects,
88            events,
89            objects,
90        }
91    }
92
93    pub fn objects(&self) -> &[Object] {
94        &self.objects
95    }
96
97    pub fn object(&self, id: ObjectID) -> Option<Object> {
98        self.objects.iter().find(|o| o.id() == id).cloned()
99    }
100
101    pub fn transaction(&self) -> &Transaction {
102        &self.transaction
103    }
104
105    pub fn effects(&self) -> &TransactionEffects {
106        &self.effects
107    }
108    pub fn events(&self) -> &TransactionEvents {
109        &self.events
110    }
111
112    pub fn checkpoint(&self) -> VerifiedCheckpoint {
113        self.checkpoint
114            .clone()
115            .try_into_verified(&self.committee())
116            .unwrap()
117    }
118
119    pub fn checkpoint_contents(&self) -> &CheckpointContents {
120        &self.checkpoint_contents
121    }
122
123    pub fn epoch(&self) -> EpochId {
124        0
125    }
126
127    pub fn validator_set_for_tooling(&self) -> Vec<SuiValidatorGenesis> {
128        self.sui_system_object()
129            .into_genesis_version_for_tooling()
130            .validators
131            .active_validators
132    }
133
134    pub fn committee_with_network(&self) -> CommitteeWithNetworkMetadata {
135        self.sui_system_object().get_current_epoch_committee()
136    }
137
138    pub fn reference_gas_price(&self) -> u64 {
139        self.sui_system_object().reference_gas_price()
140    }
141
142    pub fn committee(&self) -> Committee {
143        self.committee_with_network().committee().clone()
144    }
145
146    pub fn sui_system_wrapper_object(&self) -> SuiSystemStateWrapper {
147        get_sui_system_state_wrapper(&self.objects())
148            .expect("Sui System State Wrapper object must always exist")
149    }
150
151    pub fn sui_system_object(&self) -> SuiSystemState {
152        get_sui_system_state(&self.objects()).expect("Sui System State object must always exist")
153    }
154
155    pub fn clock(&self) -> Clock {
156        let clock = self
157            .objects()
158            .iter()
159            .find(|o| o.id() == sui_types::SUI_CLOCK_OBJECT_ID)
160            .expect("Clock must always exist")
161            .data
162            .try_as_move()
163            .expect("Clock must be a Move object");
164        bcs::from_bytes::<Clock>(clock.contents())
165            .expect("Clock object deserialization cannot fail")
166    }
167
168    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, anyhow::Error> {
169        let path = path.as_ref();
170        trace!("Reading Genesis from {}", path.display());
171        let bytes = fs::read(path)
172            .with_context(|| format!("Unable to load Genesis from {}", path.display()))?;
173        bcs::from_bytes(&bytes)
174            .with_context(|| format!("Unable to parse Genesis from {}", path.display()))
175    }
176
177    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), anyhow::Error> {
178        let path = path.as_ref();
179        trace!("Writing Genesis to {}", path.display());
180        let bytes = bcs::to_bytes(&self)?;
181        fs::write(path, bytes)
182            .with_context(|| format!("Unable to save Genesis to {}", path.display()))?;
183        Ok(())
184    }
185
186    pub fn to_bytes(&self) -> Vec<u8> {
187        bcs::to_bytes(self).expect("failed to serialize genesis")
188    }
189
190    pub fn hash(&self) -> [u8; 32] {
191        use std::io::Write;
192
193        let mut digest = DefaultHash::default();
194        digest.write_all(&self.to_bytes()).unwrap();
195        let hash = digest.finalize();
196        hash.into()
197    }
198}
199
200impl Serialize for Genesis {
201    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
202    where
203        S: Serializer,
204    {
205        use serde::ser::Error;
206
207        #[derive(Serialize)]
208        struct RawGenesis<'a> {
209            checkpoint: &'a CertifiedCheckpointSummary,
210            checkpoint_contents: &'a CheckpointContents,
211            transaction: &'a Transaction,
212            effects: &'a TransactionEffects,
213            events: &'a TransactionEvents,
214            objects: &'a [Object],
215        }
216
217        let raw_genesis = RawGenesis {
218            checkpoint: &self.checkpoint,
219            checkpoint_contents: &self.checkpoint_contents,
220            transaction: &self.transaction,
221            effects: &self.effects,
222            events: &self.events,
223            objects: &self.objects,
224        };
225
226        let bytes = bcs::to_bytes(&raw_genesis).map_err(|e| Error::custom(e.to_string()))?;
227
228        if serializer.is_human_readable() {
229            let s = Base64::encode(&bytes);
230            serializer.serialize_str(&s)
231        } else {
232            serializer.serialize_bytes(&bytes)
233        }
234    }
235}
236
237impl<'de> Deserialize<'de> for Genesis {
238    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
239    where
240        D: Deserializer<'de>,
241    {
242        use serde::de::Error;
243
244        #[derive(Deserialize)]
245        struct RawGenesis {
246            checkpoint: CertifiedCheckpointSummary,
247            checkpoint_contents: CheckpointContents,
248            transaction: Transaction,
249            effects: TransactionEffects,
250            events: TransactionEvents,
251            objects: Vec<Object>,
252        }
253
254        let bytes = if deserializer.is_human_readable() {
255            let s = String::deserialize(deserializer)?;
256            Base64::decode(&s).map_err(|e| Error::custom(e.to_string()))?
257        } else {
258            let data: Vec<u8> = Vec::deserialize(deserializer)?;
259            data
260        };
261
262        let RawGenesis {
263            checkpoint,
264            checkpoint_contents,
265            transaction,
266            effects,
267            events,
268            objects,
269        } = bcs::from_bytes(&bytes).map_err(|e| Error::custom(e.to_string()))?;
270
271        Ok(Genesis {
272            checkpoint,
273            checkpoint_contents,
274            transaction,
275            effects,
276            events,
277            objects,
278        })
279    }
280}
281
282impl UnsignedGenesis {
283    pub fn objects(&self) -> &[Object] {
284        &self.objects
285    }
286
287    pub fn object(&self, id: ObjectID) -> Option<Object> {
288        self.objects.iter().find(|o| o.id() == id).cloned()
289    }
290
291    pub fn transaction(&self) -> &Transaction {
292        &self.transaction
293    }
294
295    pub fn effects(&self) -> &TransactionEffects {
296        &self.effects
297    }
298    pub fn events(&self) -> &TransactionEvents {
299        &self.events
300    }
301
302    pub fn checkpoint(&self) -> &CheckpointSummary {
303        &self.checkpoint
304    }
305
306    pub fn checkpoint_contents(&self) -> &CheckpointContents {
307        &self.checkpoint_contents
308    }
309
310    pub fn epoch(&self) -> EpochId {
311        0
312    }
313
314    pub fn sui_system_wrapper_object(&self) -> SuiSystemStateWrapper {
315        get_sui_system_state_wrapper(&self.objects())
316            .expect("Sui System State Wrapper object must always exist")
317    }
318
319    pub fn sui_system_object(&self) -> SuiSystemState {
320        get_sui_system_state(&self.objects()).expect("Sui System State object must always exist")
321    }
322
323    pub fn authenticator_state_object(&self) -> Option<AuthenticatorStateInner> {
324        get_authenticator_state(self.objects()).expect("read from genesis cannot fail")
325    }
326
327    pub fn has_randomness_state_object(&self) -> bool {
328        self.objects()
329            .get_object(&SUI_RANDOMNESS_STATE_OBJECT_ID)
330            .is_some()
331    }
332
333    pub fn has_bridge_object(&self) -> bool {
334        self.objects().get_object(&SUI_BRIDGE_OBJECT_ID).is_some()
335    }
336
337    pub fn coin_deny_list_state(&self) -> Option<PerTypeDenyList> {
338        get_coin_deny_list(&self.objects())
339    }
340}
341
342#[derive(Clone, Debug, Serialize, Deserialize)]
343#[serde(rename_all = "kebab-case")]
344pub struct GenesisChainParameters {
345    pub protocol_version: u64,
346    pub chain_start_timestamp_ms: u64,
347    pub epoch_duration_ms: u64,
348
349    // Stake Subsidy parameters
350    pub stake_subsidy_start_epoch: u64,
351    pub stake_subsidy_initial_distribution_amount: u64,
352    pub stake_subsidy_period_length: u64,
353    pub stake_subsidy_decrease_rate: u16,
354
355    // Validator committee parameters
356    pub max_validator_count: u64,
357    pub min_validator_joining_stake: u64,
358    pub validator_low_stake_threshold: u64,
359    pub validator_very_low_stake_threshold: u64,
360    pub validator_low_stake_grace_period: u64,
361}
362
363/// Initial set of parameters for a chain.
364#[derive(Serialize, Deserialize)]
365pub struct GenesisCeremonyParameters {
366    #[serde(default = "GenesisCeremonyParameters::default_timestamp_ms")]
367    pub chain_start_timestamp_ms: u64,
368
369    /// protocol version that the chain starts at.
370    #[serde(default = "ProtocolVersion::max")]
371    pub protocol_version: ProtocolVersion,
372
373    #[serde(default = "GenesisCeremonyParameters::default_allow_insertion_of_extra_objects")]
374    pub allow_insertion_of_extra_objects: bool,
375
376    /// The duration of an epoch, in milliseconds.
377    #[serde(default = "GenesisCeremonyParameters::default_epoch_duration_ms")]
378    pub epoch_duration_ms: u64,
379
380    /// The starting epoch in which stake subsidies start being paid out.
381    #[serde(default)]
382    pub stake_subsidy_start_epoch: u64,
383
384    /// The amount of stake subsidy to be drawn down per distribution.
385    /// This amount decays and decreases over time.
386    #[serde(
387        default = "GenesisCeremonyParameters::default_initial_stake_subsidy_distribution_amount"
388    )]
389    pub stake_subsidy_initial_distribution_amount: u64,
390
391    /// Number of distributions to occur before the distribution amount decays.
392    #[serde(default = "GenesisCeremonyParameters::default_stake_subsidy_period_length")]
393    pub stake_subsidy_period_length: u64,
394
395    /// The rate at which the distribution amount decays at the end of each
396    /// period. Expressed in basis points.
397    #[serde(default = "GenesisCeremonyParameters::default_stake_subsidy_decrease_rate")]
398    pub stake_subsidy_decrease_rate: u16,
399    // Most other parameters (e.g. initial gas schedule) should be derived from protocol_version.
400}
401
402impl GenesisCeremonyParameters {
403    pub fn new() -> Self {
404        Self {
405            chain_start_timestamp_ms: Self::default_timestamp_ms(),
406            protocol_version: ProtocolVersion::MAX,
407            allow_insertion_of_extra_objects: true,
408            stake_subsidy_start_epoch: 0,
409            epoch_duration_ms: Self::default_epoch_duration_ms(),
410            stake_subsidy_initial_distribution_amount:
411                Self::default_initial_stake_subsidy_distribution_amount(),
412            stake_subsidy_period_length: Self::default_stake_subsidy_period_length(),
413            stake_subsidy_decrease_rate: Self::default_stake_subsidy_decrease_rate(),
414        }
415    }
416
417    fn default_timestamp_ms() -> u64 {
418        std::time::SystemTime::now()
419            .duration_since(std::time::UNIX_EPOCH)
420            .unwrap()
421            .as_millis() as u64
422    }
423
424    fn default_allow_insertion_of_extra_objects() -> bool {
425        true
426    }
427
428    fn default_epoch_duration_ms() -> u64 {
429        // 24 hrs
430        24 * 60 * 60 * 1000
431    }
432
433    fn default_initial_stake_subsidy_distribution_amount() -> u64 {
434        // 1M Sui
435        1_000_000 * sui_types::gas_coin::MIST_PER_SUI
436    }
437
438    fn default_stake_subsidy_period_length() -> u64 {
439        // 10 distributions or epochs
440        10
441    }
442
443    fn default_stake_subsidy_decrease_rate() -> u16 {
444        // 10% in basis points
445        1000
446    }
447
448    #[allow(deprecated)]
449    // TODO: replace deprecated constants with proper values
450    pub fn to_genesis_chain_parameters(&self) -> GenesisChainParameters {
451        GenesisChainParameters {
452            protocol_version: self.protocol_version.as_u64(),
453            stake_subsidy_start_epoch: self.stake_subsidy_start_epoch,
454            chain_start_timestamp_ms: self.chain_start_timestamp_ms,
455            epoch_duration_ms: self.epoch_duration_ms,
456            stake_subsidy_initial_distribution_amount: self
457                .stake_subsidy_initial_distribution_amount,
458            stake_subsidy_period_length: self.stake_subsidy_period_length,
459            stake_subsidy_decrease_rate: self.stake_subsidy_decrease_rate,
460            max_validator_count: sui_types::governance::MAX_VALIDATOR_COUNT,
461            min_validator_joining_stake: sui_types::governance::MIN_VALIDATOR_JOINING_STAKE_MIST,
462            validator_low_stake_threshold:
463                sui_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_MIST,
464            validator_very_low_stake_threshold:
465                sui_types::governance::VALIDATOR_VERY_LOW_STAKE_THRESHOLD_MIST,
466            validator_low_stake_grace_period:
467                sui_types::governance::VALIDATOR_LOW_STAKE_GRACE_PERIOD,
468        }
469    }
470}
471
472impl Default for GenesisCeremonyParameters {
473    fn default() -> Self {
474        Self::new()
475    }
476}
477
478#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
479#[serde(rename_all = "kebab-case")]
480pub struct TokenDistributionSchedule {
481    pub stake_subsidy_fund_mist: u64,
482    pub allocations: Vec<TokenAllocation>,
483}
484
485impl TokenDistributionSchedule {
486    pub fn validate(&self) {
487        let mut total_mist = self.stake_subsidy_fund_mist;
488
489        for allocation in &self.allocations {
490            total_mist += allocation.amount_mist;
491        }
492
493        if total_mist != TOTAL_SUPPLY_MIST {
494            panic!(
495                "TokenDistributionSchedule adds up to {total_mist} and not expected {TOTAL_SUPPLY_MIST}"
496            );
497        }
498    }
499
500    pub fn check_all_stake_operations_are_for_valid_validators<
501        I: IntoIterator<Item = SuiAddress>,
502    >(
503        &self,
504        validators: I,
505    ) {
506        use std::collections::HashMap;
507
508        let mut validators: HashMap<SuiAddress, u64> =
509            validators.into_iter().map(|a| (a, 0)).collect();
510
511        // Check that all allocations are for valid validators, while summing up all allocations
512        // for each validator
513        for allocation in &self.allocations {
514            if let Some(staked_with_validator) = &allocation.staked_with_validator {
515                *validators
516                    .get_mut(staked_with_validator)
517                    .expect("allocation must be staked with valid validator") +=
518                    allocation.amount_mist;
519            }
520        }
521    }
522
523    #[allow(deprecated)]
524    pub fn new_for_validators_with_default_allocation<I: IntoIterator<Item = SuiAddress>>(
525        validators: I,
526    ) -> Self {
527        let mut supply = TOTAL_SUPPLY_MIST;
528        let default_allocation = sui_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_MIST;
529
530        let allocations = validators
531            .into_iter()
532            .map(|a| {
533                supply -= default_allocation;
534                TokenAllocation {
535                    recipient_address: a,
536                    amount_mist: default_allocation,
537                    staked_with_validator: Some(a),
538                }
539            })
540            .collect();
541
542        let schedule = Self {
543            stake_subsidy_fund_mist: supply,
544            allocations,
545        };
546
547        schedule.validate();
548        schedule
549    }
550
551    /// Helper to read a TokenDistributionSchedule from a csv file.
552    ///
553    /// The file is encoded such that the final entry in the CSV file is used to denote the
554    /// allocation to the stake subsidy fund. It must be in the following format:
555    /// `0x0000000000000000000000000000000000000000000000000000000000000000,<amount to stake subsidy fund>,`
556    ///
557    /// All entries in a token distribution schedule must add up to 10B Sui.
558    pub fn from_csv<R: std::io::Read>(reader: R) -> Result<Self> {
559        let mut reader = csv::Reader::from_reader(reader);
560        let mut allocations: Vec<TokenAllocation> =
561            reader.deserialize().collect::<Result<_, _>>()?;
562        assert_eq!(
563            TOTAL_SUPPLY_MIST,
564            allocations.iter().map(|a| a.amount_mist).sum::<u64>(),
565            "Token Distribution Schedule must add up to 10B Sui",
566        );
567        let stake_subsidy_fund_allocation = allocations.pop().unwrap();
568        assert_eq!(
569            SuiAddress::default(),
570            stake_subsidy_fund_allocation.recipient_address,
571            "Final allocation must be for stake subsidy fund",
572        );
573        assert!(
574            stake_subsidy_fund_allocation
575                .staked_with_validator
576                .is_none(),
577            "Can't stake the stake subsidy fund",
578        );
579
580        let schedule = Self {
581            stake_subsidy_fund_mist: stake_subsidy_fund_allocation.amount_mist,
582            allocations,
583        };
584
585        schedule.validate();
586        Ok(schedule)
587    }
588
589    pub fn to_csv<W: std::io::Write>(&self, writer: W) -> Result<()> {
590        let mut writer = csv::Writer::from_writer(writer);
591
592        for allocation in &self.allocations {
593            writer.serialize(allocation)?;
594        }
595
596        writer.serialize(TokenAllocation {
597            recipient_address: SuiAddress::default(),
598            amount_mist: self.stake_subsidy_fund_mist,
599            staked_with_validator: None,
600        })?;
601
602        Ok(())
603    }
604}
605
606#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
607#[serde(rename_all = "kebab-case")]
608pub struct TokenAllocation {
609    pub recipient_address: SuiAddress,
610    pub amount_mist: u64,
611
612    /// Indicates if this allocation should be staked at genesis and with which validator
613    pub staked_with_validator: Option<SuiAddress>,
614}
615
616#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
617pub struct TokenDistributionScheduleBuilder {
618    pool: u64,
619    allocations: Vec<TokenAllocation>,
620}
621
622impl TokenDistributionScheduleBuilder {
623    #[allow(clippy::new_without_default)]
624    pub fn new() -> Self {
625        Self {
626            pool: TOTAL_SUPPLY_MIST,
627            allocations: vec![],
628        }
629    }
630
631    #[allow(deprecated)]
632    pub fn default_allocation_for_validators<I: IntoIterator<Item = SuiAddress>>(
633        &mut self,
634        validators: I,
635    ) {
636        let default_allocation = sui_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_MIST;
637
638        for validator in validators {
639            self.add_allocation(TokenAllocation {
640                recipient_address: validator,
641                amount_mist: default_allocation,
642                staked_with_validator: Some(validator),
643            });
644        }
645    }
646
647    pub fn add_allocation(&mut self, allocation: TokenAllocation) {
648        self.pool = self.pool.checked_sub(allocation.amount_mist).unwrap();
649        self.allocations.push(allocation);
650    }
651
652    pub fn build(&self) -> TokenDistributionSchedule {
653        let schedule = TokenDistributionSchedule {
654            stake_subsidy_fund_mist: self.pool,
655            allocations: self.allocations.clone(),
656        };
657
658        schedule.validate();
659        schedule
660    }
661}