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