From 83d64c041db5f2a12fb51e764d56f3d88e79a7f5 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 6 Aug 2026 20:44:22 -0400 Subject: [PATCH 1/3] refactor(runtime): split runtime configs into dedicated modules --- template/runtime/src/configs/consensus.rs | 129 +++++++ template/runtime/src/configs/evm.rs | 112 ++++++ template/runtime/src/configs/mod.rs | 13 + template/runtime/src/configs/privacy.rs | 77 +++++ template/runtime/src/configs/system.rs | 108 ++++++ template/runtime/src/lib.rs | 402 +--------------------- 6 files changed, 447 insertions(+), 394 deletions(-) create mode 100644 template/runtime/src/configs/consensus.rs create mode 100644 template/runtime/src/configs/evm.rs create mode 100644 template/runtime/src/configs/mod.rs create mode 100644 template/runtime/src/configs/privacy.rs create mode 100644 template/runtime/src/configs/system.rs diff --git a/template/runtime/src/configs/consensus.rs b/template/runtime/src/configs/consensus.rs new file mode 100644 index 00000000..0aed3ab3 --- /dev/null +++ b/template/runtime/src/configs/consensus.rs @@ -0,0 +1,129 @@ +//! Block production and finality: Aura, GRANDPA, sessions, and the validator set. +//! +//! The validator set is governance-gated and enforces prerequisites before +//! bonding — a candidate must have registered both its session keys and its +//! relayer EVM address, or it cannot author. + +use crate::*; +use frame_support::parameter_types; + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type MaxAuthorities = ConstU32<32>; + // Session manages disabled validators when pallet-session is active. + type DisabledValidators = Session; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; +} + +parameter_types! { + /// Session length: 600 blocks ≈ 1 hour at 6 s/block. + /// Validator set changes take effect at the next session boundary. + pub const Period: u32 = HOURS; + pub const Offset: u32 = 0; +} + +/// Identity converter: `AccountId` → `Option` (always `Some`). +/// +/// Used as `pallet_session::Config::ValidatorIdOf` when `ValidatorId = AccountId`. +pub struct IdentityValidatorId; +impl Convert> for IdentityValidatorId { + fn convert(a: AccountId) -> Option { + Some(a) + } +} + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + /// Validators are identified by their `AccountId`. + type ValidatorId = AccountId; + /// Identity mapping: stash AccountId → ValidatorId (same type). + type ValidatorIdOf = IdentityValidatorId; + /// Sessions rotate every `Period` blocks. + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + /// Validator set is managed by our custom `ValidatorSet` pallet (sudo-gated). + type SessionManager = ValidatorSet; + /// Session handlers: Aura + GRANDPA are notified on each session change. + type SessionHandler = ::KeyTypeIdProviders; + type Keys = opaque::SessionKeys; + /// No disabling strategy — validators are never automatically disabled. + type DisablingStrategy = (); + /// Balances pallet handles key-deposit holds. + type Currency = Balances; + /// No deposit required to set session keys (testnet). + type KeyDeposit = ConstU128<0>; + type WeightInfo = (); +} + +/// Checks that a validator candidate has completed all prerequisites before bonding. +/// +/// - [`has_session_keys`]: verifies `pallet_session::NextKeys` contains an entry for `who` +/// (i.e. the node called `session.setKeys` with its Aura + GRANDPA keys). +/// - [`has_relayer`]: verifies `pallet_relayer::RelayerByAccount` contains an entry for `who` +/// (i.e. the node called `relayer.register_relayer` with its EVM address). +pub struct ValidatorPrerequisiteChecker; + +impl pallet_validator_set::ValidatorPrerequisites for ValidatorPrerequisiteChecker { + fn has_session_keys(who: &AccountId) -> bool { + pallet_session::NextKeys::::contains_key(who) + } + fn has_relayer(who: &AccountId) -> bool { + pallet_relayer::RelayerByAccount::::contains_key(who) + } +} + +impl pallet_validator_set::Config for Runtime { + /// Only sudo (EnsureRoot) can add/remove/approve/reject validators. + type AddRemoveOrigin = frame_system::EnsureRoot; + /// Native currency (ORB) used to lock the validator bond. + type Currency = Balances; + /// Maximum 32 validators in the approved (active) set. + type MaxValidators = ConstU32<32>; + /// Maximum 32 registrations awaiting governance approval. + type MaxPendingValidators = ConstU32<32>; + /// Validator bond: 1 000 ORB (18 decimals) locked on self-registration. + /// Returned in full on deregistration, rejection, or force-removal. + type ValidatorBond = ConstU128<1_000_000_000_000_000_000_000>; + /// Prerequisite gate: verifies session keys and EVM relayer before accepting registration. + type Prerequisites = ValidatorPrerequisiteChecker; + type WeightInfo = pallet_validator_set::weights::SubstrateWeight; +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = FindAuthorAccountId; + type EventHandler = (); +} + +/// Maps an Aura authority index to its `AccountId32`. +/// +/// `pallet_aura::AuraAuthorId` implements `FindAuthor` (authority index). +/// This wrapper looks up the AuraId at that index and converts its 32-byte +/// sr25519 public key into an `AccountId32`. +pub struct FindAuthorAccountId; +impl FindAuthor for FindAuthorAccountId { + fn find_author<'a, I>(digests: I) -> Option + where + I: 'a + IntoIterator, + { + if let Some(aura_id) = pallet_aura::AuraAuthorId::::find_author(digests) { + let raw: [u8; 32] = aura_id.to_raw_vec().try_into().unwrap_or([0u8; 32]); + return Some(AccountId::from(raw)); + } + None + } +} + +impl pallet_grandpa::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type MaxAuthorities = ConstU32<32>; + type MaxNominators = ConstU32<0>; + type MaxSetIdSessionEntries = ConstU64<0>; + type KeyOwnerProof = sp_core::Void; + type EquivocationReportSystem = (); +} + +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = (); +} diff --git a/template/runtime/src/configs/evm.rs b/template/runtime/src/configs/evm.rs new file mode 100644 index 00000000..a2b2656c --- /dev/null +++ b/template/runtime/src/configs/evm.rs @@ -0,0 +1,112 @@ +//! Frontier EVM layer: EVM, Ethereum, fee models, and dev-only manual seal. +//! +//! `FindAuthorTruncated` bridges Substrate's 32-byte author to the 20-byte +//! address the EVM expects, which is what lets `block.coinbase` work. + +use crate::*; +use frame_support::parameter_types; + +impl pallet_evm_chain_id::Config for Runtime {} + +pub struct FindAuthorTruncated(PhantomData); +impl> FindAuthor for FindAuthorTruncated { + fn find_author<'a, I>(digests: I) -> Option + where + I: 'a + IntoIterator, + { + if let Some(author_index) = F::find_author(digests) { + let authority_id = + pallet_aura::Authorities::::get()[author_index as usize].clone(); + return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24])); + } + None + } +} + +const BLOCK_GAS_LIMIT: u64 = 75_000_000; +const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; +/// The maximum storage growth per block in bytes. +const MAX_STORAGE_GROWTH: u64 = 400 * 1024; + +parameter_types! { + pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); + pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); + pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); + pub PrecompilesValue: FrontierPrecompiles = FrontierPrecompiles::<_>::new(); + pub WeightPerGas: Weight = Weight::from_parts(weight_per_gas(BLOCK_GAS_LIMIT, NORMAL_DISPATCH_RATIO, WEIGHT_MILLISECS_PER_BLOCK), 0); +} + +impl pallet_evm::Config for Runtime { + type AccountProvider = pallet_evm::FrameSystemAccountProvider; + type FeeCalculator = BaseFee; + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; + type CallOrigin = EnsureAddressMatches; + type WithdrawOrigin = EnsureAddressMatches; + type AddressMapping = EeSuffixAddressMapping; + type Currency = Balances; + type PrecompilesType = FrontierPrecompiles; + type PrecompilesValue = PrecompilesValue; + type ChainId = EVMChainId; + type BlockGasLimit = BlockGasLimit; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = (); + type OnCreate = (); + type FindAuthor = FindAuthorTruncated; + type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio; + type Timestamp = Timestamp; + type CreateOriginFilter = (); + type CreateInnerOriginFilter = (); + type WeightInfo = pallet_evm::weights::SubstrateWeight; +} + +parameter_types! { + pub const PostBlockAndTxnHashes: PostLogContent = PostLogContent::BlockAndTxnHashes; +} + +impl pallet_ethereum::Config for Runtime { + type StateRoot = pallet_ethereum::IntermediateStateRoot; + type PostLogContent = PostBlockAndTxnHashes; + type ExtraDataLength = ConstU32<30>; +} + +parameter_types! { + pub BoundDivision: U256 = U256::from(1024); +} + +impl pallet_dynamic_fee::Config for Runtime { + type MinGasPriceBoundDivisor = BoundDivision; +} + +parameter_types! { + // ORB uses 18 decimals (1 ORB = 1e18 wei), aligned with Ethereum tooling. + // DefaultBaseFeePerGas is used as the runtime-level fallback (e.g. storage migration). + // Actual genesis values are set per-chain-spec in genesis_config_preset/: + // all networks → 1_000_000_000 wei/gas (1 gwei → ~0.00001 ORB / transfer) + // The floor (empty blocks) = DefaultBaseFeePerGas / 2 = 0.5 gwei. + // EIP-1559 adjusts the fee upward automatically as the network gains traffic. + pub DefaultBaseFeePerGas: U256 = U256::from(1_000_000_000u64); // 1 gwei + + pub DefaultElasticity: Permill = Permill::from_parts(125_000); +} +pub struct BaseFeeThreshold; +impl pallet_base_fee::BaseFeeThreshold for BaseFeeThreshold { + fn lower() -> Permill { + Permill::zero() + } + fn ideal() -> Permill { + Permill::from_parts(500_000) + } + fn upper() -> Permill { + Permill::from_parts(1_000_000) + } +} +impl pallet_base_fee::Config for Runtime { + type Threshold = BaseFeeThreshold; + type DefaultBaseFeePerGas = DefaultBaseFeePerGas; + type DefaultElasticity = DefaultElasticity; +} + +impl pallet_manual_seal::Config for Runtime {} diff --git a/template/runtime/src/configs/mod.rs b/template/runtime/src/configs/mod.rs new file mode 100644 index 00000000..530f13ba --- /dev/null +++ b/template/runtime/src/configs/mod.rs @@ -0,0 +1,13 @@ +//! Per-pallet `Config` implementations, grouped by domain. +//! +//! These are plain `impl` items, so splitting them out of `lib.rs` changes +//! nothing about how the runtime is assembled — unlike `#[frame_support::runtime]` +//! and `impl_runtime_apis!`, which the macros must see as single blocks. +//! +//! Each module pulls the runtime's types in through `use super::*`, so a config +//! reads the same here as it did inline. + +pub mod consensus; +pub mod evm; +pub mod privacy; +pub mod system; diff --git a/template/runtime/src/configs/privacy.rs b/template/runtime/src/configs/privacy.rs new file mode 100644 index 00000000..d21221ca --- /dev/null +++ b/template/runtime/src/configs/privacy.rs @@ -0,0 +1,77 @@ +//! Orbinum privacy stack: ZK verifier, relayer, and the shielded pool. +//! +//! The shielded-pool constants carry real operational weight — the retention +//! window must outlive mempool longevity, and the prune level trades storage +//! against how long a Merkle path takes to rebuild. Both are documented at the +//! point of use below. + +use crate::*; +use frame_support::parameter_types; + +impl pallet_zk_verifier::Config for Runtime { + type MaxProofSize = ConstU32<128>; + type MaxPublicInputs = ConstU32<32>; + type WeightInfo = pallet_zk_verifier::weights::SubstrateWeight; +} + +// ──────────────────────────────────────────────────────────────────────────── +// pallet-relayer +// ──────────────────────────────────────────────────────────────────────────── + +/// Provides the current block's author (Aura validator) for relay fee attribution. +pub struct RelayerBlockAuthor; +impl frame_support::traits::Get> for RelayerBlockAuthor { + fn get() -> Option { + pallet_authorship::Pallet::::author() + } +} + +impl pallet_relayer::Config for Runtime { + /// Block author for relay fee attribution. + type BlockAuthor = RelayerBlockAuthor; + /// Default minimum relay fee: 0.001 ORB = 1e15 planck (anti-spam). + /// Overridable at runtime via `set_min_relay_fee` (governance/sudo). + type DefaultMinRelayFee = ConstU128<1_000_000_000_000_000>; + /// Only sudo/governance can update relay configuration. + type ManageOrigin = frame_system::EnsureRoot; + /// Allow up to 16 ABI selectors in the whitelist. + type MaxAllowedSelectors = ConstU32<16>; + type WeightInfo = (); +} + +parameter_types! { + /// Pool account that holds all shielded tokens + pub const ShieldedPoolPalletId: PalletId = PalletId(*b"shld/pol"); +} + +impl pallet_shielded_pool::Config for Runtime { + /// Native currency (ORB) for shield/unshield operations + type Currency = Balances; + /// Groth16 proof verifier for unshield/transfer operations + type ZkVerifier = ZkVerifier; + /// Relay config, fee accumulation and block-author — delegated to pallet-relayer. + type Relayer = pallet_relayer::Pallet; + /// PalletId for the pool account + type PalletId = ShieldedPoolPalletId; + /// Merkle tree depth: 2^20 = 1M notes max (see MERKLE_TREE_SCALABILITY.md) + type MaxTreeDepth = ConstU32<20>; + /// Historic roots: allows proofs against past states (30s window) + /// Safety cap on the historic-root queue, not the retention window. A root + /// expires by elapsed blocks; this only bounds worst-case storage. + /// + /// Steady state is `RootRetentionBlocks × commitments-per-block`: 1200 at a + /// sustained 2 transfers/block, 6000 at 10. Sized for ~27 transfers/block + /// sustained across a full window, well past the ~127 proof verifications a + /// block can fit, so the window — never this bound — is what expires a root. + type MaxHistoricRoots = ConstU32<16384>; + /// Roots stay spendable for 300 blocks (~30 min at 6s), comfortably above + /// the 64-block mempool longevity of an unsigned transaction. + type RootRetentionBlocks = ConstU32<300>; + // Pinned to 2^20: clients derive tree_id = leaf_index >> 20 from this. + type MaxLeavesPerTree = ConstU32<1_048_576>; + /// Minimum shield amount: prevents spam, 1 ORB = 1e18 wei + type MinShieldAmount = ConstU128<1_000_000_000_000_000_000>; + type WeightInfo = pallet_shielded_pool::weights::SubstrateWeight; +} + +// Create the runtime by composing the FRAME pallets that were previously configured. diff --git a/template/runtime/src/configs/system.rs b/template/runtime/src/configs/system.rs new file mode 100644 index 00000000..116695e6 --- /dev/null +++ b/template/runtime/src/configs/system.rs @@ -0,0 +1,108 @@ +//! Core FRAME configuration: system, timestamp, balances, fees, sudo. +//! +//! `BlockWeights` is the one to read first — it caps what a block may execute, +//! which is what bounds every other pallet's per-block work. + +use crate::*; +use frame_support::{derive_impl, parameter_types}; + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; + pub const BlockHashCount: BlockNumber = 256; + pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights + ::with_sensible_defaults(MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO); + pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength + ::max_with_normal_ratio(MAXIMUM_BLOCK_LENGTH, NORMAL_DISPATCH_RATIO); + /// TODO: register a unique SS58 prefix for Orbinum at + /// https://github.com/paritytech/ss58-registry before mainnet. + /// 42 is the generic Substrate default and will conflict with other chains in + /// tools like polkadot.js. Changing this value invalidates all existing + /// encoded addresses — coordinate with explorer / wallet teams before bumping. + pub const SS58Prefix: u8 = 42; +} + +// Configure FRAME pallets to include in runtime. +#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + /// Block & extrinsics weights: base values and limits. + type BlockWeights = BlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = BlockLength; + /// The index type for storing how many extrinsics an account has signed. + type Nonce = Nonce; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = Hashing; + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = IdentityLookup; + /// The block type. + type Block = Block; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// The weight of database operations that the runtime can invoke. + type DbWeight = RuntimeDbWeight; + /// Version of the runtime. + type Version = Version; + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = SS58Prefix; + type MaxConsumers = ConstU32<16>; +} + +pub struct ConsensusOnTimestampSet(PhantomData); +impl OnTimestampSet for ConsensusOnTimestampSet { + fn on_timestamp_set(moment: T::Moment) { + if EnableManualSeal::get() { + return; + } + as OnTimestampSet>::on_timestamp_set(moment) + } +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = ConsensusOnTimestampSet; + type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; + type WeightInfo = (); +} + +pub const EXISTENTIAL_DEPOSIT: u128 = 0; + +impl pallet_balances::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type WeightInfo = pallet_balances::weights::SubstrateWeight; + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = ConstU128; + type AccountStore = System; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxLocks = ConstU32<50>; + type MaxReserves = ConstU32<50>; + type MaxFreezes = ConstU32<1>; + type DoneSlashHandler = (); +} + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = FungibleAdapter; + type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; + /// Parameterized slow adjusting fee updated based on + /// + type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; + type OperationalFeeMultiplier = ConstU8<5>; + type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight; +} + +impl pallet_sudo::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WeightInfo = pallet_sudo::weights::SubstrateWeight; +} diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 1722be13..e10f7e58 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -11,6 +11,7 @@ extern crate alloc; // Required for WASM side effects; suppress unused_crate_dependencies warning. use sp_io as _; +mod configs; mod evm_account; mod genesis_config_preset; mod orbinum_signature; @@ -51,7 +52,6 @@ use frame_support::weights::constants::ParityDbWeight as RuntimeDbWeight; #[cfg(feature = "with-rocksdb-weights")] use frame_support::weights::constants::RocksDbWeight as RuntimeDbWeight; use frame_support::{ - derive_impl, genesis_builder_helper::build_state, parameter_types, traits::{ConstBool, ConstU32, ConstU64, ConstU8, FindAuthor, OnFinalize, OnTimestampSet}, @@ -226,335 +226,18 @@ pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( ); pub const MAXIMUM_BLOCK_LENGTH: u32 = 5 * 1024 * 1024; -parameter_types! { - pub const Version: RuntimeVersion = VERSION; - pub const BlockHashCount: BlockNumber = 256; - pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights - ::with_sensible_defaults(MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO); - pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength - ::max_with_normal_ratio(MAXIMUM_BLOCK_LENGTH, NORMAL_DISPATCH_RATIO); - /// TODO: register a unique SS58 prefix for Orbinum at - /// https://github.com/paritytech/ss58-registry before mainnet. - /// 42 is the generic Substrate default and will conflict with other chains in - /// tools like polkadot.js. Changing this value invalidates all existing - /// encoded addresses — coordinate with explorer / wallet teams before bumping. - pub const SS58Prefix: u8 = 42; -} - -// Configure FRAME pallets to include in runtime. -#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig as frame_system::DefaultConfig)] -impl frame_system::Config for Runtime { - /// Block & extrinsics weights: base values and limits. - type BlockWeights = BlockWeights; - /// The maximum length of a block (in bytes). - type BlockLength = BlockLength; - /// The index type for storing how many extrinsics an account has signed. - type Nonce = Nonce; - /// The type for hashing blocks and tries. - type Hash = Hash; - /// The hashing algorithm used. - type Hashing = Hashing; - /// The identifier used to distinguish between accounts. - type AccountId = AccountId; - /// The lookup mechanism to get account ID from whatever is passed in dispatchers. - type Lookup = IdentityLookup; - /// The block type. - type Block = Block; - /// Maximum number of block number to block hash mappings to keep (oldest pruned first). - type BlockHashCount = BlockHashCount; - /// The weight of database operations that the runtime can invoke. - type DbWeight = RuntimeDbWeight; - /// Version of the runtime. - type Version = Version; - /// The data to be stored in an account. - type AccountData = pallet_balances::AccountData; - /// This is used as an identifier of the chain. 42 is the generic substrate prefix. - type SS58Prefix = SS58Prefix; - type MaxConsumers = ConstU32<16>; -} - -impl pallet_aura::Config for Runtime { - type AuthorityId = AuraId; - type MaxAuthorities = ConstU32<32>; - // Session manages disabled validators when pallet-session is active. - type DisabledValidators = Session; - type AllowMultipleBlocksPerSlot = ConstBool; - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; -} - -parameter_types! { - /// Session length: 600 blocks ≈ 1 hour at 6 s/block. - /// Validator set changes take effect at the next session boundary. - pub const Period: u32 = HOURS; - pub const Offset: u32 = 0; -} - -/// Identity converter: `AccountId` → `Option` (always `Some`). -/// -/// Used as `pallet_session::Config::ValidatorIdOf` when `ValidatorId = AccountId`. -pub struct IdentityValidatorId; -impl Convert> for IdentityValidatorId { - fn convert(a: AccountId) -> Option { - Some(a) - } -} - -impl pallet_session::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - /// Validators are identified by their `AccountId`. - type ValidatorId = AccountId; - /// Identity mapping: stash AccountId → ValidatorId (same type). - type ValidatorIdOf = IdentityValidatorId; - /// Sessions rotate every `Period` blocks. - type ShouldEndSession = pallet_session::PeriodicSessions; - type NextSessionRotation = pallet_session::PeriodicSessions; - /// Validator set is managed by our custom `ValidatorSet` pallet (sudo-gated). - type SessionManager = ValidatorSet; - /// Session handlers: Aura + GRANDPA are notified on each session change. - type SessionHandler = ::KeyTypeIdProviders; - type Keys = opaque::SessionKeys; - /// No disabling strategy — validators are never automatically disabled. - type DisablingStrategy = (); - /// Balances pallet handles key-deposit holds. - type Currency = Balances; - /// No deposit required to set session keys (testnet). - type KeyDeposit = ConstU128<0>; - type WeightInfo = (); -} - -/// Checks that a validator candidate has completed all prerequisites before bonding. -/// -/// - [`has_session_keys`]: verifies `pallet_session::NextKeys` contains an entry for `who` -/// (i.e. the node called `session.setKeys` with its Aura + GRANDPA keys). -/// - [`has_relayer`]: verifies `pallet_relayer::RelayerByAccount` contains an entry for `who` -/// (i.e. the node called `relayer.register_relayer` with its EVM address). -pub struct ValidatorPrerequisiteChecker; - -impl pallet_validator_set::ValidatorPrerequisites for ValidatorPrerequisiteChecker { - fn has_session_keys(who: &AccountId) -> bool { - pallet_session::NextKeys::::contains_key(who) - } - fn has_relayer(who: &AccountId) -> bool { - pallet_relayer::RelayerByAccount::::contains_key(who) - } -} - -impl pallet_validator_set::Config for Runtime { - /// Only sudo (EnsureRoot) can add/remove/approve/reject validators. - type AddRemoveOrigin = frame_system::EnsureRoot; - /// Native currency (ORB) used to lock the validator bond. - type Currency = Balances; - /// Maximum 32 validators in the approved (active) set. - type MaxValidators = ConstU32<32>; - /// Maximum 32 registrations awaiting governance approval. - type MaxPendingValidators = ConstU32<32>; - /// Validator bond: 1 000 ORB (18 decimals) locked on self-registration. - /// Returned in full on deregistration, rejection, or force-removal. - type ValidatorBond = ConstU128<1_000_000_000_000_000_000_000>; - /// Prerequisite gate: verifies session keys and EVM relayer before accepting registration. - type Prerequisites = ValidatorPrerequisiteChecker; - type WeightInfo = pallet_validator_set::weights::SubstrateWeight; -} - -impl pallet_authorship::Config for Runtime { - type FindAuthor = FindAuthorAccountId; - type EventHandler = (); -} - -/// Maps an Aura authority index to its `AccountId32`. -/// -/// `pallet_aura::AuraAuthorId` implements `FindAuthor` (authority index). -/// This wrapper looks up the AuraId at that index and converts its 32-byte -/// sr25519 public key into an `AccountId32`. -pub struct FindAuthorAccountId; -impl FindAuthor for FindAuthorAccountId { - fn find_author<'a, I>(digests: I) -> Option - where - I: 'a + IntoIterator, - { - if let Some(aura_id) = pallet_aura::AuraAuthorId::::find_author(digests) { - let raw: [u8; 32] = aura_id.to_raw_vec().try_into().unwrap_or([0u8; 32]); - return Some(AccountId::from(raw)); - } - None - } -} - -impl pallet_grandpa::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - type MaxAuthorities = ConstU32<32>; - type MaxNominators = ConstU32<0>; - type MaxSetIdSessionEntries = ConstU64<0>; - type KeyOwnerProof = sp_core::Void; - type EquivocationReportSystem = (); -} - -impl cumulus_pallet_weight_reclaim::Config for Runtime { - type WeightInfo = (); -} +// Pallet `Config` impls live in `configs/`, grouped by domain. They are plain +// `impl` items, so moving them out changes nothing about assembly — unlike the +// macro blocks below, which must stay whole. +pub use configs::{consensus::*, evm::*, privacy::*, system::*}; parameter_types! { pub storage EnableManualSeal: bool = false; } -pub struct ConsensusOnTimestampSet(PhantomData); -impl OnTimestampSet for ConsensusOnTimestampSet { - fn on_timestamp_set(moment: T::Moment) { - if EnableManualSeal::get() { - return; - } - as OnTimestampSet>::on_timestamp_set(moment) - } -} - -impl pallet_timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = ConsensusOnTimestampSet; - type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; - type WeightInfo = (); -} - -pub const EXISTENTIAL_DEPOSIT: u128 = 0; - -impl pallet_balances::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type RuntimeHoldReason = RuntimeHoldReason; - type RuntimeFreezeReason = RuntimeFreezeReason; - type WeightInfo = pallet_balances::weights::SubstrateWeight; - type Balance = Balance; - type DustRemoval = (); - type ExistentialDeposit = ConstU128; - type AccountStore = System; - type ReserveIdentifier = [u8; 8]; - type FreezeIdentifier = RuntimeFreezeReason; - type MaxLocks = ConstU32<50>; - type MaxReserves = ConstU32<50>; - type MaxFreezes = ConstU32<1>; - type DoneSlashHandler = (); -} - -impl pallet_transaction_payment::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = FungibleAdapter; - type WeightToFee = IdentityFee; - type LengthToFee = IdentityFee; - /// Parameterized slow adjusting fee updated based on - /// - type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; - type OperationalFeeMultiplier = ConstU8<5>; - type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight; -} - -impl pallet_sudo::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; - type WeightInfo = pallet_sudo::weights::SubstrateWeight; -} - -impl pallet_evm_chain_id::Config for Runtime {} - -pub struct FindAuthorTruncated(PhantomData); -impl> FindAuthor for FindAuthorTruncated { - fn find_author<'a, I>(digests: I) -> Option - where - I: 'a + IntoIterator, - { - if let Some(author_index) = F::find_author(digests) { - let authority_id = - pallet_aura::Authorities::::get()[author_index as usize].clone(); - return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24])); - } - None - } -} - -const BLOCK_GAS_LIMIT: u64 = 75_000_000; -const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; -/// The maximum storage growth per block in bytes. -const MAX_STORAGE_GROWTH: u64 = 400 * 1024; - -parameter_types! { - pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); - pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); - pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); - pub PrecompilesValue: FrontierPrecompiles = FrontierPrecompiles::<_>::new(); - pub WeightPerGas: Weight = Weight::from_parts(weight_per_gas(BLOCK_GAS_LIMIT, NORMAL_DISPATCH_RATIO, WEIGHT_MILLISECS_PER_BLOCK), 0); -} - -impl pallet_evm::Config for Runtime { - type AccountProvider = pallet_evm::FrameSystemAccountProvider; - type FeeCalculator = BaseFee; - type GasWeightMapping = pallet_evm::FixedGasWeightMapping; - type WeightPerGas = WeightPerGas; - type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; - type CallOrigin = EnsureAddressMatches; - type WithdrawOrigin = EnsureAddressMatches; - type AddressMapping = EeSuffixAddressMapping; - type Currency = Balances; - type PrecompilesType = FrontierPrecompiles; - type PrecompilesValue = PrecompilesValue; - type ChainId = EVMChainId; - type BlockGasLimit = BlockGasLimit; - type Runner = pallet_evm::runner::stack::Runner; - type OnChargeTransaction = (); - type OnCreate = (); - type FindAuthor = FindAuthorTruncated; - type GasLimitPovSizeRatio = GasLimitPovSizeRatio; - type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio; - type Timestamp = Timestamp; - type CreateOriginFilter = (); - type CreateInnerOriginFilter = (); - type WeightInfo = pallet_evm::weights::SubstrateWeight; -} - -parameter_types! { - pub const PostBlockAndTxnHashes: PostLogContent = PostLogContent::BlockAndTxnHashes; -} - -impl pallet_ethereum::Config for Runtime { - type StateRoot = pallet_ethereum::IntermediateStateRoot; - type PostLogContent = PostBlockAndTxnHashes; - type ExtraDataLength = ConstU32<30>; -} - -parameter_types! { - pub BoundDivision: U256 = U256::from(1024); -} - -impl pallet_dynamic_fee::Config for Runtime { - type MinGasPriceBoundDivisor = BoundDivision; -} - -parameter_types! { - // ORB uses 18 decimals (1 ORB = 1e18 wei), aligned with Ethereum tooling. - // DefaultBaseFeePerGas is used as the runtime-level fallback (e.g. storage migration). - // Actual genesis values are set per-chain-spec in genesis_config_preset/: - // all networks → 1_000_000_000 wei/gas (1 gwei → ~0.00001 ORB / transfer) - // The floor (empty blocks) = DefaultBaseFeePerGas / 2 = 0.5 gwei. - // EIP-1559 adjusts the fee upward automatically as the network gains traffic. - pub DefaultBaseFeePerGas: U256 = U256::from(1_000_000_000u64); // 1 gwei - - pub DefaultElasticity: Permill = Permill::from_parts(125_000); -} -pub struct BaseFeeThreshold; -impl pallet_base_fee::BaseFeeThreshold for BaseFeeThreshold { - fn lower() -> Permill { - Permill::zero() - } - fn ideal() -> Permill { - Permill::from_parts(500_000) - } - fn upper() -> Permill { - Permill::from_parts(1_000_000) - } -} -impl pallet_base_fee::Config for Runtime { - type Threshold = BaseFeeThreshold; - type DefaultBaseFeePerGas = DefaultBaseFeePerGas; - type DefaultElasticity = DefaultElasticity; -} - +// Dev-only manual seal, defined inline because `#[frame_support::runtime]` below +// must see the pallet in this crate root. Its `Config` impl lives in `configs/evm` +// with the rest of the EVM stack. #[frame_support::pallet] pub mod pallet_manual_seal { use super::*; @@ -582,75 +265,6 @@ pub mod pallet_manual_seal { } } -impl pallet_manual_seal::Config for Runtime {} - -impl pallet_zk_verifier::Config for Runtime { - type MaxProofSize = ConstU32<128>; - type MaxPublicInputs = ConstU32<32>; - type WeightInfo = pallet_zk_verifier::weights::SubstrateWeight; -} - -// ──────────────────────────────────────────────────────────────────────────── -// pallet-relayer -// ──────────────────────────────────────────────────────────────────────────── - -/// Provides the current block's author (Aura validator) for relay fee attribution. -pub struct RelayerBlockAuthor; -impl frame_support::traits::Get> for RelayerBlockAuthor { - fn get() -> Option { - pallet_authorship::Pallet::::author() - } -} - -impl pallet_relayer::Config for Runtime { - /// Block author for relay fee attribution. - type BlockAuthor = RelayerBlockAuthor; - /// Default minimum relay fee: 0.001 ORB = 1e15 planck (anti-spam). - /// Overridable at runtime via `set_min_relay_fee` (governance/sudo). - type DefaultMinRelayFee = ConstU128<1_000_000_000_000_000>; - /// Only sudo/governance can update relay configuration. - type ManageOrigin = frame_system::EnsureRoot; - /// Allow up to 16 ABI selectors in the whitelist. - type MaxAllowedSelectors = ConstU32<16>; - type WeightInfo = (); -} - -parameter_types! { - /// Pool account that holds all shielded tokens - pub const ShieldedPoolPalletId: PalletId = PalletId(*b"shld/pol"); -} - -impl pallet_shielded_pool::Config for Runtime { - /// Native currency (ORB) for shield/unshield operations - type Currency = Balances; - /// Groth16 proof verifier for unshield/transfer operations - type ZkVerifier = ZkVerifier; - /// Relay config, fee accumulation and block-author — delegated to pallet-relayer. - type Relayer = pallet_relayer::Pallet; - /// PalletId for the pool account - type PalletId = ShieldedPoolPalletId; - /// Merkle tree depth: 2^20 = 1M notes max (see MERKLE_TREE_SCALABILITY.md) - type MaxTreeDepth = ConstU32<20>; - /// Historic roots: allows proofs against past states (30s window) - /// Safety cap on the historic-root queue, not the retention window. A root - /// expires by elapsed blocks; this only bounds worst-case storage. - /// - /// Steady state is `RootRetentionBlocks × commitments-per-block`: 1200 at a - /// sustained 2 transfers/block, 6000 at 10. Sized for ~27 transfers/block - /// sustained across a full window, well past the ~127 proof verifications a - /// block can fit, so the window — never this bound — is what expires a root. - type MaxHistoricRoots = ConstU32<16384>; - /// Roots stay spendable for 300 blocks (~30 min at 6s), comfortably above - /// the 64-block mempool longevity of an unsigned transaction. - type RootRetentionBlocks = ConstU32<300>; - // Pinned to 2^20: clients derive tree_id = leaf_index >> 20 from this. - type MaxLeavesPerTree = ConstU32<1_048_576>; - /// Minimum shield amount: prevents spam, 1 ORB = 1e18 wei - type MinShieldAmount = ConstU128<1_000_000_000_000_000_000>; - type WeightInfo = pallet_shielded_pool::weights::SubstrateWeight; -} - -// Create the runtime by composing the FRAME pallets that were previously configured. #[frame_support::runtime] mod runtime { #[runtime::runtime] From d3a8936192a244e3f6159c15d4d6530ede8d1569 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 6 Aug 2026 22:10:24 -0400 Subject: [PATCH 2/3] feat(shielded-pool)!: remove the minimum shield amount --- Cargo.lock | 2 +- .../shielded-pool/src/calls/shield.rs | 4 +-- .../evm/precompile/shielded-pool/src/mock.rs | 2 -- .../evm/precompile/shielded-pool/src/tests.rs | 6 ++-- frame/shielded-pool/src/benchmarking.rs | 10 +++---- frame/shielded-pool/src/lib.rs | 7 +---- frame/shielded-pool/src/mock.rs | 2 -- frame/shielded-pool/src/operations/shield.rs | 28 +++++++++++++------ template/runtime/src/configs/privacy.rs | 2 -- 9 files changed, 32 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ead1397..60b56177 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7894,7 +7894,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-shielded-pool" -version = "0.3.0" +version = "0.4.0" dependencies = [ "fp-evm", "frame-support", diff --git a/frame/evm/precompile/shielded-pool/src/calls/shield.rs b/frame/evm/precompile/shielded-pool/src/calls/shield.rs index def93fb5..9ec0ef80 100644 --- a/frame/evm/precompile/shielded-pool/src/calls/shield.rs +++ b/frame/evm/precompile/shielded-pool/src/calls/shield.rs @@ -40,8 +40,8 @@ where let asset_id = abi::decode_u32(¶ms[0..32])?; // Reject zero-value calls at the precompile boundary (defense-in-depth; - // the pallet also rejects via MinShieldAmount, but this produces a cleaner - // error before reaching the dispatch layer). + // the pallet also rejects them, but this produces a cleaner error before + // reaching the dispatch layer). let apparent_value = handle.context().apparent_value; if apparent_value.is_zero() { return Err(err("shield: amount must be non-zero")); diff --git a/frame/evm/precompile/shielded-pool/src/mock.rs b/frame/evm/precompile/shielded-pool/src/mock.rs index 0eb444cd..4b45a880 100644 --- a/frame/evm/precompile/shielded-pool/src/mock.rs +++ b/frame/evm/precompile/shielded-pool/src/mock.rs @@ -122,7 +122,6 @@ parameter_types! { pub const MaxHistoricRoots: u32 = 100; pub const RootRetentionBlocks: u64 = 128; pub const MaxLeavesPerTree: u32 = 8; - pub const MinShieldAmount: u128 = 100; } pub struct MockZkVerifier; @@ -248,7 +247,6 @@ impl pallet_shielded_pool::Config for Test { type MaxHistoricRoots = MaxHistoricRoots; type RootRetentionBlocks = RootRetentionBlocks; type MaxLeavesPerTree = MaxLeavesPerTree; - type MinShieldAmount = MinShieldAmount; type WeightInfo = (); type Relayer = MockRelayer; } diff --git a/frame/evm/precompile/shielded-pool/src/tests.rs b/frame/evm/precompile/shielded-pool/src/tests.rs index 533f4704..f6c9a89d 100644 --- a/frame/evm/precompile/shielded-pool/src/tests.rs +++ b/frame/evm/precompile/shielded-pool/src/tests.rs @@ -376,12 +376,12 @@ fn shield_rejects_truncated_input() { } #[test] -fn shield_rejects_below_min_amount() { - // MinShieldAmount = 100; sending value = 1 should be rejected by the pallet. +fn shield_accepts_smallest_non_zero_amount() { + // There is no minimum shield amount: msg.value = 1 must go through. new_test_ext().execute_with(|| { let input = encode_shield(0, [0x11; 32], &[0xAB; 180]); let mut h = MockHandle::with_value(input, 1); - expect_error(ShieldedPoolPrecompile::::execute(&mut h)); + assert_success(ShieldedPoolPrecompile::::execute(&mut h)); }); } diff --git a/frame/shielded-pool/src/benchmarking.rs b/frame/shielded-pool/src/benchmarking.rs index acfcfab4..85ef638f 100644 --- a/frame/shielded-pool/src/benchmarking.rs +++ b/frame/shielded-pool/src/benchmarking.rs @@ -58,7 +58,7 @@ mod benchmarks { } // 2. Fund caller - let amount: BalanceOf = T::MinShieldAmount::get() * 1000u32.into(); + let amount: BalanceOf = 1_000_000u32.into(); let _ = >::make_free_balance_be(&caller, amount); (caller, asset_id) @@ -67,7 +67,7 @@ mod benchmarks { #[benchmark] fn shield() { let (caller, asset_id) = setup_benchmark_env::(); - let amount: BalanceOf = T::MinShieldAmount::get() * 10u32.into(); + let amount: BalanceOf = 10_000u32.into(); let commitment = Commitment([1u8; 32]); // Memo must be exactly 180 bytes (MAX_ENCRYPTED_MEMO_SIZE): nonce(12) + data(120) + MAC(16) + ephPk(32) let memo_bytes = vec![0u8; MAX_ENCRYPTED_MEMO_SIZE as usize]; @@ -86,7 +86,7 @@ mod benchmarks { #[benchmark] fn shield_batch(n: Linear<1, 20>) { let (caller, asset_id) = setup_benchmark_env::(); - let amount: BalanceOf = T::MinShieldAmount::get() * 10u32.into(); + let amount: BalanceOf = 10_000u32.into(); let mut operations = Vec::new(); for i in 0..n { @@ -151,7 +151,7 @@ mod benchmarks { let (_caller, asset_id) = setup_benchmark_env::(); let recipient: T::AccountId = account("recipient", 0, 0); let merkle_root = [1u8; 32]; - let amount: BalanceOf = T::MinShieldAmount::get() * 10u32.into(); + let amount: BalanceOf = 10_000u32.into(); // Setup valid state: root and pool balance crate::storage::MerkleRepository::add_historic_poseidon_root::(merkle_root); @@ -222,7 +222,7 @@ mod benchmarks { #[benchmark] fn claim_shielded_fees() { let (caller, asset_id) = setup_benchmark_env::(); - let amount: BalanceOf = T::MinShieldAmount::get() * 10u32.into(); + let amount: BalanceOf = 10_000u32.into(); let amount_u128: u128 = amount.saturated_into(); // Accumulate relay fees for the validator. diff --git a/frame/shielded-pool/src/lib.rs b/frame/shielded-pool/src/lib.rs index 4c223a1e..89184dd8 100644 --- a/frame/shielded-pool/src/lib.rs +++ b/frame/shielded-pool/src/lib.rs @@ -163,9 +163,6 @@ pub mod pallet { #[pallet::constant] type RootRetentionBlocks: Get>; - /// Minimum amount that can be shielded - #[pallet::constant] - type MinShieldAmount: Get>; /// Weight information for extrinsics in this pallet type WeightInfo: WeightInfo; } @@ -584,8 +581,6 @@ pub mod pallet { InvalidProof, /// Insufficient balance in the pool InsufficientPoolBalance, - /// The amount is below the minimum - AmountTooSmall, /// The amount is invalid (zero or overflow) InvalidAmount, /// Too many inputs or outputs @@ -640,7 +635,7 @@ pub mod pallet { /// * `encrypted_memo` - Encrypted metadata for note recovery and audit /// /// # Errors - /// * `AmountTooSmall` - Amount is below minimum + /// * `InvalidAmount` - Amount is zero /// * `MerkleTreeFull` - No more space in the tree /// * `CommitmentAlreadyExists` - Duplicate commitment /// * `InvalidMemoSize` - Encrypted memo is not exactly 180 bytes diff --git a/frame/shielded-pool/src/mock.rs b/frame/shielded-pool/src/mock.rs index a6850d1c..94396c1f 100644 --- a/frame/shielded-pool/src/mock.rs +++ b/frame/shielded-pool/src/mock.rs @@ -54,7 +54,6 @@ parameter_types! { /// a test can advance past it to exercise expiry. pub const RootRetentionBlocks: u64 = 128; pub const MaxLeavesPerTree: u32 = 8; - pub const MinShieldAmount: u128 = 100; pub const MaxProofSize: u32 = 256; pub const MaxPublicInputs: u32 = 10; } @@ -160,7 +159,6 @@ impl pallet_shielded_pool::Config for Test { type MaxHistoricRoots = MaxHistoricRoots; type RootRetentionBlocks = RootRetentionBlocks; type MaxLeavesPerTree = MaxLeavesPerTree; - type MinShieldAmount = MinShieldAmount; type WeightInfo = (); type Relayer = pallet_relayer::Pallet; } diff --git a/frame/shielded-pool/src/operations/shield.rs b/frame/shielded-pool/src/operations/shield.rs index b3bb280c..444bbd7b 100644 --- a/frame/shielded-pool/src/operations/shield.rs +++ b/frame/shielded-pool/src/operations/shield.rs @@ -2,6 +2,7 @@ use frame_support::{ pallet_prelude::*, traits::{Currency, ExistenceRequirement}, }; +use sp_runtime::traits::Zero; use crate::{ merkle::MerkleTreeService, @@ -22,10 +23,7 @@ impl ShieldOperation { ) -> DispatchResult { let asset = AssetRepository::get_asset::(asset_id).ok_or(Error::::InvalidAssetId)?; ensure!(asset.is_verified, Error::::AssetNotVerified); - ensure!( - amount >= T::MinShieldAmount::get(), - Error::::AmountTooSmall - ); + ensure!(!amount.is_zero(), Error::::InvalidAmount); ensure!( encrypted_memo.0.len() == MAX_ENCRYPTED_MEMO_SIZE as usize, Error::::InvalidMemoSize @@ -160,23 +158,37 @@ mod tests { } #[test] - fn execute_amount_too_small_fails() { + fn execute_zero_amount_fails() { new_test_ext().execute_with(|| { let asset_id = setup_asset(); - // MinShieldAmount = 100; amount = 50 < 100 assert_noop!( ShieldOperation::execute::( acc(1), asset_id, - 50u128, + 0u128, commitment(1), memo_valid() ), - crate::pallet::Error::::AmountTooSmall + crate::pallet::Error::::InvalidAmount ); }); } + /// There is no minimum: a 1-unit shield is accepted. + #[test] + fn execute_accepts_smallest_non_zero_amount() { + new_test_ext().execute_with(|| { + let asset_id = setup_asset(); + assert_ok!(ShieldOperation::execute::( + acc(1), + asset_id, + 1u128, + commitment(1), + memo_valid(), + )); + }); + } + #[test] fn execute_invalid_memo_size_fails() { new_test_ext().execute_with(|| { diff --git a/template/runtime/src/configs/privacy.rs b/template/runtime/src/configs/privacy.rs index d21221ca..9266f79b 100644 --- a/template/runtime/src/configs/privacy.rs +++ b/template/runtime/src/configs/privacy.rs @@ -69,8 +69,6 @@ impl pallet_shielded_pool::Config for Runtime { type RootRetentionBlocks = ConstU32<300>; // Pinned to 2^20: clients derive tree_id = leaf_index >> 20 from this. type MaxLeavesPerTree = ConstU32<1_048_576>; - /// Minimum shield amount: prevents spam, 1 ORB = 1e18 wei - type MinShieldAmount = ConstU128<1_000_000_000_000_000_000>; type WeightInfo = pallet_shielded_pool::weights::SubstrateWeight; } From 8ff67f4ecf6a197dc465f49362573a4cd1777ab7 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 6 Aug 2026 23:55:05 -0400 Subject: [PATCH 3/3] feat(validator-set)!: remove the 1 000 ORB registration bond --- Cargo.lock | 2 +- frame/validator-set/CHANGELOG.md | 38 ++++ frame/validator-set/Cargo.toml | 2 +- frame/validator-set/src/lib.rs | 114 +++--------- frame/validator-set/src/mock.rs | 6 +- frame/validator-set/src/tests.rs | 166 +++++------------- scripts/validator-keys/insert-session-keys.sh | 6 +- template/runtime/src/configs/consensus.rs | 5 - 8 files changed, 114 insertions(+), 225 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60b56177..f6a97564 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8281,7 +8281,7 @@ dependencies = [ [[package]] name = "pallet-validator-set" -version = "0.1.0" +version = "0.2.0" dependencies = [ "frame-benchmarking", "frame-support", diff --git a/frame/validator-set/CHANGELOG.md b/frame/validator-set/CHANGELOG.md index 86f973e2..45ec1a2e 100644 --- a/frame/validator-set/CHANGELOG.md +++ b/frame/validator-set/CHANGELOG.md @@ -5,6 +5,44 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). --- +## [0.2.0] — 2026-08-06 + +### Removed + +#### Validator bond +The 1 000 ORB bond required by `register_validator` is gone, along with the +`ValidatorBond` and `Currency` Config items, the `ValidatorBondOf` storage map, +the `ValidatorBondReserved` / `ValidatorBondReleased` events, and the +`InsufficientBond` error. + +Registration is now free. What still gates it is unchanged: the account needs +session keys and a registered EVM relayer, the pending queue is bounded by +`MaxPendingValidators`, and — the part that actually matters — no account enters +the active set without an explicit `approve_validator` from sudo. A bond deters +spam that governance approval already blocks, and on a testnet where operators +are onboarded by hand it only added a funding step. + +Consensus is expected to change before mainnet; a staking-based scheme will +bring its own economic gate. + +**Breaking:** `register_validator` no longer reserves funds and no longer fails +with `InsufficientBond`. Callers that pre-funded 1 001 ORB to register can stop. + +### Notes +- No migration ships with this change: `ValidatorBondOf` was verified empty on + testnet (0 entries, empty pending queue) before removing it, so there are no + reserves left stranded. A chain that *had* live bonds would need one. + +### Verification +50 pallet tests; runtime, `try-runtime` and `runtime-benchmarks` all compile. A +dev-node E2E (`ts-tests/no-bond-no-min-shield.test.cjs`, 11/11) checks that the +constant, the storage map, the events and `InsufficientBond` are all absent from +metadata, and that an account holding 1 ORB — a thousandth of the old bond — +reaches the prerequisite gate instead of failing on funds, reserving nothing on +the way. + +--- + ## [0.1.0] — 2026-06-03 ### Added diff --git a/frame/validator-set/Cargo.toml b/frame/validator-set/Cargo.toml index 3d161b56..1b5c544b 100644 --- a/frame/validator-set/Cargo.toml +++ b/frame/validator-set/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-validator-set" -version = "0.1.0" +version = "0.2.0" description = "Sudo-controlled validator set for Orbinum. Validators can only join via governance." authors = { workspace = true } license = "GPL-3.0-or-later" diff --git a/frame/validator-set/src/lib.rs b/frame/validator-set/src/lib.rs index 3a67c400..1b0ce779 100644 --- a/frame/validator-set/src/lib.rs +++ b/frame/validator-set/src/lib.rs @@ -1,32 +1,31 @@ //! # Pallet Validator Set //! -//! Two-phase validator registration for Orbinum: self-registration with a bond deposit, +//! Two-phase validator registration for Orbinum: permissionless self-registration, //! followed by explicit governance approval before the node joins the active set. //! //! ## Registration Flow //! -//! 1. The candidate account holds **> 1 001 ORB** (1 000 bond + fees buffer). -//! 2. The candidate calls [`register_validator`][Pallet::register_validator], which locks -//! `T::ValidatorBond` (1 000 ORB) and places the account in the **pending** queue. +//! 1. The candidate registers session keys (`session.setKeys`) and an EVM relay address +//! (`relayer.register_relayer`). +//! 2. The candidate calls [`register_validator`][Pallet::register_validator], which places +//! the account in the **pending** queue. //! 3. `AddRemoveOrigin` (sudo / governance) reviews and calls //! [`approve_validator`][Pallet::approve_validator] → account moves to the **approved** set //! and becomes an active block-producer at the next session rotation. -//! 4. To leave voluntarily, the validator calls [`deregister_validator`][Pallet::deregister_validator]; -//! the bond is returned immediately (whether still pending or already approved). +//! 4. To leave voluntarily, the validator calls [`deregister_validator`][Pallet::deregister_validator]. //! //! ## Sudo Paths //! -//! - [`add_validator`][Pallet::add_validator] — Directly add a trusted node (no bond). -//! - [`remove_validator`][Pallet::remove_validator] — Force-remove from pending or approved; bond returned. +//! - [`add_validator`][Pallet::add_validator] — Directly add a trusted node. +//! - [`remove_validator`][Pallet::remove_validator] — Force-remove from pending or approved. //! - [`approve_validator`][Pallet::approve_validator] — Approve a pending registration. -//! - [`reject_validator`][Pallet::reject_validator] — Reject a pending registration; bond returned. +//! - [`reject_validator`][Pallet::reject_validator] — Reject a pending registration. //! //! ## Security //! //! - A non-approved account **never** enters the active validator set. -//! - The bond incentivises legitimate registrations and deters spam. -//! - Bond is always returned (approve → stay in set; reject/remove → immediate release; -//! deregister → immediate release). +//! - Spam is bounded by `MaxPendingValidators` and by the session-key and relayer +//! prerequisites, both of which cost a transaction to satisfy. #![cfg_attr(not(feature = "std"), no_std)] @@ -64,10 +63,7 @@ pub trait ValidatorPrerequisites { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{ - pallet_prelude::*, - traits::{Currency, EnsureOrigin, ReservableCurrency}, - }; + use frame_support::{pallet_prelude::*, traits::EnsureOrigin}; use frame_system::pallet_prelude::*; use pallet_session::SessionManager; @@ -79,9 +75,6 @@ pub mod pallet { /// Origin allowed to add/remove/approve/reject validators. Use `EnsureRoot` for sudo. type AddRemoveOrigin: EnsureOrigin; - /// Currency used to reserve the validator bond on self-registration. - type Currency: ReservableCurrency; - /// Maximum number of validators in the **approved** (active) set. #[pallet::constant] type MaxValidators: Get; @@ -90,25 +83,16 @@ pub mod pallet { #[pallet::constant] type MaxPendingValidators: Get; - /// Bond (in native tokens) locked when a candidate calls `register_validator`. - /// With 18 decimals: `1_000 * 10^18` = 1 000 ORB. - #[pallet::constant] - type ValidatorBond: Get>; - /// Prerequisite gate checked on every `register_validator` call. /// /// The caller must have both session keys and an EVM relayer registered - /// before they are allowed to lock the bond and enter the pending queue. + /// before they are allowed to enter the pending queue. type Prerequisites: crate::ValidatorPrerequisites; /// Weight information for the pallet's dispatchables. type WeightInfo: crate::WeightInfo; } - /// Shorthand for the currency balance type. - pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; - /// The approved (active) set of validator account IDs. /// /// Included as block producers at every session rotation. @@ -127,36 +111,19 @@ pub mod pallet { pub type PendingValidators = StorageValue<_, BoundedVec, ValueQuery>; - /// Bond amount reserved at the time of self-registration (`register_validator`). - /// Stored per-account so the exact amount is returned even if `T::ValidatorBond` changes. - /// Validators added via sudo `add_validator` have no entry here. - #[pallet::storage] - pub type ValidatorBondOf = - StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf, OptionQuery>; - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// A validator was directly added by sudo. Takes effect next session. ValidatorAdded { validator: T::AccountId }, - /// A validator was removed (approved or pending). Bond returned if held. + /// A validator was removed (approved or pending). ValidatorRemoved { validator: T::AccountId }, /// A self-registration was submitted; awaiting governance approval. ValidatorRegistrationRequested { validator: T::AccountId }, /// A pending registration was approved. Takes effect next session. ValidatorApproved { validator: T::AccountId }, - /// A pending registration was rejected; bond returned. + /// A pending registration was rejected. ValidatorRejected { validator: T::AccountId }, - /// Bond reserved when a candidate self-registered. - ValidatorBondReserved { - validator: T::AccountId, - amount: BalanceOf, - }, - /// Bond released on deregistration, removal, or rejection. - ValidatorBondReleased { - validator: T::AccountId, - amount: BalanceOf, - }, } #[pallet::error] @@ -173,8 +140,6 @@ pub mod pallet { TooManyValidators, /// The pending queue is full (`MaxPendingValidators` reached). TooManyPending, - /// Account does not have enough free balance to cover `ValidatorBond`. - InsufficientBond, /// Session keys (Aura + GRANDPA) not yet registered via `session.setKeys`. NoSessionKeys, /// EVM relay address not yet registered via `relayer.register_relayer`. @@ -204,7 +169,7 @@ pub mod pallet { impl Pallet { // ── Sudo paths ─────────────────────────────────────────────────────────────────── - /// Directly add a trusted account to the approved set (no bond required). + /// Directly add a trusted account to the approved set. /// /// Requires `AddRemoveOrigin`. The account must not be in the pending queue. /// Takes effect at the next session rotation. @@ -232,8 +197,8 @@ pub mod pallet { /// Force-remove an account from the approved set or the pending queue. /// - /// Requires `AddRemoveOrigin`. If the account had a bond, it is returned. - /// Takes effect at the next session rotation (for approved validators). + /// Requires `AddRemoveOrigin`. Takes effect at the next session rotation + /// (for approved validators). #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::remove_validator())] pub fn remove_validator(origin: OriginFor, validator: T::AccountId) -> DispatchResult { @@ -264,7 +229,6 @@ pub mod pallet { }; ensure!(in_approved || in_pending, Error::::NotValidator); - Self::release_bond(&validator); Self::deposit_event(Event::ValidatorRemoved { validator }); Ok(()) } @@ -272,7 +236,7 @@ pub mod pallet { /// Approve a pending validator registration. /// /// Requires `AddRemoveOrigin`. Moves the account from the pending queue to the - /// approved set. The bond remains locked. Takes effect at the next session rotation. + /// approved set. Takes effect at the next session rotation. #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::add_validator())] pub fn approve_validator(origin: OriginFor, validator: T::AccountId) -> DispatchResult { @@ -303,10 +267,9 @@ pub mod pallet { Ok(()) } - /// Reject a pending validator registration and return the bond. + /// Reject a pending validator registration. /// - /// Requires `AddRemoveOrigin`. The applicant is removed from the pending queue - /// and their `ValidatorBond` is unreserved in full. + /// Requires `AddRemoveOrigin`. The applicant is removed from the pending queue. #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::remove_validator())] pub fn reject_validator(origin: OriginFor, validator: T::AccountId) -> DispatchResult { @@ -321,26 +284,20 @@ pub mod pallet { Ok(()) })?; - Self::release_bond(&validator); Self::deposit_event(Event::ValidatorRejected { validator }); Ok(()) } // ── Self-service paths ──────────────────────────────────────────────────── - /// Submit a validator registration request by locking `T::ValidatorBond` tokens. + /// Submit a validator registration request. /// /// The caller is placed in the **pending** queue — they do NOT become an active - /// validator until `AddRemoveOrigin` calls `approve_validator`. The bond is held - /// until the account is approved, rejected, or calls `deregister_validator`. - /// - /// The caller must have **> `ValidatorBond`** free balance to also cover - /// any subsequent transaction fees (e.g., registering the EVM relay address). + /// validator until `AddRemoveOrigin` calls `approve_validator`. #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::add_validator())] pub fn register_validator(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let bond = T::ValidatorBond::get(); // Reject if already active or already pending. ensure!( @@ -357,26 +314,19 @@ pub mod pallet { PendingValidators::::try_mutate(|pending| { ensure!(!pending.contains(&who), Error::::AlreadyPending); - T::Currency::reserve(&who, bond).map_err(|_| Error::::InsufficientBond)?; - ValidatorBondOf::::insert(&who, bond); pending .try_push(who.clone()) .map_err(|_| Error::::TooManyPending) })?; - Self::deposit_event(Event::ValidatorBondReserved { - validator: who.clone(), - amount: bond, - }); Self::deposit_event(Event::ValidatorRegistrationRequested { validator: who }); Ok(()) } /// Cancel a pending registration or self-remove from the approved set. /// - /// Works from both the **pending** queue and the **approved** set. The bond - /// (if held) is returned immediately. Removal from the approved set takes effect - /// at the next session rotation. + /// Works from both the **pending** queue and the **approved** set. Removal from + /// the approved set takes effect at the next session rotation. #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::remove_validator())] pub fn deregister_validator(origin: OriginFor) -> DispatchResult { @@ -404,25 +354,11 @@ pub mod pallet { })?; } - Self::release_bond(&who); Self::deposit_event(Event::ValidatorRemoved { validator: who }); Ok(()) } } - impl Pallet { - /// Unreserve the bond held for `who` (if any) and emit `ValidatorBondReleased`. - fn release_bond(who: &T::AccountId) { - if let Some(bond) = ValidatorBondOf::::take(who) { - T::Currency::unreserve(who, bond); - Self::deposit_event(Event::ValidatorBondReleased { - validator: who.clone(), - amount: bond, - }); - } - } - } - /// `pallet_session::SessionManager` implementation. /// /// Returns the current approved validator list on every new session, applying diff --git a/frame/validator-set/src/mock.rs b/frame/validator-set/src/mock.rs index 708e4641..43dd506f 100644 --- a/frame/validator-set/src/mock.rs +++ b/frame/validator-set/src/mock.rs @@ -64,16 +64,12 @@ impl pallet_balances::Config for Test { parameter_types! { pub const MaxValidators: u32 = 10; pub const MaxPendingValidators: u32 = 10; - /// 1 000 test units — mirrors the 1 000 ORB requirement on mainnet. - pub const ValidatorBond: u64 = 1_000; } impl pallet_validator_set::Config for Test { type AddRemoveOrigin = frame_system::EnsureRoot; - type Currency = Balances; type MaxValidators = MaxValidators; type MaxPendingValidators = MaxPendingValidators; - type ValidatorBond = ValidatorBond; type Prerequisites = MockPrerequisites; type WeightInfo = (); } @@ -109,7 +105,7 @@ impl ExtBuilder { .assimilate_storage(&mut storage) .unwrap(); - // Pre-fund well-known test accounts with enough balance to cover ValidatorBond. + // Pre-fund well-known test accounts so they exist and can dispatch. pallet_balances::GenesisConfig:: { balances: vec![ (10u64, 10_000), diff --git a/frame/validator-set/src/tests.rs b/frame/validator-set/src/tests.rs index 69c1817b..166ff31b 100644 --- a/frame/validator-set/src/tests.rs +++ b/frame/validator-set/src/tests.rs @@ -1,6 +1,6 @@ use frame_support::{assert_noop, assert_ok}; -use crate::{ApprovedValidators, Error, Event, PendingValidators, ValidatorBondOf, mock::*}; +use crate::{ApprovedValidators, Error, Event, PendingValidators, mock::*}; use pallet_session::SessionManager; // ── Genesis ────────────────────────────────────────────────────────────────── @@ -161,39 +161,33 @@ fn remove_validator_requires_root() { #[test] fn remove_validator_can_cancel_pending_registration() { - // Sudo can force-cancel a pending applicant and their bond is returned. + // Sudo can force-cancel a pending applicant. ExtBuilder::default() .validators(vec![1]) .build() .execute_with(|| { assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); assert!(PendingValidators::::get().contains(&42)); - assert_eq!(Balances::reserved_balance(42u64), 1_000); assert_ok!(ValidatorSet::remove_validator(RuntimeOrigin::root(), 42)); assert!(!PendingValidators::::get().contains(&42)); assert!(!ApprovedValidators::::get().contains(&42)); - assert_eq!(Balances::reserved_balance(42u64), 0); - assert_eq!(ValidatorBondOf::::get(42u64), None); }); } #[test] -fn remove_validator_releases_bond_of_approved_self_registered() { - // Sudo removes an approved self-registered validator; bond is returned. +fn remove_validator_drops_approved_self_registered() { ExtBuilder::default() .validators(vec![1]) .build() .execute_with(|| { assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); assert_ok!(ValidatorSet::approve_validator(RuntimeOrigin::root(), 42)); - assert_eq!(Balances::reserved_balance(42u64), 1_000); assert_ok!(ValidatorSet::remove_validator(RuntimeOrigin::root(), 42)); - assert_eq!(Balances::reserved_balance(42u64), 0); - assert_eq!(ValidatorBondOf::::get(42u64), None); + assert!(!ApprovedValidators::::get().contains(&42)); }); } @@ -214,37 +208,41 @@ fn register_validator_goes_to_pending_not_approved() { } #[test] -fn register_validator_reserves_bond() { +fn register_validator_emits_event() { ExtBuilder::default() .validators(vec![1]) .build() .execute_with(|| { assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); - assert_eq!(Balances::reserved_balance(42u64), 1_000); - assert_eq!(Balances::free_balance(42u64), 10_000 - 1_000); - assert_eq!(ValidatorBondOf::::get(42u64), Some(1_000)); + System::assert_last_event( + Event::ValidatorRegistrationRequested { validator: 42 }.into(), + ); }); } #[test] -fn register_validator_emits_events() { +fn register_validator_does_not_touch_balances() { + // Registration is free: no reserve, no transfer, no fee taken by the pallet. ExtBuilder::default() .validators(vec![1]) .build() .execute_with(|| { assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); - let events = System::events(); - assert!(events.iter().any(|r| matches!( - &r.event, - RuntimeEvent::ValidatorSet(Event::ValidatorBondReserved { - validator: 42, - amount: 1_000, - }) - ))); - assert!(events.iter().any(|r| matches!( - &r.event, - RuntimeEvent::ValidatorSet(Event::ValidatorRegistrationRequested { validator: 42 }) - ))); + assert_eq!(Balances::reserved_balance(42u64), 0); + assert_eq!(Balances::free_balance(42u64), 10_000); + }); +} + +#[test] +fn register_validator_works_for_account_with_no_balance() { + // Account 999 has no pre-funded balance — registration must still succeed + // now that there is no bond to reserve. + ExtBuilder::default() + .validators(vec![1]) + .build() + .execute_with(|| { + assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(999))); + assert!(PendingValidators::::get().contains(&999)); }); } @@ -305,42 +303,20 @@ fn register_validator_fails_if_no_relayer() { }); } -#[test] -fn register_validator_fails_if_insufficient_balance() { - ExtBuilder::default() - .validators(vec![1]) - .build() - .execute_with(|| { - // Account 999 has no pre-funded balance. - assert_noop!( - ValidatorSet::register_validator(RuntimeOrigin::signed(999)), - Error::::InsufficientBond - ); - }); -} - #[test] fn register_validator_fails_when_pending_queue_is_full() { // MaxPendingValidators = 10. Fill the pending queue then try one more. ExtBuilder::default() - .validators(vec![]) // empty approved set so genesis accounts can register + .validators(vec![]) // empty approved set so any account can register .build() .execute_with(|| { - // 7 funded accounts from ExtBuilder (10,20,30,42,99,100,200). - for acc in [10u64, 20, 30, 42, 99, 100, 200] { - assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(acc))); - } - // Top up 3 more accounts inline. - use frame_support::traits::fungible::Mutate; - for acc in [201u64, 202, 203] { - Balances::set_balance(&acc, 10_000); + for acc in 1u64..=10 { assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(acc))); } assert_eq!(PendingValidators::::get().len(), 10); - Balances::set_balance(&999u64, 10_000); assert_noop!( - ValidatorSet::register_validator(RuntimeOrigin::signed(999)), + ValidatorSet::register_validator(RuntimeOrigin::signed(11)), Error::::TooManyPending ); }); @@ -362,21 +338,6 @@ fn approve_validator_moves_pending_to_approved() { }); } -#[test] -fn approve_validator_does_not_release_bond() { - // Bond stays locked after approval; the validator is active. - ExtBuilder::default() - .validators(vec![1]) - .build() - .execute_with(|| { - assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); - assert_ok!(ValidatorSet::approve_validator(RuntimeOrigin::root(), 42)); - - assert_eq!(Balances::reserved_balance(42u64), 1_000); - assert_eq!(ValidatorBondOf::::get(42u64), Some(1_000)); - }); -} - #[test] fn approve_validator_emits_event() { ExtBuilder::default() @@ -415,9 +376,8 @@ fn approve_validator_fails_when_approved_set_is_full() { ValidatorSet::approve_validator(RuntimeOrigin::root(), 42), Error::::TooManyValidators ); - // Applicant remains pending, bond not lost. + // Applicant remains pending. assert!(PendingValidators::::get().contains(&42)); - assert_eq!(Balances::reserved_balance(42u64), 1_000); }); } @@ -438,44 +398,29 @@ fn approve_validator_requires_root() { // ── reject_validator (sudo) ─────────────────────────────────────────────────── #[test] -fn reject_validator_removes_from_pending_and_releases_bond() { +fn reject_validator_removes_from_pending() { ExtBuilder::default() .validators(vec![1]) .build() .execute_with(|| { assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); - assert_eq!(Balances::reserved_balance(42u64), 1_000); assert_ok!(ValidatorSet::reject_validator(RuntimeOrigin::root(), 42)); assert!(!PendingValidators::::get().contains(&42)); assert!(!ApprovedValidators::::get().contains(&42)); - assert_eq!(Balances::reserved_balance(42u64), 0); - assert_eq!(Balances::free_balance(42u64), 10_000); - assert_eq!(ValidatorBondOf::::get(42u64), None); }); } #[test] -fn reject_validator_emits_events() { +fn reject_validator_emits_event() { ExtBuilder::default() .validators(vec![1]) .build() .execute_with(|| { assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); assert_ok!(ValidatorSet::reject_validator(RuntimeOrigin::root(), 42)); - let events = System::events(); - assert!(events.iter().any(|r| matches!( - &r.event, - RuntimeEvent::ValidatorSet(Event::ValidatorBondReleased { - validator: 42, - amount: 1_000, - }) - ))); - assert!(events.iter().any(|r| matches!( - &r.event, - RuntimeEvent::ValidatorSet(Event::ValidatorRejected { validator: 42 }) - ))); + System::assert_last_event(Event::ValidatorRejected { validator: 42 }.into()); }); } @@ -509,36 +454,31 @@ fn reject_validator_requires_root() { // ── deregister_validator (signed) ───────────────────────────────────────────── #[test] -fn deregister_from_approved_removes_and_releases_bond() { +fn deregister_from_approved_removes_account() { ExtBuilder::default() .validators(vec![1]) .build() .execute_with(|| { assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); assert_ok!(ValidatorSet::approve_validator(RuntimeOrigin::root(), 42)); - let free_after_register = Balances::free_balance(42u64); assert_ok!(ValidatorSet::deregister_validator(RuntimeOrigin::signed( 42 ))); assert!(!ApprovedValidators::::get().contains(&42)); - assert_eq!(Balances::reserved_balance(42u64), 0); - assert_eq!(Balances::free_balance(42u64), free_after_register + 1_000); - assert_eq!(ValidatorBondOf::::get(42u64), None); }); } #[test] -fn deregister_from_pending_removes_and_releases_bond() { - // Candidate changes their mind before approval — bond must be returned. +fn deregister_from_pending_removes_account() { + // Candidate changes their mind before approval. ExtBuilder::default() .validators(vec![1]) .build() .execute_with(|| { assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); assert!(PendingValidators::::get().contains(&42)); - assert_eq!(Balances::reserved_balance(42u64), 1_000); assert_ok!(ValidatorSet::deregister_validator(RuntimeOrigin::signed( 42 @@ -546,14 +486,11 @@ fn deregister_from_pending_removes_and_releases_bond() { assert!(!PendingValidators::::get().contains(&42)); assert!(!ApprovedValidators::::get().contains(&42)); - assert_eq!(Balances::reserved_balance(42u64), 0); - assert_eq!(Balances::free_balance(42u64), 10_000); - assert_eq!(ValidatorBondOf::::get(42u64), None); }); } #[test] -fn deregister_emits_events() { +fn deregister_emits_event() { ExtBuilder::default() .validators(vec![1]) .build() @@ -563,18 +500,7 @@ fn deregister_emits_events() { assert_ok!(ValidatorSet::deregister_validator(RuntimeOrigin::signed( 42 ))); - let events = System::events(); - assert!(events.iter().any(|r| matches!( - &r.event, - RuntimeEvent::ValidatorSet(Event::ValidatorBondReleased { - validator: 42, - amount: 1_000, - }) - ))); - assert!(events.iter().any(|r| matches!( - &r.event, - RuntimeEvent::ValidatorSet(Event::ValidatorRemoved { validator: 42 }) - ))); + System::assert_last_event(Event::ValidatorRemoved { validator: 42 }.into()); }); } @@ -592,8 +518,7 @@ fn deregister_fails_if_not_in_pending_or_approved() { } #[test] -fn deregister_no_bond_if_added_via_sudo() { - // Validators added via sudo have no bond — deregister should still work cleanly. +fn deregister_works_for_validator_added_via_sudo() { ExtBuilder::default() .validators(vec![42]) .build() @@ -602,7 +527,6 @@ fn deregister_no_bond_if_added_via_sudo() { 42 ))); assert!(!ApprovedValidators::::get().contains(&42)); - assert_eq!(Balances::reserved_balance(42u64), 0); }); } @@ -712,22 +636,22 @@ fn register_approve_deregister_full_flow() { .validators(vec![1]) .build() .execute_with(|| { - // 1. Register → pending, bond locked. + // 1. Register → pending. assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); assert!(PendingValidators::::get().contains(&42)); - assert_eq!(Balances::reserved_balance(42u64), 1_000); - // 2. Approve → approved, bond still locked. + // 2. Approve → approved. assert_ok!(ValidatorSet::approve_validator(RuntimeOrigin::root(), 42)); assert!(!PendingValidators::::get().contains(&42)); assert!(ApprovedValidators::::get().contains(&42)); - assert_eq!(Balances::reserved_balance(42u64), 1_000); - // 3. Deregister → removed, bond returned. + // 3. Deregister → removed. assert_ok!(ValidatorSet::deregister_validator(RuntimeOrigin::signed( 42 ))); assert!(!ApprovedValidators::::get().contains(&42)); + + // Balances untouched throughout. assert_eq!(Balances::reserved_balance(42u64), 0); assert_eq!(Balances::free_balance(42u64), 10_000); }); @@ -741,7 +665,7 @@ fn register_reject_then_re_register_works() { .execute_with(|| { assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); assert_ok!(ValidatorSet::reject_validator(RuntimeOrigin::root(), 42)); - // Bond returned — can register again. + // Rejection is not a ban — the account can apply again. assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); assert!(PendingValidators::::get().contains(&42)); }); @@ -758,7 +682,7 @@ fn can_re_register_after_deregister_from_approved() { assert_ok!(ValidatorSet::deregister_validator(RuntimeOrigin::signed( 42 ))); - // Bond was released — can register again immediately. + // Leaving the set does not block re-application. assert_ok!(ValidatorSet::register_validator(RuntimeOrigin::signed(42))); assert!(PendingValidators::::get().contains(&42)); }); diff --git a/scripts/validator-keys/insert-session-keys.sh b/scripts/validator-keys/insert-session-keys.sh index f6ad4dc3..5b8ebba9 100755 --- a/scripts/validator-keys/insert-session-keys.sh +++ b/scripts/validator-keys/insert-session-keys.sh @@ -167,13 +167,13 @@ echo -e " proof: 0x" echo -e " Sign with: your validator account (needs small balance for tx fee)" echo "" -echo -e "${BOLD}Step 3 — Submit validator registration (locks 1 000 ORB bond)${NC}" +echo -e "${BOLD}Step 3 — Submit validator registration${NC}" echo -e " Both session keys (Step 2) and EVM relayer (auto at Step 1) must be set." echo -e " Polkadot.js Apps → Developer → Extrinsics" echo -e " Pallet: validatorSet" echo -e " Method: registerValidator()" -echo -e " Sign with: your validator account (must have > 1 001 ORB free balance)" -echo -e " ${YELLOW}→ Your account enters the pending queue. Bond is locked.${NC}" +echo -e " Sign with: your validator account (needs small balance for tx fee)" +echo -e " ${YELLOW}→ Your account enters the pending queue.${NC}" echo "" echo -e "${BOLD}Step 4 — Wait for sudo/governance approval${NC}" diff --git a/template/runtime/src/configs/consensus.rs b/template/runtime/src/configs/consensus.rs index 0aed3ab3..d051a98f 100644 --- a/template/runtime/src/configs/consensus.rs +++ b/template/runtime/src/configs/consensus.rs @@ -76,15 +76,10 @@ impl pallet_validator_set::ValidatorPrerequisites for ValidatorPrereq impl pallet_validator_set::Config for Runtime { /// Only sudo (EnsureRoot) can add/remove/approve/reject validators. type AddRemoveOrigin = frame_system::EnsureRoot; - /// Native currency (ORB) used to lock the validator bond. - type Currency = Balances; /// Maximum 32 validators in the approved (active) set. type MaxValidators = ConstU32<32>; /// Maximum 32 registrations awaiting governance approval. type MaxPendingValidators = ConstU32<32>; - /// Validator bond: 1 000 ORB (18 decimals) locked on self-registration. - /// Returned in full on deregistration, rejection, or force-removal. - type ValidatorBond = ConstU128<1_000_000_000_000_000_000_000>; /// Prerequisite gate: verifies session keys and EVM relayer before accepting registration. type Prerequisites = ValidatorPrerequisiteChecker; type WeightInfo = pallet_validator_set::weights::SubstrateWeight;