From f7283c861b335b2b7a50cc5e5d70a981bdb3ee37 Mon Sep 17 00:00:00 2001 From: edhastings Date: Wed, 16 Jul 2025 13:24:39 -0700 Subject: [PATCH 1/2] Initial port of changes --- .../tests/src/test/explorer/faucet.rs | 2 +- .../tests/src/test/regression/ee_1119.rs | 4 +- .../tests/src/test/regression/ee_598.rs | 8 +- .../src/reactor/main_reactor/tests/auction.rs | 73 +++++++++++++++++-- .../src/reactor/main_reactor/tests/fixture.rs | 22 ++++++ storage/src/system/auction.rs | 17 +++-- 6 files changed, 110 insertions(+), 16 deletions(-) diff --git a/execution_engine_testing/tests/src/test/explorer/faucet.rs b/execution_engine_testing/tests/src/test/explorer/faucet.rs index 905134bf82..73d03b743c 100644 --- a/execution_engine_testing/tests/src/test/explorer/faucet.rs +++ b/execution_engine_testing/tests/src/test/explorer/faucet.rs @@ -663,7 +663,7 @@ fn faucet_costs() { // This test will fail if execution costs vary. The expected costs should not be updated // without understanding why the cost has changed. If the costs do change, it should be // reflected in the "Costs by Entry Point" section of the faucet crate's README.md. - const EXPECTED_FAUCET_INSTALL_COST: u64 = 160_442_504_927; + const EXPECTED_FAUCET_INSTALL_COST: u64 = 160_503_972_212; const EXPECTED_FAUCET_INSTALL_COST_ALT: u64 = 149_230_872_143; const EXPECTED_FAUCET_SET_VARIABLES_COST: u64 = 79_455_975; diff --git a/execution_engine_testing/tests/src/test/regression/ee_1119.rs b/execution_engine_testing/tests/src/test/regression/ee_1119.rs index 93301454f2..90eadb35cf 100644 --- a/execution_engine_testing/tests/src/test/regression/ee_1119.rs +++ b/execution_engine_testing/tests/src/test/regression/ee_1119.rs @@ -145,7 +145,7 @@ fn should_slash_validator_and_their_delegators() { // Other genesis validator withdraws his bid // - let withdraw_bid_request = ExecuteRequestBuilder::standard( + let unbond_request = ExecuteRequestBuilder::standard( *VALIDATOR_1_ADDR, CONTRACT_WITHDRAW_BID, runtime_args! { @@ -155,7 +155,7 @@ fn should_slash_validator_and_their_delegators() { ) .build(); - builder.exec(withdraw_bid_request).expect_success().commit(); + builder.exec(unbond_request).expect_success().commit(); let unbond_purses = builder.get_unbonds(); assert_eq!(unbond_purses.len(), 2); diff --git a/execution_engine_testing/tests/src/test/regression/ee_598.rs b/execution_engine_testing/tests/src/test/regression/ee_598.rs index ae5501d3c3..8ee9d5106b 100644 --- a/execution_engine_testing/tests/src/test/regression/ee_598.rs +++ b/execution_engine_testing/tests/src/test/regression/ee_598.rs @@ -84,6 +84,12 @@ fn should_handle_unbond_for_more_than_stake_as_full_unbond_of_stake_ee_598_regre builder .exec(combined_bond_and_unbond_request) - .expect_success() + .expect_failure() .commit(); + + let err = builder.get_error().expect("should have error"); + assert_eq!( + "ApiError::AuctionError(UnbondTooLarge) [64532]", + err.to_string() + ); } diff --git a/node/src/reactor/main_reactor/tests/auction.rs b/node/src/reactor/main_reactor/tests/auction.rs index c7bdd7ff28..6586e95636 100644 --- a/node/src/reactor/main_reactor/tests/auction.rs +++ b/node/src/reactor/main_reactor/tests/auction.rs @@ -1,15 +1,14 @@ use std::sync::Arc; -use casper_types::{ - execution::TransformKindV2, - system::{auction::BidAddr, AUCTION}, - Deploy, Key, PublicKey, StoredValue, TimeDiff, Timestamp, Transaction, U512, -}; - use crate::reactor::main_reactor::tests::{ configs_override::ConfigsOverride, fixture::TestFixture, initial_stakes::InitialStakes, ERA_ONE, ERA_TWO, ONE_MIN, TEN_SECS, }; +use casper_types::{ + execution::{ExecutionResult, TransformKindV2}, + system::{auction::BidAddr, AUCTION}, + Deploy, Key, PublicKey, StoredValue, TimeDiff, Timestamp, Transaction, U512, +}; #[tokio::test] async fn run_withdraw_bid_network() { @@ -82,6 +81,68 @@ async fn run_withdraw_bid_network() { .await; } +#[tokio::test] +async fn should_error_on_validator_unbond_to_large() { + let alice_stake = 200_000_000_000_u64; + let initial_stakes = InitialStakes::FromVec(vec![alice_stake.into(), 10_000_000_000]); + + let unbonding_delay = 2; + + let mut fixture = TestFixture::new( + initial_stakes, + Some(ConfigsOverride { + unbonding_delay, + ..Default::default() + }), + ) + .await; + let alice_secret_key = Arc::clone(&fixture.node_contexts[0].secret_key); + let alice_public_key = PublicKey::from(&*alice_secret_key); + + // Wait for all nodes to complete block 0. + fixture.run_until_block_height(0, ONE_MIN).await; + + // Ensure our post genesis assumption that Alice has a bid is correct. + fixture.check_bid_existence_at_tip(&alice_public_key, None, true); + + let too_much_stake = alice_stake + 1; + + // Create & sign deploy to withdraw MORE than Alice's full stake. + let mut deploy = Deploy::withdraw_bid( + fixture.chainspec.network_config.name.clone(), + fixture.system_contract_hash(AUCTION), + alice_public_key.clone(), + too_much_stake.into(), + Timestamp::now(), + TimeDiff::from_seconds(60), + ); + deploy.sign(&alice_secret_key); + let txn = Transaction::Deploy(deploy); + let txn_hash = txn.hash(); + + // Inject the transaction and run the network until executed. + fixture.inject_transaction(txn).await; + fixture + .run_until_executed_transaction(&txn_hash, TEN_SECS) + .await; + + let result = fixture.transaction_execution_result(&txn_hash); + + if let ExecutionResult::V2(exec_result) = result { + let msg = exec_result + .error_message + .expect("error message should not be none"); + + assert_eq!( + msg, "ApiError::AuctionError(UnbondTooLarge) [64532]", + "{}", + msg + ); + } else { + panic!("unexpected execution result"); + } +} + #[tokio::test] async fn run_undelegate_bid_network() { let alice_stake = 200_000_000_000_u64; diff --git a/node/src/reactor/main_reactor/tests/fixture.rs b/node/src/reactor/main_reactor/tests/fixture.rs index b3f2992d8d..b323999b4c 100644 --- a/node/src/reactor/main_reactor/tests/fixture.rs +++ b/node/src/reactor/main_reactor/tests/fixture.rs @@ -886,6 +886,28 @@ impl TestFixture { } } + /// Returns the execution results from storage. + /// Panics on error. + #[track_caller] + pub(crate) fn transaction_execution_result( + &self, + txn_hash: &TransactionHash, + ) -> ExecutionResult { + let node_0 = self + .node_contexts + .first() + .expect("should have at least one node") + .id; + self.network + .nodes() + .get(&node_0) + .expect("should have node 0") + .main_reactor() + .storage() + .read_execution_result(txn_hash) + .expect("node 0 should have given execution result") + } + #[inline(always)] pub(crate) fn network_mut(&mut self) -> &mut TestingNetwork> { &mut self.network diff --git a/storage/src/system/auction.rs b/storage/src/system/auction.rs index 88fd9d747e..3eaaf35e91 100644 --- a/storage/src/system/auction.rs +++ b/storage/src/system/auction.rs @@ -226,15 +226,20 @@ pub trait Auction: let mut validator_bid = read_validator_bid(self, &validator_bid_key)?; let staked_amount = validator_bid.staked_amount(); - // An attempt to unbond more than is staked results in unbonding the staked amount. - let unbonding_amount = U512::min(amount, validator_bid.staked_amount()); + if amount > staked_amount { + // An attempt to unbond more than is staked results in an error. + // We've gone back and forth on this behavior. + // * In 1.x it was an error. + // * In 2.0 it was changed to interpret it as "up to amount" and not error, by request. + // * In 2.1 it is restored to the original 1.x behavior, also by request. + return Err(Error::UnbondTooLarge); + } let era_end_timestamp_millis = detail::get_era_end_timestamp_millis(self)?; - let updated_stake = - validator_bid.decrease_stake(unbonding_amount, era_end_timestamp_millis)?; + let updated_stake = validator_bid.decrease_stake(amount, era_end_timestamp_millis)?; debug!( - "withdrawing bid for {validator_bid_addr} reducing {staked_amount} by {unbonding_amount} to {updated_stake}", + "withdrawing bid for {validator_bid_addr} reducing {staked_amount} by {amount} to {updated_stake}", ); // if validator stake is less than minimum_bid_amount, unbond fully and prune validator bid if updated_stake < U512::from(minimum_bid_amount) { @@ -274,7 +279,7 @@ pub trait Auction: public_key.clone(), UnbondKind::Validator(public_key.clone()), // validator is the unbonder *validator_bid.bonding_purse(), - unbonding_amount, + amount, None, )?; self.write_bid(validator_bid_key, BidKind::Validator(validator_bid))?; From 87d469a835f02567d8df9b3c4e10ac8fee9c681d Mon Sep 17 00:00:00 2001 From: Karan Dhareshwar Date: Mon, 26 Jan 2026 07:56:13 -0600 Subject: [PATCH 2/2] add --ignore RUSTSEC-2026-0001 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d7bf67add7..1ed27399f1 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,7 @@ lint-smart-contracts: .PHONY: audit-rs audit-rs: - $(CARGO) audit --ignore RUSTSEC-2024-0437 --ignore RUSTSEC-2025-0022 --ignore RUSTSEC-2025-0055 + $(CARGO) audit --ignore RUSTSEC-2024-0437 --ignore RUSTSEC-2025-0022 --ignore RUSTSEC-2025-0055 --ignore RUSTSEC-2026-0001 .PHONY: audit audit: audit-rs