diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0270bee28..05c259725 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,6 +63,11 @@ jobs: -p serai-dex-pallet \ -p serai-validator-sets-primitives \ -p serai-validator-sets-pallet \ + -p serai-genesis-liquidity-primitives \ + -p serai-genesis-liquidity-pallet \ + -p serai-emissions-primitives \ + -p serai-emissions-pallet \ + -p serai-economic-security-pallet \ -p serai-in-instructions-primitives \ -p serai-in-instructions-pallet \ -p serai-signals-primitives \ diff --git a/Cargo.lock b/Cargo.lock index e6ff320e2..96d012505 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8090,6 +8090,19 @@ dependencies = [ "chrono", ] +[[package]] +name = "serai-economic-security-pallet" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serai-coins-pallet", + "serai-dex-pallet", + "serai-primitives", +] + [[package]] name = "serai-emissions-pallet" version = "0.1.0" @@ -8100,6 +8113,7 @@ dependencies = [ "scale-info", "serai-coins-pallet", "serai-dex-pallet", + "serai-economic-security-pallet", "serai-emissions-primitives", "serai-genesis-liquidity-pallet", "serai-primitives", @@ -8166,6 +8180,7 @@ dependencies = [ "scale-info", "serai-coins-pallet", "serai-dex-pallet", + "serai-economic-security-pallet", "serai-genesis-liquidity-primitives", "serai-primitives", "serai-validator-sets-pallet", @@ -8469,6 +8484,7 @@ dependencies = [ "serai-abi", "serai-coins-pallet", "serai-dex-pallet", + "serai-economic-security-pallet", "serai-emissions-pallet", "serai-genesis-liquidity-pallet", "serai-in-instructions-pallet", diff --git a/Cargo.toml b/Cargo.toml index 4593d40da..ebd877f58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,14 @@ members = [ "substrate/validator-sets/primitives", "substrate/validator-sets/pallet", + "substrate/genesis-liquidity/primitives", + "substrate/genesis-liquidity/pallet", + + "substrate/emissions/primitives", + "substrate/emissions/pallet", + + "substrate/economic-security/pallet", + "substrate/in-instructions/primitives", "substrate/in-instructions/pallet", diff --git a/deny.toml b/deny.toml index b10772ef6..ec9fb458f 100644 --- a/deny.toml +++ b/deny.toml @@ -56,6 +56,8 @@ exceptions = [ { allow = ["AGPL-3.0"], name = "serai-genesis-liquidity-pallet" }, { allow = ["AGPL-3.0"], name = "serai-emissions-pallet" }, + { allow = ["AGPL-3.0"], name = "serai-economic-security-pallet" }, + { allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" }, { allow = ["AGPL-3.0"], name = "serai-validator-sets-pallet" }, diff --git a/substrate/abi/src/economic_security.rs b/substrate/abi/src/economic_security.rs new file mode 100644 index 000000000..fc548def9 --- /dev/null +++ b/substrate/abi/src/economic_security.rs @@ -0,0 +1,8 @@ +use serai_primitives::NetworkId; + +#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] +#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Event { + EconomicSecurityReached { network: NetworkId }, +} diff --git a/substrate/abi/src/lib.rs b/substrate/abi/src/lib.rs index c45a89356..255f3254d 100644 --- a/substrate/abi/src/lib.rs +++ b/substrate/abi/src/lib.rs @@ -20,6 +20,8 @@ pub mod validator_sets; pub mod genesis_liquidity; pub mod emissions; +pub mod economic_security; + pub mod in_instructions; pub mod signals; @@ -60,6 +62,7 @@ pub enum Event { ValidatorSets(validator_sets::Event), GenesisLiquidity(genesis_liquidity::Event), Emissions, + EconomicSecurity(economic_security::Event), InInstructions(in_instructions::Event), Signals(signals::Event), Babe, diff --git a/substrate/economic-security/pallet/Cargo.toml b/substrate/economic-security/pallet/Cargo.toml new file mode 100644 index 000000000..cefeee8ec --- /dev/null +++ b/substrate/economic-security/pallet/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "serai-economic-security-pallet" +version = "0.1.0" +description = "Economic Security pallet for Serai" +license = "AGPL-3.0-only" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/economic-security/pallet" +authors = ["Akil Demir "] +edition = "2021" +rust-version = "1.77" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.cargo-machete] +ignored = ["scale", "scale-info"] + +[lints] +workspace = true + +[dependencies] +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"] } + +frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } +frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } + +dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false } +coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false } + +serai-primitives = { path = "../../primitives", default-features = false } + +[features] +std = [ + "scale/std", + "scale-info/std", + + "frame-system/std", + "frame-support/std", + + "dex-pallet/std", + "coins-pallet/std", + + "serai-primitives/std", +] +try-runtime = [] # TODO + +default = ["std"] diff --git a/substrate/economic-security/pallet/LICENSE b/substrate/economic-security/pallet/LICENSE new file mode 100644 index 000000000..e091b1498 --- /dev/null +++ b/substrate/economic-security/pallet/LICENSE @@ -0,0 +1,15 @@ +AGPL-3.0-only license + +Copyright (c) 2024 Luke Parker + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License Version 3 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/substrate/economic-security/pallet/src/lib.rs b/substrate/economic-security/pallet/src/lib.rs new file mode 100644 index 000000000..cfcd573e5 --- /dev/null +++ b/substrate/economic-security/pallet/src/lib.rs @@ -0,0 +1,53 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[allow(clippy::cast_possible_truncation, clippy::no_effect_underscore_binding, clippy::empty_docs)] +#[frame_support::pallet] +pub mod pallet { + use frame_system::pallet_prelude::*; + use frame_support::pallet_prelude::*; + + use dex_pallet::{Config as DexConfig, Pallet as Dex}; + use coins_pallet::{Config as CoinsConfig, AllowMint}; + + use serai_primitives::*; + + #[pallet::config] + pub trait Config: frame_system::Config + CoinsConfig + DexConfig { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event { + EconomicSecurityReached { network: NetworkId }, + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::storage] + #[pallet::getter(fn economic_security_block)] + pub(crate) type EconomicSecurityBlock = + StorageMap<_, Identity, NetworkId, BlockNumberFor, OptionQuery>; + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: BlockNumberFor) -> Weight { + // we accept we reached economic security once we can mint smallest amount of a network's coin + for coin in COINS { + let existing = EconomicSecurityBlock::::get(coin.network()); + if existing.is_none() && + Dex::::security_oracle_value(coin).is_some() && + ::AllowMint::is_allowed(&Balance { coin, amount: Amount(1) }) + { + EconomicSecurityBlock::::set(coin.network(), Some(n)); + Self::deposit_event(Event::EconomicSecurityReached { network: coin.network() }); + } + } + + Weight::zero() // TODO + } + } +} + +pub use pallet::*; diff --git a/substrate/emissions/pallet/Cargo.toml b/substrate/emissions/pallet/Cargo.toml index d7f9bc59b..a56bcee48 100644 --- a/substrate/emissions/pallet/Cargo.toml +++ b/substrate/emissions/pallet/Cargo.toml @@ -33,6 +33,8 @@ validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../.. dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false } genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../../genesis-liquidity/pallet", default-features = false } +economic-security-pallet = { package = "serai-economic-security-pallet", path = "../../economic-security/pallet", default-features = false } + serai-primitives = { path = "../../primitives", default-features = false } validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../validator-sets/primitives", default-features = false } emissions-primitives = { package = "serai-emissions-primitives", path = "../primitives", default-features = false } @@ -52,6 +54,8 @@ std = [ "validator-sets-pallet/std", "dex-pallet/std", "genesis-liquidity-pallet/std", + + "economic-security-pallet/std", "serai-primitives/std", "emissions-primitives/std", diff --git a/substrate/emissions/pallet/src/lib.rs b/substrate/emissions/pallet/src/lib.rs index e280ea894..fff77b5aa 100644 --- a/substrate/emissions/pallet/src/lib.rs +++ b/substrate/emissions/pallet/src/lib.rs @@ -10,12 +10,14 @@ pub mod pallet { use sp_std::{vec, vec::Vec, ops::Mul, collections::btree_map::BTreeMap}; use sp_runtime; - use coins_pallet::{Config as CoinsConfig, Pallet as Coins, AllowMint}; + use coins_pallet::{Config as CoinsConfig, Pallet as Coins}; use dex_pallet::{Config as DexConfig, Pallet as Dex}; use validator_sets_pallet::{Pallet as ValidatorSets, Config as ValidatorSetsConfig}; use genesis_liquidity_pallet::{Pallet as GenesisLiquidity, Config as GenesisLiquidityConfig}; + use economic_security_pallet::{Config as EconomicSecurityConfig, Pallet as EconomicSecurity}; + use serai_primitives::*; use validator_sets_primitives::{MAX_KEY_SHARES_PER_SET, Session}; pub use emissions_primitives as primitives; @@ -28,6 +30,7 @@ pub mod pallet { + CoinsConfig + DexConfig + GenesisLiquidityConfig + + EconomicSecurityConfig { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } @@ -76,12 +79,6 @@ pub mod pallet { #[pallet::getter(fn session)] pub type CurrentSession = StorageMap<_, Identity, NetworkId, u32, ValueQuery>; - // TODO: Find a better place for this - #[pallet::storage] - #[pallet::getter(fn economic_security_reached)] - pub(crate) type EconomicSecurityReached = - StorageMap<_, Identity, NetworkId, bool, ValueQuery>; - #[pallet::storage] pub(crate) type LastSwapVolume = StorageMap<_, Identity, Coin, u64, OptionQuery>; @@ -95,7 +92,6 @@ pub mod pallet { } Participants::::set(id, Some(participants.try_into().unwrap())); CurrentSession::::set(id, 0); - EconomicSecurityReached::::set(id, false); } } } @@ -103,25 +99,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(n: BlockNumberFor) -> Weight { - // we wait 1 extra block after genesis ended to see the changes. We only need this extra - // block in dev&test networks where we start the chain with accounts that already has some - // staked SRI. So when we check for ec-security pre-genesis we look like we are economically - // secure. The reason for this although we only check for it once the genesis is complete(so - // if the genesis complete we shouldn't be economically secure because the funds are not - // enough) is because ValidatorSets pallet runs before the genesis pallet in runtime. - // So ValidatorSets pallet sees the old state until next block. - // TODO: revisit this when mainnet genesis validator stake code is done. - let gcb = GenesisLiquidity::::genesis_complete_block(); - let genesis_ended = gcb.is_some() && (n.saturated_into::() > gcb.unwrap()); - - // we accept we reached economic security once we can mint smallest amount of a network's coin - for coin in COINS { - let check = genesis_ended && !Self::economic_security_reached(coin.network()); - if check && ::AllowMint::is_allowed(&Balance { coin, amount: Amount(1) }) - { - EconomicSecurityReached::::set(coin.network(), true); - } - } + let genesis_ended = GenesisLiquidity::::genesis_complete_block().is_some(); // check if we got a new session let mut session_changed = false; @@ -341,7 +319,7 @@ pub mod pallet { continue; } - if !Self::economic_security_reached(n) { + if EconomicSecurity::::economic_security_block(n).is_none() { return true; } } @@ -383,7 +361,7 @@ pub mod pallet { balance: Balance, ) -> DispatchResult { // check the network didn't reach the economic security yet - if Self::economic_security_reached(network) { + if EconomicSecurity::::economic_security_block(network).is_some() { Err(Error::::NetworkHasEconomicSecurity)?; } diff --git a/substrate/genesis-liquidity/pallet/Cargo.toml b/substrate/genesis-liquidity/pallet/Cargo.toml index 5af38ef49..3668b9953 100644 --- a/substrate/genesis-liquidity/pallet/Cargo.toml +++ b/substrate/genesis-liquidity/pallet/Cargo.toml @@ -33,6 +33,8 @@ dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default- coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false } validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false } +economic-security-pallet = { package = "serai-economic-security-pallet", path = "../../economic-security/pallet", default-features = false } + serai-primitives = { path = "../../primitives", default-features = false } genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", path = "../primitives", default-features = false } validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../validator-sets/primitives", default-features = false } @@ -53,6 +55,8 @@ std = [ "dex-pallet/std", "validator-sets-pallet/std", + "economic-security-pallet/std", + "serai-primitives/std", "genesis-liquidity-primitives/std", "validator-sets-primitives/std", diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index 54bec3de5..b6d39bc3d 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -12,9 +12,11 @@ pub mod pallet { use sp_application_crypto::RuntimePublic; use dex_pallet::{Pallet as Dex, Config as DexConfig}; - use coins_pallet::{Config as CoinsConfig, Pallet as Coins, AllowMint}; + use coins_pallet::{Config as CoinsConfig, Pallet as Coins}; use validator_sets_pallet::{Config as VsConfig, Pallet as ValidatorSets}; + use economic_security_pallet::{Config as EconomicSecurityConfig, Pallet as EconomicSecurity}; + use serai_primitives::*; use validator_sets_primitives::{ValidatorSet, musig_key}; pub use genesis_liquidity_primitives as primitives; @@ -29,6 +31,7 @@ pub mod pallet { frame_system::Config + VsConfig + DexConfig + + EconomicSecurityConfig + CoinsConfig + coins_pallet::Config { @@ -49,7 +52,6 @@ pub mod pallet { GenesisLiquidityAdded { by: SeraiAddress, balance: Balance }, GenesisLiquidityRemoved { by: SeraiAddress, balance: Balance }, GenesisLiquidityAddedToPool { coin1: Balance, sri: Amount }, - EconomicSecurityReached { network: NetworkId }, } #[pallet::pallet] @@ -64,10 +66,6 @@ pub mod pallet { #[pallet::storage] pub(crate) type Supply = StorageMap<_, Identity, Coin, LiquidityAmount, OptionQuery>; - #[pallet::storage] - pub(crate) type EconomicSecurityReached = - StorageMap<_, Identity, NetworkId, BlockNumberFor, OptionQuery>; - #[pallet::storage] pub(crate) type Oracle = StorageMap<_, Identity, Coin, u64, OptionQuery>; @@ -170,18 +168,6 @@ pub mod pallet { GenesisCompleteBlock::::set(Some(n.saturated_into::())); } - // we accept we reached economic security once we can mint smallest amount of a network's coin - // TODO: move EconomicSecurity to a separate pallet - for coin in COINS { - let existing = EconomicSecurityReached::::get(coin.network()); - if existing.is_none() && - ::AllowMint::is_allowed(&Balance { coin, amount: Amount(1) }) - { - EconomicSecurityReached::::set(coin.network(), Some(n)); - Self::deposit_event(Event::EconomicSecurityReached { network: coin.network() }); - } - } - Weight::zero() // TODO } } @@ -237,7 +223,8 @@ pub mod pallet { fn blocks_since_ec_security() -> Option { let mut min = u64::MAX; for n in NETWORKS { - let ec_security_block = EconomicSecurityReached::::get(n)?.saturated_into::(); + let ec_security_block = + EconomicSecurity::::economic_security_block(n)?.saturated_into::(); let current = >::block_number().saturated_into::(); let diff = current.saturating_sub(ec_security_block); min = diff.min(min); diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index 8419d9bfb..9cd0f5ab0 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -63,6 +63,8 @@ validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../va genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../genesis-liquidity/pallet", default-features = false } emissions-pallet = { package = "serai-emissions-pallet", path = "../emissions/pallet", default-features = false } +economic-security-pallet = { package = "serai-economic-security-pallet", path = "../economic-security/pallet", default-features = false } + in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false } signals-pallet = { package = "serai-signals-pallet", path = "../signals/pallet", default-features = false } @@ -120,6 +122,8 @@ std = [ "genesis-liquidity-pallet/std", "emissions-pallet/std", + "economic-security-pallet/std", + "in-instructions-pallet/std", "signals-pallet/std", diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 5046c1f25..e55270cb9 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -34,6 +34,8 @@ pub use pallet_grandpa as grandpa; pub use genesis_liquidity_pallet as genesis_liquidity; pub use emissions_pallet as emissions; +pub use economic_security_pallet as economic_security; + // Actually used by the runtime use sp_core::OpaqueMetadata; use sp_std::prelude::*; @@ -257,6 +259,10 @@ impl emissions::Config for Runtime { type RuntimeEvent = RuntimeEvent; } +impl economic_security::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + // for publishing equivocation evidences. impl frame_system::offchain::SendTransactionTypes for Runtime where @@ -336,6 +342,8 @@ construct_runtime!( GenesisLiquidity: genesis_liquidity, Emissions: emissions, + EconomicSecurity: economic_security, + InInstructions: in_instructions, Signals: signals,