From 7ccfb2e0e4ce2a5c855b402ce2f3fa4ad3225eb7 Mon Sep 17 00:00:00 2001 From: Jakub Zajkowski Date: Wed, 4 Mar 2026 21:27:27 +0100 Subject: [PATCH 1/2] =?UTF-8?q?Added=20chainspec=20property=20minimum=5Fde?= =?UTF-8?q?legation=5Frate=20Minimum=20delegation=20rate=20applies=20to=20?= =?UTF-8?q?new=20add=20bids=20and=20new=20reservations=20Co-author:=20Mich?= =?UTF-8?q?a=C5=82=20Papierski=20michal@papierski.net?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 5 +- execution_engine/src/runtime/mod.rs | 37 +++++- .../test_support/src/chainspec_config.rs | 9 ++ .../src/genesis_config_builder.rs | 11 +- .../src/upgrade_request_builder.rs | 16 ++- .../tests/src/test/contract_api/auction.rs | 55 +++++++++ .../tests/src/test/contract_api/mod.rs | 1 + .../src/test/system_contracts/upgrade.rs | 69 ++++++++++- .../tests/src/test/upgrade.rs | 76 +++++++++++- node/Cargo.toml | 3 +- .../main_reactor/tests/configs_override.rs | 7 ++ .../src/reactor/main_reactor/tests/fixture.rs | 2 + .../tests/transaction_scenario.rs | 109 +++++++++++++++++- .../tests/transaction_scenario/asertions.rs | 24 +++- .../tests/transaction_scenario/utils.rs | 8 ++ .../transaction/transaction_v1_builder.rs | 22 +++- resources/integration-test/chainspec.toml | 2 + resources/local/chainspec.toml.in | 2 + resources/mainnet/chainspec.toml | 2 + resources/production/chainspec.toml | 2 + resources/testnet/chainspec.toml | 2 + .../contracts/test/auction-bids/src/main.rs | 24 +++- storage/src/global_state/state/mod.rs | 45 +++++--- storage/src/system/auction.rs | 14 ++- storage/src/system/auction/detail.rs | 22 +++- .../genesis/account_contract_installer.rs | 39 +++++-- .../src/system/genesis/entity_installer.rs | 20 +++- storage/src/system/protocol_upgrade.rs | 31 ++++- storage/src/system/runtime_native.rs | 30 ++++- types/src/chainspec.rs | 2 + types/src/chainspec/core_config.rs | 9 ++ types/src/chainspec/genesis_config.rs | 15 ++- types/src/chainspec/upgrade_config.rs | 12 +- types/src/crypto/asymmetric_key.rs | 1 + types/src/system/auction/constants.rs | 2 + types/src/system/auction/error.rs | 8 ++ 36 files changed, 661 insertions(+), 77 deletions(-) create mode 100644 execution_engine_testing/tests/src/test/contract_api/auction.rs diff --git a/Cargo.lock b/Cargo.lock index d08411ca9b..6669844a6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -585,9 +585,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "call-contract" @@ -1034,6 +1034,7 @@ dependencies = [ "rand_core", "regex", "reqwest", + "rmp", "rmp-serde", "schemars", "serde", diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index a594dbe080..2a5a7af2a2 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -52,9 +52,9 @@ use casper_types::{ }, system::{ self, - auction::{self, DelegatorKind, EraInfo}, - handle_payment, mint, - mint::MINT_SUSTAIN_PURSE_KEY, + auction::{self, DelegatorKind, EraInfo, MINIMUM_DELEGATION_RATE_KEY}, + handle_payment, + mint::{self, MINT_SUSTAIN_PURSE_KEY}, CallStackElement, Caller, CallerInfo, SystemEntityType, AUCTION, HANDLE_PAYMENT, MINT, STANDARD_PAYMENT, }, @@ -1084,7 +1084,7 @@ where let max_delegators_per_validator = self.context.engine_config().max_delegators_per_validator(); - + let minimum_delegation_rate = self.get_minimum_delegation_rate()?; let minimum_bid_amount = self.context().engine_config().minimum_bid_amount(); let result = runtime @@ -1099,6 +1099,7 @@ where reserved_slots, global_minimum_delegation_amount, global_maximum_delegation_amount, + minimum_delegation_rate, ) .map_err(Self::reverter)?; @@ -1325,8 +1326,9 @@ where let reservations = Self::get_named_argument(runtime_args, auction::ARG_RESERVATIONS)?; + let minimum_delegation_rate = self.get_minimum_delegation_rate()?; runtime - .add_reservations(reservations) + .add_reservations(reservations, minimum_delegation_rate) .map_err(Self::reverter)?; CLValue::from_t(()).map_err(Self::reverter) @@ -4665,6 +4667,31 @@ where )?; Ok(Ok(())) } + + fn get_minimum_delegation_rate(&self) -> Result { + let auction_contract_hash = self.context.get_system_contract(AUCTION)?; + let auction_named_keys = self + .context + .state() + .borrow_mut() + .get_named_keys(EntityAddr::System(auction_contract_hash.value()))?; + let minimum_delegation_rate_key = + auction_named_keys.get(MINIMUM_DELEGATION_RATE_KEY).ok_or( + ExecError::NamedKeyNotFound(MINIMUM_DELEGATION_RATE_KEY.to_string()), + )?; + let stored_value = self + .context + .state() + .borrow_mut() + .read(minimum_delegation_rate_key)? + .ok_or(ExecError::KeyNotFound(*minimum_delegation_rate_key))?; + if let StoredValue::CLValue(cl_value) = stored_value { + let minimum_delegation_rate: u8 = cl_value.into_t().map_err(ExecError::CLValue)?; + Ok(minimum_delegation_rate) + } else { + Err(ExecError::UnexpectedStoredValueVariant) + } + } } #[cfg(feature = "test-support")] diff --git a/execution_engine_testing/test_support/src/chainspec_config.rs b/execution_engine_testing/test_support/src/chainspec_config.rs index b097835c27..ba46eb3305 100644 --- a/execution_engine_testing/test_support/src/chainspec_config.rs +++ b/execution_engine_testing/test_support/src/chainspec_config.rs @@ -131,6 +131,7 @@ impl ChainspecConfig { locked_funds_period, unbonding_delay, round_seigniorage_rate, + minimum_delegation_rate, .. } = core_config; @@ -145,6 +146,7 @@ impl ChainspecConfig { .with_unbonding_delay(*unbonding_delay) .with_genesis_timestamp_millis(DEFAULT_GENESIS_TIMESTAMP_MILLIS) .with_storage_costs(*storage_costs) + .with_minimum_delegation_rate(*minimum_delegation_rate) .build(); Ok(GenesisRequest::new( @@ -167,6 +169,12 @@ impl ChainspecConfig { ) } + /// Sets the minimum delegation rate config option. + pub fn with_minimum_delegation_rate(mut self, minimum_delegation_rate: u8) -> Self { + self.core_config.minimum_delegation_rate = minimum_delegation_rate; + self + } + /// Sets the vesting schedule period millis config option. pub fn with_max_associated_keys(&mut self, value: u32) -> &mut Self { self.core_config.max_associated_keys = value; @@ -311,6 +319,7 @@ impl TryFrom for GenesisConfig { .with_genesis_timestamp_millis(DEFAULT_GENESIS_TIMESTAMP_MILLIS) .with_storage_costs(chainspec_config.storage_costs) .with_enable_addressable_entity(chainspec_config.core_config.enable_addressable_entity) + .with_minimum_delegation_rate(chainspec_config.core_config.minimum_delegation_rate) .build()) } } diff --git a/execution_engine_testing/test_support/src/genesis_config_builder.rs b/execution_engine_testing/test_support/src/genesis_config_builder.rs index 39afa47e5f..5ec1f3e0d1 100644 --- a/execution_engine_testing/test_support/src/genesis_config_builder.rs +++ b/execution_engine_testing/test_support/src/genesis_config_builder.rs @@ -1,7 +1,8 @@ //! A builder for an [`GenesisConfig`]. use casper_execution_engine::engine_state::engine_config::DEFAULT_ENABLE_ENTITY; use casper_types::{ - GenesisAccount, GenesisConfig, HoldBalanceHandling, StorageCosts, SystemConfig, WasmConfig, + system::auction::DelegationRate, GenesisAccount, GenesisConfig, HoldBalanceHandling, + StorageCosts, SystemConfig, WasmConfig, }; use num_rational::Ratio; @@ -31,6 +32,7 @@ pub struct GenesisConfigBuilder { enable_addressable_entity: Option, rewards_ratio: Option>, storage_costs: Option, + minimum_delegation_rate: DelegationRate, } impl GenesisConfigBuilder { @@ -111,6 +113,12 @@ impl GenesisConfigBuilder { self } + /// Sets the minimum delegation rate config option. + pub fn with_minimum_delegation_rate(mut self, minimum_delegation_rate: DelegationRate) -> Self { + self.minimum_delegation_rate = minimum_delegation_rate; + self + } + /// Builds a new [`GenesisConfig`] object. pub fn build(self) -> GenesisConfig { GenesisConfig::new( @@ -134,6 +142,7 @@ impl GenesisConfigBuilder { .unwrap_or(DEFAULT_ENABLE_ENTITY), self.rewards_ratio, self.storage_costs.unwrap_or_default(), + self.minimum_delegation_rate, ) } } diff --git a/execution_engine_testing/test_support/src/upgrade_request_builder.rs b/execution_engine_testing/test_support/src/upgrade_request_builder.rs index 388952897e..e51d652b61 100644 --- a/execution_engine_testing/test_support/src/upgrade_request_builder.rs +++ b/execution_engine_testing/test_support/src/upgrade_request_builder.rs @@ -3,8 +3,8 @@ use std::collections::BTreeMap; use num_rational::Ratio; use casper_types::{ - ChainspecRegistry, Digest, EraId, FeeHandling, HoldBalanceHandling, Key, ProtocolUpgradeConfig, - ProtocolVersion, RewardsHandling, StoredValue, + system::auction::DelegationRate, ChainspecRegistry, Digest, EraId, FeeHandling, + HoldBalanceHandling, Key, ProtocolUpgradeConfig, ProtocolVersion, RewardsHandling, StoredValue, }; /// Builds an `UpgradeConfig`. @@ -28,6 +28,7 @@ pub struct UpgradeRequestBuilder { minimum_delegation_amount: u64, enable_addressable_entity: bool, rewards_handling: RewardsHandling, + new_minimum_delegation_rate: Option, } impl UpgradeRequestBuilder { @@ -156,6 +157,15 @@ impl UpgradeRequestBuilder { self } + /// Sets the minimum delegation rate for validator bids and reservations. + pub fn with_new_minimum_delegation_rate( + mut self, + new_minimum_delegation_rate: DelegationRate, + ) -> Self { + self.new_minimum_delegation_rate = Some(new_minimum_delegation_rate); + self + } + /// Consumes the `UpgradeRequestBuilder` and returns an [`ProtocolUpgradeConfig`]. pub fn build(self) -> ProtocolUpgradeConfig { ProtocolUpgradeConfig::new( @@ -178,6 +188,7 @@ impl UpgradeRequestBuilder { self.minimum_delegation_amount, self.enable_addressable_entity, self.rewards_handling, + self.new_minimum_delegation_rate, ) } } @@ -204,6 +215,7 @@ impl Default for UpgradeRequestBuilder { minimum_delegation_amount: 0, enable_addressable_entity: false, rewards_handling: RewardsHandling::Standard, + new_minimum_delegation_rate: None, } } } diff --git a/execution_engine_testing/tests/src/test/contract_api/auction.rs b/execution_engine_testing/tests/src/test/contract_api/auction.rs new file mode 100644 index 0000000000..f305d23fc2 --- /dev/null +++ b/execution_engine_testing/tests/src/test/contract_api/auction.rs @@ -0,0 +1,55 @@ +use casper_engine_test_support::{ + ChainspecConfig, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNTS, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_PUBLIC_KEY, DEFAULT_PROTOCOL_VERSION, +}; +use casper_execution_engine::{engine_state::Error, execution::ExecError}; +use casper_types::{ + runtime_args, + system::auction::{ARG_AMOUNT, ARG_DELEGATION_RATE, ARG_PUBLIC_KEY}, + ApiError, U512, +}; +use once_cell::sync::Lazy; +use std::path::PathBuf; + +const ADD_BIDS_WASM: &str = "auction_bids.wasm"; +const ARG_ENTRY_POINT: &str = "entry_point"; +/// The name of the chainspec file on disk. +pub const CHAINSPEC_NAME: &str = "chainspec.toml"; +pub static LOCAL_PATH: Lazy = + Lazy::new(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../resources/local/")); + +#[ignore] +#[test] +fn add_auction_should_fail_when_delegation_rate_not_met() { + let path = LOCAL_PATH.join(CHAINSPEC_NAME); + let mut chainspec = + ChainspecConfig::from_chainspec_path(path).expect("must build chainspec configuration"); + chainspec = chainspec.with_minimum_delegation_rate(20); + let mut builder = LmdbWasmTestBuilder::new_temporary_with_config(chainspec.clone()); + let genesis_request = chainspec + .create_genesis_request(DEFAULT_ACCOUNTS.clone(), DEFAULT_PROTOCOL_VERSION) + .unwrap(); + builder.run_genesis(genesis_request); + + let exec_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + ADD_BIDS_WASM, + runtime_args! { + ARG_ENTRY_POINT => "add_bid", + ARG_PUBLIC_KEY => DEFAULT_ACCOUNT_PUBLIC_KEY.clone(), + ARG_AMOUNT => U512::from(10_000_000_000_000u64), + ARG_DELEGATION_RATE => 19u8, + }, + ) + .build(); + + let commit = builder.exec(exec_request).commit(); + commit.expect_failure(); + let last_exec_result = commit + .get_last_exec_result() + .expect("Expected to be called after exec()"); + assert!(matches!( + last_exec_result.error().cloned(), + Some(Error::Exec(ExecError::Revert(ApiError::AuctionError(64)))) + )); +} diff --git a/execution_engine_testing/tests/src/test/contract_api/mod.rs b/execution_engine_testing/tests/src/test/contract_api/mod.rs index 2db086c25c..45af77f278 100644 --- a/execution_engine_testing/tests/src/test/contract_api/mod.rs +++ b/execution_engine_testing/tests/src/test/contract_api/mod.rs @@ -1,5 +1,6 @@ mod account; mod add_contract_version; +mod auction; mod create_purse; mod dictionary; mod generic_hash; diff --git a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs index 2fc54255ab..8c6f948825 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs @@ -18,8 +18,9 @@ use casper_types::{ auction::{ DelegatorKind, SeigniorageRecipientsSnapshotV1, SeigniorageRecipientsSnapshotV2, AUCTION_DELAY_KEY, DEFAULT_SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION, - LOCKED_FUNDS_PERIOD_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, - SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY, UNBONDING_DELAY_KEY, VALIDATOR_SLOTS_KEY, + LOCKED_FUNDS_PERIOD_KEY, MINIMUM_DELEGATION_RATE_KEY, + SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY, + UNBONDING_DELAY_KEY, VALIDATOR_SLOTS_KEY, }, mint::ROUND_SEIGNIORAGE_RATE_KEY, }, @@ -889,3 +890,67 @@ fn should_migrate_seigniorage_snapshot_to_new_version() { } } } + +#[test] +fn should_store_and_upgrade_minimum_delegation_rate_named_key() { + const UPGRADED_MINIMUM_DELEGATION_RATE: u8 = 20; + + let mut builder = LmdbWasmTestBuilder::default(); + builder.run_genesis(LOCAL_GENESIS_REQUEST.clone()); + + let auction_contract_hash = builder.get_auction_contract_hash(); + let auction_named_keys = + builder.get_named_keys(EntityAddr::System(auction_contract_hash.value())); + let minimum_delegation_rate_key = *auction_named_keys + .get(MINIMUM_DELEGATION_RATE_KEY) + .expect("minimum delegation rate key should exist at genesis"); + + let minimum_delegation_rate: u8 = builder + .query(None, minimum_delegation_rate_key, &[]) + .expect("should have minimum delegation rate") + .as_cl_value() + .expect("minimum delegation rate should be a CLValue") + .clone() + .into_t() + .expect("minimum delegation rate should be u8"); + + assert_eq!( + minimum_delegation_rate, 0, + "genesis should have set minimum delegation rate to 0!" + ); + + let sem_ver = PROTOCOL_VERSION.value(); + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major, sem_ver.minor, sem_ver.patch + 1); + + let mut upgrade_request = UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .with_new_minimum_delegation_rate(UPGRADED_MINIMUM_DELEGATION_RATE) + .build(); + + builder + .upgrade(&mut upgrade_request) + .expect_upgrade_success(); + + let upgraded_auction_contract_hash = builder.get_auction_contract_hash(); + let upgraded_named_keys = + builder.get_named_keys(EntityAddr::System(upgraded_auction_contract_hash.value())); + let upgraded_minimum_delegation_rate_key = *upgraded_named_keys + .get(MINIMUM_DELEGATION_RATE_KEY) + .expect("minimum delegation rate key should exist after upgrade"); + let upgraded_minimum_delegation_rate: u8 = builder + .query(None, upgraded_minimum_delegation_rate_key, &[]) + .expect("should have upgraded minimum delegation rate") + .as_cl_value() + .expect("minimum delegation rate should be a CLValue") + .clone() + .into_t() + .expect("minimum delegation rate should be u8"); + + assert_eq!( + upgraded_minimum_delegation_rate, + UPGRADED_MINIMUM_DELEGATION_RATE + ); +} diff --git a/execution_engine_testing/tests/src/test/upgrade.rs b/execution_engine_testing/tests/src/test/upgrade.rs index 2c179087a2..ffe5b1c5b6 100644 --- a/execution_engine_testing/tests/src/test/upgrade.rs +++ b/execution_engine_testing/tests/src/test/upgrade.rs @@ -17,10 +17,10 @@ use casper_types::{ bytesrepr::{Bytes, FromBytes}, contracts::ContractPackageHash, runtime_args, - system::mint::MINT_SUSTAIN_PURSE_KEY, - AccessRights, AddressableEntityHash, CLValue, EntityVersion, EraId, HoldBalanceHandling, Key, - PackageHash, ProtocolVersion, RewardsHandling, RuntimeArgs, StoredValue, Timestamp, URef, - ENTITY_INITIAL_VERSION, REWARDS_HANDLING_RATIO_TAG, + system::{auction::MINIMUM_DELEGATION_RATE_KEY, mint::MINT_SUSTAIN_PURSE_KEY}, + AccessRights, AddressableEntityHash, CLValue, EntityAddr, EntityVersion, EraId, + HoldBalanceHandling, Key, PackageHash, ProtocolVersion, RewardsHandling, RuntimeArgs, + StoredValue, Timestamp, URef, ENTITY_INITIAL_VERSION, REWARDS_HANDLING_RATIO_TAG, }; const DO_NOTHING_STORED_CONTRACT_NAME: &str = "do_nothing_stored"; @@ -1227,6 +1227,74 @@ fn should_correctly_retain_disabled_contract_version() { builder.exec(exec_request).expect_failure(); } +#[ignore] +#[test] +fn should_correctly_attach_minimum_delegation_rate_on_upgrade() { + const DISABLED_VERSIONS_FIX: &str = "disabled_versions"; + + let (mut builder, lmdb_fixture_state, _temp_dir) = + lmdb_fixture::builder_from_global_state_fixture(DISABLED_VERSIONS_FIX); + + let previous_protocol_version = lmdb_fixture_state.genesis_protocol_version(); + + let new_protocol_version = + ProtocolVersion::from_parts(previous_protocol_version.value().major + 1, 0, 0); + + let activation_point = EraId::new(0u64); + + let auction_contract_hash = builder.get_auction_contract_hash(); + let auction_named_keys = + builder.get_named_keys(EntityAddr::System(auction_contract_hash.value())); + let minimum_delegation_rate_key = auction_named_keys.get(MINIMUM_DELEGATION_RATE_KEY); + // The stored lmdb state shouldn't have this key - it's an invariant of this test. + assert!(minimum_delegation_rate_key.is_none()); + + let mut upgrade_request = UpgradeRequestBuilder::new() + .with_current_protocol_version(previous_protocol_version) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(activation_point) + .with_new_minimum_delegation_rate(30) + .build(); + + builder + .with_block_time(Timestamp::now().into()) + .upgrade_using_scratch(&mut upgrade_request) + .expect_upgrade_success(); + + let exec_request = { + let contract_name = format!("{}.wasm", "do_nothing_stored_upgrader"); + ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + &contract_name, + RuntimeArgs::default(), + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + + let auction_contract_hash = builder.get_auction_contract_hash(); + let auction_named_keys = + builder.get_named_keys(EntityAddr::System(auction_contract_hash.value())); + let minimum_delegation_rate_key = *auction_named_keys + .get(MINIMUM_DELEGATION_RATE_KEY) + .expect("minimum delegation rate key should exist at genesis"); + + let minimum_delegation_rate: u8 = builder + .query(None, minimum_delegation_rate_key, &[]) + .expect("should have minimum delegation rate") + .as_cl_value() + .expect("minimum delegation rate should be a CLValue") + .clone() + .into_t() + .expect("minimum delegation rate should be u8"); + + assert_eq!( + minimum_delegation_rate, 30, + "this upgrade should have set minimum delegation rate to 30!" + ); +} + fn setup_state_for_version_tests( should_trap_on_ambiguous_entity_version: bool, ) -> (LmdbWasmTestBuilder, ContractPackageHash) { diff --git a/node/Cargo.toml b/node/Cargo.toml index 8d602d2e87..38b2929eb1 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -21,7 +21,7 @@ backtrace = "0.3.50" base16 = "0.2.1" base64 = "0.13.0" bincode = "1" -bytes = "1.0.1" +bytes = "1.11.0" casper-binary-port = { version = "1.1.1", path = "../binary_port" } casper-storage = { version = "4.0.1", path = "../storage" } casper-types = { version = "6.1.0", path = "../types", features = ["datasize", "json-schema", "std-fs-io"] } @@ -59,6 +59,7 @@ rand = "0.8.3" rand_chacha = "0.3.0" regex = "1" rmp-serde = "0.14.4" +rmp = "=0.8.14" schemars = { version = "0.8.16", features = ["preserve_order", "impl_json_schema"] } serde = { version = "1", features = ["derive", "rc"] } serde-big-array = "0.3.0" diff --git a/node/src/reactor/main_reactor/tests/configs_override.rs b/node/src/reactor/main_reactor/tests/configs_override.rs index 45aa27f5f0..a3e243c036 100644 --- a/node/src/reactor/main_reactor/tests/configs_override.rs +++ b/node/src/reactor/main_reactor/tests/configs_override.rs @@ -37,6 +37,7 @@ pub(crate) struct ConfigsOverride { pub gas_hold_balance_handling: Option, pub transaction_v1_override: Option, pub node_config_override: NodeConfigOverride, + pub minimum_delegation_rate: u8, } impl ConfigsOverride { @@ -126,6 +127,11 @@ impl ConfigsOverride { self.transaction_v1_override = Some(transaction_v1config); self } + + pub(crate) fn with_minimum_delegation_rate(mut self, minimum_delegation_rate: u8) -> Self { + self.minimum_delegation_rate = minimum_delegation_rate; + self + } } impl Default for ConfigsOverride { @@ -157,6 +163,7 @@ impl Default for ConfigsOverride { gas_hold_balance_handling: None, transaction_v1_override: None, node_config_override: NodeConfigOverride::default(), + minimum_delegation_rate: 0, } } } diff --git a/node/src/reactor/main_reactor/tests/fixture.rs b/node/src/reactor/main_reactor/tests/fixture.rs index b323999b4c..f5acc5754f 100644 --- a/node/src/reactor/main_reactor/tests/fixture.rs +++ b/node/src/reactor/main_reactor/tests/fixture.rs @@ -175,6 +175,7 @@ impl TestFixture { gas_hold_balance_handling, transaction_v1_override, node_config_override, + minimum_delegation_rate, } = spec_override.unwrap_or_default(); if era_duration != TimeDiff::from_millis(0) { chainspec.core_config.era_duration = era_duration; @@ -189,6 +190,7 @@ impl TestFixture { chainspec.core_config.finality_signature_proportion = finality_signature_proportion; chainspec.core_config.minimum_block_time = minimum_block_time; chainspec.core_config.minimum_era_height = minimum_era_height; + chainspec.core_config.minimum_delegation_rate = minimum_delegation_rate; chainspec.vacancy_config.min_gas_price = min_gas_price; chainspec.vacancy_config.max_gas_price = max_gas_price; chainspec.vacancy_config.upper_threshold = upper_threshold; diff --git a/node/src/reactor/main_reactor/tests/transaction_scenario.rs b/node/src/reactor/main_reactor/tests/transaction_scenario.rs index 451360140b..ab137befdb 100644 --- a/node/src/reactor/main_reactor/tests/transaction_scenario.rs +++ b/node/src/reactor/main_reactor/tests/transaction_scenario.rs @@ -5,8 +5,9 @@ use asertions::{ TransactionFailure, TransactionSuccessful, }; use casper_types::{ - testing::TestRng, FeeHandling, Gas, PricingMode, PublicKey, RefundHandling, TimeDiff, - Transaction, U512, + system::auction::{DelegatorKind, Reservation}, + testing::TestRng, + FeeHandling, Gas, PricingMode, PublicKey, RefundHandling, TimeDiff, Transaction, U512, }; use num_rational::Ratio; use utils::{build_wasm_transction, RunUntilCondition, TestScenarioBuilder}; @@ -318,3 +319,107 @@ async fn should_not_refund_erroneous_wasm_burn_fixed() { )) .await; } + +#[tokio::test] +async fn native_add_bid_should_fail_when_minimum_delegation_rate_not_met() { + let mut rng = TestRng::new(); + let mut test_scenario = TestScenarioBuilder::new() + .with_minimum_delegation_rate(20) + .build(&mut rng) + .await; + let chain_name = test_scenario.chain_name(); + test_scenario.setup().await.unwrap(); + let mut txn: Transaction = Transaction::from( + TransactionV1Builder::new_add_bid( + ALICE_PUBLIC_KEY.clone(), + 19, + 100_000_000_000_u64, + None, + None, + None, + ) + .unwrap() + .with_initiator_addr(PublicKey::from(ALICE_SECRET_KEY.as_ref())) + .with_pricing_mode(PricingMode::PaymentLimited { + payment_amount: 100_000_000_000_u64, + gas_price_tolerance: 1, + standard_payment: true, + }) + .with_chain_name(chain_name.clone()) + .build() + .unwrap(), + ); + txn.sign(&ALICE_SECRET_KEY); + let hash = txn.hash(); + test_scenario.run(vec![txn]).await.unwrap(); + test_scenario + .assert(TransactionFailure::expected_error_message( + hash, + "ApiError::AuctionError(DelegationRateTooSmall) [64576]", + )) + .await; +} + +#[tokio::test] +async fn native_add_bid_should_fail_when_minimum_delegation_rate_not_met_in_reservation() { + let mut rng = TestRng::new(); + let mut test_scenario = TestScenarioBuilder::new() + .with_minimum_delegation_rate(20) + .build(&mut rng) + .await; + let chain_name = test_scenario.chain_name(); + test_scenario.setup().await.unwrap(); + let mut txn: Transaction = Transaction::from( + TransactionV1Builder::new_add_bid( + ALICE_PUBLIC_KEY.clone(), + 20, + 100_000_000_000_u64, + None, + None, + Some(1), + ) + .unwrap() + .with_initiator_addr(PublicKey::from(ALICE_SECRET_KEY.as_ref())) + .with_pricing_mode(PricingMode::PaymentLimited { + payment_amount: 100_000_000_000_u64, + gas_price_tolerance: 1, + standard_payment: true, + }) + .with_chain_name(chain_name.clone()) + .build() + .unwrap(), + ); + txn.sign(&ALICE_SECRET_KEY); + let hash = txn.hash(); + test_scenario.run(vec![txn]).await.unwrap(); + test_scenario.assert(TransactionSuccessful::new(hash)).await; + + // Try reserve a slot with to little delegation rate + let reservations = vec![Reservation::new( + ALICE_PUBLIC_KEY.clone(), + DelegatorKind::PublicKey(BOB_PUBLIC_KEY.clone()), + 19, + )]; + let mut txn: Transaction = Transaction::from( + TransactionV1Builder::new_reserve_slot(reservations) + .unwrap() + .with_initiator_addr(PublicKey::from(ALICE_SECRET_KEY.as_ref())) + .with_pricing_mode(PricingMode::PaymentLimited { + payment_amount: 100_000_000_000_u64, + gas_price_tolerance: 1, + standard_payment: true, + }) + .with_chain_name(chain_name.clone()) + .build() + .unwrap(), + ); + txn.sign(&ALICE_SECRET_KEY); + let hash = txn.hash(); + test_scenario.run(vec![txn]).await.unwrap(); + test_scenario + .assert(TransactionFailure::expected_error_message( + hash, + "Auction error: Delegation rate too small", + )) + .await; +} diff --git a/node/src/reactor/main_reactor/tests/transaction_scenario/asertions.rs b/node/src/reactor/main_reactor/tests/transaction_scenario/asertions.rs index c062f617a2..c9c33a702c 100644 --- a/node/src/reactor/main_reactor/tests/transaction_scenario/asertions.rs +++ b/node/src/reactor/main_reactor/tests/transaction_scenario/asertions.rs @@ -33,11 +33,22 @@ impl Assertion for TransactionSuccessful { pub(crate) struct TransactionFailure { hash: TransactionHash, + expected_error_message: Option, } impl TransactionFailure { pub(crate) fn new(hash: TransactionHash) -> Self { - Self { hash } + Self { + hash, + expected_error_message: None, + } + } + + pub(crate) fn expected_error_message(hash: TransactionHash, error_message: &str) -> Self { + Self { + hash, + expected_error_message: Some(error_message.to_string()), + } } } @@ -49,7 +60,16 @@ impl Assertion for TransactionFailure { let exec_info = current_state.exec_infos.get(&self.hash).unwrap(); assert!(exec_info.execution_result.is_some()); let result = exec_info.execution_result.as_ref().unwrap(); - assert!(!exec_result_is_success(result)); + let error_msg = match result { + casper_types::execution::ExecutionResult::V1(_) => todo!(), + casper_types::execution::ExecutionResult::V2(execution_result_v2) => { + execution_result_v2.error_message.clone() + } + }; + assert!(error_msg.is_some()); + if let Some(msg) = &self.expected_error_message { + assert_eq!(error_msg.unwrap(), msg.to_string()); + } } } diff --git a/node/src/reactor/main_reactor/tests/transaction_scenario/utils.rs b/node/src/reactor/main_reactor/tests/transaction_scenario/utils.rs index 5854f81efa..2483e19abc 100644 --- a/node/src/reactor/main_reactor/tests/transaction_scenario/utils.rs +++ b/node/src/reactor/main_reactor/tests/transaction_scenario/utils.rs @@ -368,6 +368,7 @@ pub(crate) struct TestScenarioBuilder { maybe_fee_handling: Option, maybe_balance_hold_interval_override: Option, maybe_minimum_era_height: Option, + minimum_delegation_rate: u8, } impl TestScenarioBuilder { @@ -384,6 +385,7 @@ impl TestScenarioBuilder { maybe_fee_handling, maybe_balance_hold_interval_override, maybe_minimum_era_height, + minimum_delegation_rate, } = self; let (secret_keys, stakes) = maybe_stakes_setup.unwrap_or({ /* Node 0 is effectively guaranteed to be the proposer. */ @@ -431,6 +433,7 @@ impl TestScenarioBuilder { } else { config }; + let config = config.with_minimum_delegation_rate(minimum_delegation_rate); let child_rng = rng.create_child(); let fixture = TestFixture::new_with_keys(child_rng, secret_keys, stakes, Some(config)).await; @@ -468,6 +471,11 @@ impl TestScenarioBuilder { self.maybe_minimum_era_height = Some(minimum_era_height); self } + + pub(crate) fn with_minimum_delegation_rate(mut self, minimum_delegation_rate: u8) -> Self { + self.minimum_delegation_rate = minimum_delegation_rate; + self + } } pub(super) fn build_wasm_transction( diff --git a/node/src/types/transaction/transaction_v1_builder.rs b/node/src/types/transaction/transaction_v1_builder.rs index 62412e1ce7..99caa7cd45 100644 --- a/node/src/types/transaction/transaction_v1_builder.rs +++ b/node/src/types/transaction/transaction_v1_builder.rs @@ -10,9 +10,9 @@ use casper_types::{ }; #[cfg(test)] use casper_types::{ - contracts::ProtocolVersionMajor, testing::TestRng, AddressableEntityHash, Approval, - CLValueError, EntityVersion, PackageHash, PublicKey, TransactionConfig, - TransactionInvocationTarget, TransferTarget, URef, U512, + contracts::ProtocolVersionMajor, system::auction::Reservation, testing::TestRng, + AddressableEntityHash, Approval, CLValueError, EntityVersion, PackageHash, PublicKey, + TransactionConfig, TransactionInvocationTarget, TransferTarget, URef, U512, }; use core::marker::PhantomData; #[cfg(test)] @@ -204,7 +204,21 @@ impl<'a> TransactionV1Builder<'a> { Ok(builder) } - /// Returns a new `TransactionV1Builder` suitable for building a native add_bid transaction. + /// Returns a new `TransactionV1Builder` suitable for building a native reserve slot + /// transaction. + #[cfg(test)] + pub(crate) fn new_reserve_slot(reservations: Vec) -> Result { + let args = arg_handling::new_add_reservations_args(reservations)?; + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(args); + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::AddReservations; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) + } + + /// Returns a new `TransactionV1Builder` suitable for building a native add_bid + /// transaction. #[cfg(test)] pub(crate) fn new_add_bid>( public_key: PublicKey, diff --git a/resources/integration-test/chainspec.toml b/resources/integration-test/chainspec.toml index 4b8db8f32f..950a7a4509 100644 --- a/resources/integration-test/chainspec.toml +++ b/resources/integration-test/chainspec.toml @@ -85,6 +85,8 @@ simultaneous_peer_requests = 5 consensus_protocol = 'Zug' # The maximum amount of delegators per validator. max_delegators_per_validator = 1200 +# Minimum delegation rate validators can specify (0-100). +minimum_delegation_rate = 0 # The split in finality signature rewards between block producer and participating signers. finders_fee = [1, 5] # The proportion of baseline rewards going to reward finality signatures specifically. diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index 25de4dd6b0..b9b34c1328 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -79,6 +79,8 @@ simultaneous_peer_requests = 5 consensus_protocol = 'Zug' # The maximum amount of delegators per validator. max_delegators_per_validator = 1200 +# Minimum delegation rate validators can specify (0-100). +minimum_delegation_rate = 0 # The split in finality signature rewards between block producer and participating signers. finders_fee = [1, 5] # The proportion of baseline rewards going to reward finality signatures specifically. diff --git a/resources/mainnet/chainspec.toml b/resources/mainnet/chainspec.toml index 82f58aaf7c..c3e4ef5197 100644 --- a/resources/mainnet/chainspec.toml +++ b/resources/mainnet/chainspec.toml @@ -85,6 +85,8 @@ simultaneous_peer_requests = 5 consensus_protocol = 'Zug' # The maximum amount of delegators per validator. max_delegators_per_validator = 1200 +# Minimum delegation rate validators can specify (0-100). +minimum_delegation_rate = 0 # The split in finality signature rewards between block producer and participating signers. finders_fee = [1, 5] # The proportion of baseline rewards going to reward finality signatures specifically. diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index 5f8694cfe7..58d0008057 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -85,6 +85,8 @@ simultaneous_peer_requests = 5 consensus_protocol = 'Zug' # The maximum amount of delegators per validator. max_delegators_per_validator = 1200 +# Minimum delegation rate validators can specify (0-100). +minimum_delegation_rate = 0 # The split in finality signature rewards between block producer and participating signers. finders_fee = [1, 5] # The proportion of baseline rewards going to reward finality signatures specifically. diff --git a/resources/testnet/chainspec.toml b/resources/testnet/chainspec.toml index ec9620207d..052a3178e9 100644 --- a/resources/testnet/chainspec.toml +++ b/resources/testnet/chainspec.toml @@ -85,6 +85,8 @@ simultaneous_peer_requests = 5 consensus_protocol = 'Zug' # The maximum amount of delegators per validator. max_delegators_per_validator = 1200 +# Minimum delegation rate validators can specify (0-100). +minimum_delegation_rate = 0 # The split in finality signature rewards between block producer and participating signers. finders_fee = [1, 5] # The proportion of baseline rewards going to reward finality signatures specifically. diff --git a/smart_contracts/contracts/test/auction-bids/src/main.rs b/smart_contracts/contracts/test/auction-bids/src/main.rs index 087425f661..1fb156ca8f 100644 --- a/smart_contracts/contracts/test/auction-bids/src/main.rs +++ b/smart_contracts/contracts/test/auction-bids/src/main.rs @@ -10,8 +10,9 @@ use casper_contract::contract_api::{runtime, system}; use casper_types::{ runtime_args, system::auction::{ - ARG_DELEGATOR, ARG_ERA_END_TIMESTAMP_MILLIS, ARG_VALIDATOR, METHOD_DELEGATE, - METHOD_DISTRIBUTE, METHOD_RUN_AUCTION, METHOD_UNDELEGATE, + ARG_DELEGATION_RATE, ARG_DELEGATOR, ARG_ERA_END_TIMESTAMP_MILLIS, ARG_PUBLIC_KEY, + ARG_VALIDATOR, METHOD_ADD_BID, METHOD_DELEGATE, METHOD_DISTRIBUTE, METHOD_RUN_AUCTION, + METHOD_UNDELEGATE, }, ApiError, PublicKey, U512, }; @@ -21,6 +22,7 @@ const ARG_AMOUNT: &str = "amount"; const ARG_DELEGATE: &str = "delegate"; const ARG_UNDELEGATE: &str = "undelegate"; const ARG_RUN_AUCTION: &str = "run_auction"; +const ARG_ADD_BID: &str = "add_bid"; #[repr(u16)] enum Error { @@ -40,6 +42,9 @@ pub extern "C" fn call() { } ARG_RUN_AUCTION => run_auction(), METHOD_DISTRIBUTE => distribute(), + ARG_ADD_BID => { + add_bid(); + } _ => runtime::revert(ApiError::User(Error::UnknownCommand as u16)), }; } @@ -73,6 +78,21 @@ fn undelegate() -> U512 { runtime::call_contract(auction, METHOD_UNDELEGATE, args) } +fn add_bid() -> U512 { + let auction = system::get_auction(); + let validator: PublicKey = runtime::get_named_arg(ARG_PUBLIC_KEY); + let delegation_rate: u8 = runtime::get_named_arg(ARG_DELEGATION_RATE); + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + + let args = runtime_args! { + ARG_AMOUNT => amount, + ARG_PUBLIC_KEY => validator, + ARG_DELEGATION_RATE => delegation_rate, + }; + + runtime::call_contract(auction, METHOD_ADD_BID, args) +} + fn run_auction() { let auction = system::get_auction(); let era_end_timestamp_millis: u64 = runtime::get_named_arg(ARG_ERA_END_TIMESTAMP_MILLIS); diff --git a/storage/src/global_state/state/mod.rs b/storage/src/global_state/state/mod.rs index 9c280f3b1a..910b100037 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -1271,20 +1271,25 @@ pub trait StateProvider: Send + Sync + Sized { minimum_bid_amount, reserved_slots, } => runtime - .add_bid( - public_key, - delegation_rate, - amount, - minimum_delegation_amount, - maximum_delegation_amount, - minimum_bid_amount, - max_delegators_per_validator, - reserved_slots, - global_minimum_delegation_limit, - global_maximum_delegation_limit, - ) - .map(AuctionMethodRet::UpdatedAmount) - .map_err(TrackingCopyError::Api), + .get_minimum_delegation_rate() + .and_then(|minimum_delegation_rate| { + runtime + .add_bid( + public_key, + delegation_rate, + amount, + minimum_delegation_amount, + maximum_delegation_amount, + minimum_bid_amount, + max_delegators_per_validator, + reserved_slots, + global_minimum_delegation_limit, + global_maximum_delegation_limit, + minimum_delegation_rate, + ) + .map(AuctionMethodRet::UpdatedAmount) + .map_err(TrackingCopyError::Api) + }), AuctionMethod::WithdrawBid { public_key, amount, @@ -1335,10 +1340,14 @@ pub trait StateProvider: Send + Sync + Sized { TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) }), AuctionMethod::AddReservations { reservations } => runtime - .add_reservations(reservations) - .map(|_| AuctionMethodRet::Unit) - .map_err(|auc_err| { - TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) + .get_minimum_delegation_rate() + .and_then(|minimum_delegation_rate| { + runtime + .add_reservations(reservations, minimum_delegation_rate) + .map(|_| AuctionMethodRet::Unit) + .map_err(|auc_err| { + TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) + }) }), AuctionMethod::CancelReservations { validator, diff --git a/storage/src/system/auction.rs b/storage/src/system/auction.rs index ad9bf4ecaf..d8dd519ecf 100644 --- a/storage/src/system/auction.rs +++ b/storage/src/system/auction.rs @@ -80,6 +80,7 @@ pub trait Auction: reserved_slots: u32, global_minimum_delegation_amount: u64, global_maximum_delegation_amount: u64, + minimum_delegation_rate: u8, ) -> Result { if !self.allow_auction_bids() { // The validator set may be closed on some side chains, @@ -91,6 +92,10 @@ pub trait Auction: return Err(Error::BondTooSmall.into()); } + if delegation_rate < minimum_delegation_rate { + return Err(Error::DelegationRateTooSmall.into()); + } + if delegation_rate > DELEGATION_RATE_DENOMINATOR { return Err(Error::DelegationRateTooLarge.into()); } @@ -392,12 +397,15 @@ pub trait Auction: /// delegator slots is exceeded it returns an error. /// /// If given reservation exists already and the delegation rate was changed it's updated. - fn add_reservations(&mut self, reservations: Vec) -> Result<(), Error> { + fn add_reservations( + &mut self, + reservations: Vec, + minimum_delegation_rate: u8, + ) -> Result<(), Error> { if !self.allow_auction_bids() { // The auction process can be disabled on a given network. return Err(Error::AuctionBidsDisabled); } - for reservation in reservations { if !self .is_allowed_session_caller(&AccountHash::from(reservation.validator_public_key())) @@ -405,7 +413,7 @@ pub trait Auction: return Err(Error::InvalidContext); } - detail::handle_add_reservation(self, reservation)?; + detail::handle_add_reservation(self, reservation, minimum_delegation_rate)?; } Ok(()) } diff --git a/storage/src/system/auction/detail.rs b/storage/src/system/auction/detail.rs index 19a14099ea..8b29d5ba00 100644 --- a/storage/src/system/auction/detail.rs +++ b/storage/src/system/auction/detail.rs @@ -916,10 +916,25 @@ where /// reservation. For a new reservation a bid record will be created to track the reservation, /// otherwise the existing tracking record will be updated. #[allow(clippy::too_many_arguments)] -pub fn handle_add_reservation

(provider: &mut P, reservation: Reservation) -> Result<(), Error> +pub fn handle_add_reservation

( + provider: &mut P, + reservation: Reservation, + minimum_delegation_rate: u8, +) -> Result<(), Error> where P: StorageProvider + MintProvider + RuntimeProvider, { + let delegation_rate = *reservation.delegation_rate(); + + // validate specified delegation rate + if reservation.delegation_rate() > &DELEGATION_RATE_DENOMINATOR { + return Err(Error::DelegationRateTooLarge); + } + + if delegation_rate < minimum_delegation_rate { + return Err(Error::DelegationRateTooSmall); + } + // is there such a validator? let validator_bid_addr = BidAddr::from(reservation.validator_public_key().clone()); let bid = read_validator_bid(provider, &validator_bid_addr.into())?; @@ -949,11 +964,6 @@ where } }; - // validate specified delegation rate - if reservation.delegation_rate() > &DELEGATION_RATE_DENOMINATOR { - return Err(Error::DelegationRateTooLarge); - } - provider.write_bid( reservation_bid_key, BidKind::Reservation(Box::new(reservation)), diff --git a/storage/src/system/genesis/account_contract_installer.rs b/storage/src/system/genesis/account_contract_installer.rs index eb50c1c4a8..a470a2626c 100644 --- a/storage/src/system/genesis/account_contract_installer.rs +++ b/storage/src/system/genesis/account_contract_installer.rs @@ -29,23 +29,22 @@ use casper_types::{ }, execution::Effects, system::{ - auction, auction::{ - BidAddr, BidKind, Delegator, DelegatorBid, DelegatorKind, SeigniorageRecipient, - SeigniorageRecipientV2, SeigniorageRecipients, SeigniorageRecipientsSnapshot, - SeigniorageRecipientsSnapshotV2, SeigniorageRecipientsV2, Staking, ValidatorBid, - AUCTION_DELAY_KEY, DEFAULT_SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION, - DELEGATION_RATE_DENOMINATOR, ERA_END_TIMESTAMP_MILLIS_KEY, ERA_ID_KEY, - INITIAL_ERA_END_TIMESTAMP_MILLIS, INITIAL_ERA_ID, LOCKED_FUNDS_PERIOD_KEY, + self, BidAddr, BidKind, DelegationRate, Delegator, DelegatorBid, DelegatorKind, + SeigniorageRecipient, SeigniorageRecipientV2, SeigniorageRecipients, + SeigniorageRecipientsSnapshot, SeigniorageRecipientsSnapshotV2, + SeigniorageRecipientsV2, Staking, ValidatorBid, AUCTION_DELAY_KEY, + DEFAULT_SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION, DELEGATION_RATE_DENOMINATOR, + ERA_END_TIMESTAMP_MILLIS_KEY, ERA_ID_KEY, INITIAL_ERA_END_TIMESTAMP_MILLIS, + INITIAL_ERA_ID, LOCKED_FUNDS_PERIOD_KEY, MINIMUM_DELEGATION_RATE_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY, UNBONDING_DELAY_KEY, VALIDATOR_SLOTS_KEY, }, - handle_payment, - handle_payment::ACCUMULATION_PURSE_KEY, - mint, + handle_payment::{self, ACCUMULATION_PURSE_KEY}, mint::{ - ARG_ROUND_SEIGNIORAGE_RATE, MINT_GAS_HOLD_HANDLING_KEY, MINT_GAS_HOLD_INTERVAL_KEY, - MINT_SUSTAIN_PURSE_KEY, ROUND_SEIGNIORAGE_RATE_KEY, TOTAL_SUPPLY_KEY, + self, ARG_ROUND_SEIGNIORAGE_RATE, MINT_GAS_HOLD_HANDLING_KEY, + MINT_GAS_HOLD_INTERVAL_KEY, MINT_SUSTAIN_PURSE_KEY, ROUND_SEIGNIORAGE_RATE_KEY, + TOTAL_SUPPLY_KEY, }, standard_payment, SystemEntityType, AUCTION, HANDLE_PAYMENT, MINT, STANDARD_PAYMENT, }, @@ -254,9 +253,25 @@ where let locked_funds_period_millis = self.config.locked_funds_period_millis(); let auction_delay: u64 = self.config.auction_delay(); let genesis_timestamp_millis: u64 = self.config.genesis_timestamp_millis(); + let minimum_delegation_rate = self.config.minimum_delegation_rate(); let mut named_keys = NamedKeys::new(); + let cl_value = CLValue::from_t(minimum_delegation_rate) + .map_err(|cl_error| GenesisError::CLValue(cl_error.to_string()))?; + let minimum_delegation_rate_uref = self + .address_generator + .borrow_mut() + .new_uref(AccessRights::READ_ADD_WRITE); + self.tracking_copy.borrow_mut().write( + minimum_delegation_rate_uref.into(), + StoredValue::CLValue(cl_value), + ); + named_keys.insert( + MINIMUM_DELEGATION_RATE_KEY.into(), + minimum_delegation_rate_uref.into(), + ); + let genesis_validators: Vec<_> = self.config.get_bonded_validators().collect(); if (self.config.validator_slots() as usize) < genesis_validators.len() { return Err(GenesisError::InvalidValidatorSlots { diff --git a/storage/src/system/genesis/entity_installer.rs b/storage/src/system/genesis/entity_installer.rs index 38ebf8d0d1..325801f88e 100644 --- a/storage/src/system/genesis/entity_installer.rs +++ b/storage/src/system/genesis/entity_installer.rs @@ -28,8 +28,8 @@ use casper_types::{ AUCTION_DELAY_KEY, DEFAULT_SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION, DELEGATION_RATE_DENOMINATOR, ERA_END_TIMESTAMP_MILLIS_KEY, ERA_ID_KEY, INITIAL_ERA_END_TIMESTAMP_MILLIS, INITIAL_ERA_ID, LOCKED_FUNDS_PERIOD_KEY, - SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY, - UNBONDING_DELAY_KEY, VALIDATOR_SLOTS_KEY, + MINIMUM_DELEGATION_RATE_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, + SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY, UNBONDING_DELAY_KEY, VALIDATOR_SLOTS_KEY, }, handle_payment, handle_payment::ACCUMULATION_PURSE_KEY, @@ -252,9 +252,25 @@ where let locked_funds_period_millis = self.config.locked_funds_period_millis(); let auction_delay: u64 = self.config.auction_delay(); let genesis_timestamp_millis: u64 = self.config.genesis_timestamp_millis(); + let minimum_delegation_rate = self.config.minimum_delegation_rate(); let mut named_keys = NamedKeys::new(); + let minimum_delegation_rate_uref = self + .address_generator + .borrow_mut() + .new_uref(AccessRights::READ_ADD_WRITE); + let cl_value = CLValue::from_t(minimum_delegation_rate) + .map_err(|cl_error| GenesisError::CLValue(cl_error.to_string()))?; + self.tracking_copy.borrow_mut().write( + minimum_delegation_rate_uref.into(), + StoredValue::CLValue(cl_value), + ); + named_keys.insert( + MINIMUM_DELEGATION_RATE_KEY.into(), + minimum_delegation_rate_uref.into(), + ); + let genesis_validators: Vec<_> = self.config.get_bonded_validators().collect(); if (self.config.validator_slots() as usize) < genesis_validators.len() { return Err(GenesisError::InvalidValidatorSlots { diff --git a/storage/src/system/protocol_upgrade.rs b/storage/src/system/protocol_upgrade.rs index 4388481e26..c9594a1776 100644 --- a/storage/src/system/protocol_upgrade.rs +++ b/storage/src/system/protocol_upgrade.rs @@ -22,8 +22,8 @@ use casper_types::{ SeigniorageRecipientsV2, Unbond, UnbondEra, UnbondKind, ValidatorBid, AUCTION_DELAY_KEY, DEFAULT_SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION, ERA_END_TIMESTAMP_MILLIS_KEY, ERA_ID_KEY, LOCKED_FUNDS_PERIOD_KEY, - SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY, - UNBONDING_DELAY_KEY, VALIDATOR_SLOTS_KEY, + MINIMUM_DELEGATION_RATE_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, + SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY, UNBONDING_DELAY_KEY, VALIDATOR_SLOTS_KEY, }, handle_payment::{ACCUMULATION_PURSE_KEY, PAYMENT_PURSE_KEY}, mint::{ @@ -220,6 +220,7 @@ where self.handle_seignorage_snapshot_migration(system_entity_addresses.auction())?; self.handle_total_supply_calc(system_entity_addresses.mint())?; self.handle_rewards_handling(system_entity_addresses.mint())?; + self.handle_minimum_delegation_rate(system_entity_addresses.auction())?; Ok(self.tracking_copy) } @@ -1729,4 +1730,30 @@ where self.tracking_copy.write(*key, value.clone()); } } + + /// Handle setting up minimum_delegation_rate + pub fn handle_minimum_delegation_rate( + &mut self, + auction: HashAddr, + ) -> Result<(), ProtocolUpgradeError> { + let minimum_delegation_rate = self.config.new_minimum_delegation_rate(); + let minimum_delegation_rate = if let Some(minimum_delegation_rate) = minimum_delegation_rate + { + minimum_delegation_rate + } else { + return Ok(()); + }; + let named_keys = self.get_named_keys(auction)?; + let cl_value = CLValue::from_t(minimum_delegation_rate) + .map_err(|cl_error| ProtocolUpgradeError::CLValue(cl_error.to_string()))?; + let stored_value = StoredValue::CLValue(cl_value); + let auction_addr = EntityAddr::System(auction); + self.system_uref( + auction_addr, + MINIMUM_DELEGATION_RATE_KEY, + &named_keys, + stored_value, + )?; + Ok(()) + } } diff --git a/storage/src/system/runtime_native.rs b/storage/src/system/runtime_native.rs index 803418cb62..1589534aac 100644 --- a/storage/src/system/runtime_native.rs +++ b/storage/src/system/runtime_native.rs @@ -4,9 +4,12 @@ use crate::{ AddressGenerator, TrackingCopy, }; use casper_types::{ - account::AccountHash, contracts::NamedKeys, Chainspec, ContextAccessRights, EntityAddr, - FeeHandling, Key, Phase, ProtocolVersion, PublicKey, RefundHandling, RewardsHandling, - RuntimeFootprint, StoredValue, TransactionHash, Transfer, URef, U512, + account::AccountHash, + contracts::NamedKeys, + system::{auction::MINIMUM_DELEGATION_RATE_KEY, AUCTION}, + Chainspec, ContextAccessRights, EntityAddr, FeeHandling, Key, Phase, ProtocolVersion, + PublicKey, RefundHandling, RewardsHandling, RuntimeFootprint, StoredValue, TransactionHash, + Transfer, URef, U512, }; use num_rational::Ratio; use parking_lot::RwLock; @@ -600,4 +603,25 @@ where pub(crate) fn native_transfer_cost(&self) -> u32 { self.config.native_transfer_cost } + + pub(crate) fn get_minimum_delegation_rate(&self) -> Result { + let mut borrow_mut = self.tracking_copy.borrow_mut(); + let key = borrow_mut + .system_contract_named_key(AUCTION, MINIMUM_DELEGATION_RATE_KEY)? + .ok_or(TrackingCopyError::NamedKeyNotFound( + MINIMUM_DELEGATION_RATE_KEY.to_string(), + ))?; + let stored_value = borrow_mut + .read(&key)? + .ok_or(TrackingCopyError::ValueNotFound( + MINIMUM_DELEGATION_RATE_KEY.to_string(), + ))?; + if let StoredValue::CLValue(cl_value) = stored_value { + let minimum_delegation_rate: u8 = + cl_value.into_t().map_err(TrackingCopyError::CLValue)?; + Ok(minimum_delegation_rate) + } else { + Err(TrackingCopyError::UnexpectedStoredValueVariant) + } + } } diff --git a/types/src/chainspec.rs b/types/src/chainspec.rs index 4b25f79c11..f29bcfcdba 100644 --- a/types/src/chainspec.rs +++ b/types/src/chainspec.rs @@ -197,6 +197,7 @@ impl Chainspec { let minimum_delegation_amount = self.core_config.minimum_delegation_amount; let enable_addressable_entity = self.core_config.enable_addressable_entity; let rewards_handling = self.core_config.rewards_handling.clone(); + let minimum_delegation_rate = Some(self.core_config.minimum_delegation_rate); Ok(ProtocolUpgradeConfig::new( pre_state_hash, @@ -218,6 +219,7 @@ impl Chainspec { minimum_delegation_amount, enable_addressable_entity, rewards_handling, + minimum_delegation_rate, )) } diff --git a/types/src/chainspec/core_config.rs b/types/src/chainspec/core_config.rs index 83592196d3..ff21783b05 100644 --- a/types/src/chainspec/core_config.rs +++ b/types/src/chainspec/core_config.rs @@ -193,6 +193,9 @@ pub struct CoreConfig { pub trap_on_ambiguous_entity_version: bool, #[cfg_attr(feature = "datasize", data_size(skip))] pub rewards_handling: RewardsHandling, + /// Minimum delegation rate a validator can specify. + /// Also applies to delegation reservations. + pub minimum_delegation_rate: u8, } impl CoreConfig { @@ -339,6 +342,7 @@ impl CoreConfig { baseline_motes_amount: DEFAULT_BASELINE_MOTES_AMOUNT, trap_on_ambiguous_entity_version: false, rewards_handling: RewardsHandling::Standard, + minimum_delegation_rate: 0, } } } @@ -387,6 +391,7 @@ impl Default for CoreConfig { baseline_motes_amount: DEFAULT_BASELINE_MOTES_AMOUNT, trap_on_ambiguous_entity_version: false, rewards_handling: RewardsHandling::Standard, + minimum_delegation_rate: 0, } } } @@ -437,6 +442,7 @@ impl ToBytes for CoreConfig { buffer.extend(self.baseline_motes_amount.to_bytes()?); buffer.extend(self.trap_on_ambiguous_entity_version.to_bytes()?); buffer.extend(self.rewards_handling.to_bytes()?); + buffer.extend(self.minimum_delegation_rate.to_bytes()?); Ok(buffer) } @@ -483,6 +489,7 @@ impl ToBytes for CoreConfig { + self.baseline_motes_amount.serialized_length() + self.trap_on_ambiguous_entity_version.serialized_length() + self.rewards_handling.serialized_length() + + self.minimum_delegation_rate.serialized_length() } } @@ -529,6 +536,7 @@ impl FromBytes for CoreConfig { let (baseline_motes_amount, remainder) = u64::from_bytes(remainder)?; let (trap_on_ambiguous_entity_version, remainder) = bool::from_bytes(remainder)?; let (rewards_handling, remainder) = RewardsHandling::from_bytes(remainder)?; + let (minimum_delegation_rate, remainder) = u8::from_bytes(remainder)?; let config = CoreConfig { era_duration, minimum_era_height, @@ -570,6 +578,7 @@ impl FromBytes for CoreConfig { baseline_motes_amount, trap_on_ambiguous_entity_version, rewards_handling, + minimum_delegation_rate, }; Ok((config, remainder)) } diff --git a/types/src/chainspec/genesis_config.rs b/types/src/chainspec/genesis_config.rs index 8eb98ed57c..9721ace5dd 100644 --- a/types/src/chainspec/genesis_config.rs +++ b/types/src/chainspec/genesis_config.rs @@ -12,8 +12,9 @@ use rand::{ use serde::{Deserialize, Serialize}; use crate::{ - AdministratorAccount, Chainspec, GenesisAccount, GenesisValidator, HoldBalanceHandling, Motes, - PublicKey, RewardsHandling, SystemConfig, WasmConfig, + system::auction::DelegationRate, AdministratorAccount, Chainspec, GenesisAccount, + GenesisValidator, HoldBalanceHandling, Motes, PublicKey, RewardsHandling, SystemConfig, + WasmConfig, }; use super::StorageCosts; @@ -35,6 +36,7 @@ pub struct GenesisConfig { enable_addressable_entity: bool, rewards_ratio: Option>, storage_costs: StorageCosts, + minimum_delegation_rate: DelegationRate, } impl GenesisConfig { @@ -55,6 +57,7 @@ impl GenesisConfig { enable_addressable_entity: bool, rewards_handling: Option>, storage_costs: StorageCosts, + minimum_delegation_rate: DelegationRate, ) -> GenesisConfig { GenesisConfig { accounts, @@ -71,6 +74,7 @@ impl GenesisConfig { enable_addressable_entity, rewards_ratio: rewards_handling, storage_costs, + minimum_delegation_rate, } } @@ -192,6 +196,10 @@ impl GenesisConfig { pub fn push_rewards_ratio(&mut self, rewards_ratio: Ratio) { self.rewards_ratio = Some(rewards_ratio); } + + pub fn minimum_delegation_rate(&self) -> DelegationRate { + self.minimum_delegation_rate + } } #[cfg(any(feature = "testing", test))] @@ -222,6 +230,7 @@ impl Distribution for Standard { let gas_hold_balance_handling = rng.gen(); let gas_hold_interval_millis = rng.gen(); let storage_costs = rng.gen(); + let minimum_delegation_rate = rng.gen(); GenesisConfig { accounts, @@ -238,6 +247,7 @@ impl Distribution for Standard { enable_addressable_entity: false, rewards_ratio: None, storage_costs, + minimum_delegation_rate, } } } @@ -271,6 +281,7 @@ impl From<&Chainspec> for GenesisConfig { enable_addressable_entity: chainspec.core_config.enable_addressable_entity, rewards_ratio, storage_costs, + minimum_delegation_rate: chainspec.core_config.minimum_delegation_rate, } } } diff --git a/types/src/chainspec/upgrade_config.rs b/types/src/chainspec/upgrade_config.rs index 1e441c073b..465726d0d2 100644 --- a/types/src/chainspec/upgrade_config.rs +++ b/types/src/chainspec/upgrade_config.rs @@ -3,8 +3,8 @@ use serde::Serialize; use std::collections::BTreeMap; use crate::{ - ChainspecRegistry, Digest, EraId, FeeHandling, HoldBalanceHandling, Key, ProtocolVersion, - RewardsHandling, StoredValue, + system::auction::DelegationRate, ChainspecRegistry, Digest, EraId, FeeHandling, + HoldBalanceHandling, Key, ProtocolVersion, RewardsHandling, StoredValue, }; /// Represents the configuration of a protocol upgrade. @@ -29,6 +29,7 @@ pub struct ProtocolUpgradeConfig { minimum_delegation_amount: u64, enable_addressable_entity: bool, rewards_handling: RewardsHandling, + minimum_delegation_rate: Option, } impl ProtocolUpgradeConfig { @@ -54,6 +55,7 @@ impl ProtocolUpgradeConfig { minimum_delegation_amount: u64, enable_addressable_entity: bool, rewards_handling: RewardsHandling, + minimum_delegation_rate: Option, ) -> Self { ProtocolUpgradeConfig { pre_state_hash, @@ -75,6 +77,7 @@ impl ProtocolUpgradeConfig { minimum_delegation_amount, enable_addressable_entity, rewards_handling, + minimum_delegation_rate, } } @@ -175,4 +178,9 @@ impl ProtocolUpgradeConfig { pub fn rewards_handling(&self) -> RewardsHandling { self.rewards_handling.clone() } + + /// Returns minimum_delegation_rate. + pub fn new_minimum_delegation_rate(&self) -> Option { + self.minimum_delegation_rate + } } diff --git a/types/src/crypto/asymmetric_key.rs b/types/src/crypto/asymmetric_key.rs index 3a60de816b..15219c776a 100644 --- a/types/src/crypto/asymmetric_key.rs +++ b/types/src/crypto/asymmetric_key.rs @@ -253,6 +253,7 @@ impl SecretKey { /// DER encodes a key. #[cfg(any(feature = "std", test))] + #[allow(deprecated)] pub fn to_der(&self) -> Result, ErrorExt> { match self { SecretKey::System => Err(Error::System(String::from("to_der")).into()), diff --git a/types/src/system/auction/constants.rs b/types/src/system/auction/constants.rs index ee1cdbe3f1..624dba05ad 100644 --- a/types/src/system/auction/constants.rs +++ b/types/src/system/auction/constants.rs @@ -120,3 +120,5 @@ pub const AUCTION_DELAY_KEY: &str = "auction_delay"; pub const LOCKED_FUNDS_PERIOD_KEY: &str = "locked_funds_period"; /// Unbonding delay expressed in eras. pub const UNBONDING_DELAY_KEY: &str = "unbonding_delay"; +/// Key under which minimum delegation rate is stored in the auction contracts named keys +pub const MINIMUM_DELEGATION_RATE_KEY: &str = "minimum_delegation_rate"; diff --git a/types/src/system/auction/error.rs b/types/src/system/auction/error.rs index 62cb075435..804fe86158 100644 --- a/types/src/system/auction/error.rs +++ b/types/src/system/auction/error.rs @@ -413,6 +413,12 @@ pub enum Error { /// assert_eq!(63, Error::VestingLockout as u8); /// ``` VestingLockout = 63, + /// The minimum delegation rate is not met. + /// ``` + /// # use casper_types::system::auction::Error; + /// assert_eq!(64, Error::DelegationRateTooSmall as u8); + /// ``` + DelegationRateTooSmall = 64, } impl Display for Error { @@ -482,6 +488,7 @@ impl Display for Error { Error::UnexpectedStoredValueVariant => formatter.write_str("Unexpected stored value variant"), Error::RedelegationValidatorNotFound => formatter.write_str("Redelegation validator not found"), Error::VestingLockout => formatter.write_str("Cannot perform attempted action during vesting periods"), + Error::DelegationRateTooSmall => formatter.write_str("Delegation rate too small"), } } } @@ -589,6 +596,7 @@ impl TryFrom for Error { Ok(Error::RedelegationValidatorNotFound) } d if d == Error::VestingLockout as u8 => Ok(Error::VestingLockout), + d if d == Error::DelegationRateTooSmall as u8 => Ok(Error::DelegationRateTooSmall), _ => Err(TryFromU8ForError(())), } } From 38466daa49146a2b6473a50628e86f2c2ff88bb3 Mon Sep 17 00:00:00 2001 From: Jakub Zajkowski Date: Wed, 4 Mar 2026 23:15:53 +0100 Subject: [PATCH 2/2] Fixing failing tests --- storage/src/system/protocol_upgrade.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/storage/src/system/protocol_upgrade.rs b/storage/src/system/protocol_upgrade.rs index c9594a1776..1148b9a720 100644 --- a/storage/src/system/protocol_upgrade.rs +++ b/storage/src/system/protocol_upgrade.rs @@ -1736,13 +1736,7 @@ where &mut self, auction: HashAddr, ) -> Result<(), ProtocolUpgradeError> { - let minimum_delegation_rate = self.config.new_minimum_delegation_rate(); - let minimum_delegation_rate = if let Some(minimum_delegation_rate) = minimum_delegation_rate - { - minimum_delegation_rate - } else { - return Ok(()); - }; + let minimum_delegation_rate = self.config.new_minimum_delegation_rate().unwrap_or(0); let named_keys = self.get_named_keys(auction)?; let cl_value = CLValue::from_t(minimum_delegation_rate) .map_err(|cl_error| ProtocolUpgradeError::CLValue(cl_error.to_string()))?;