From 14c042677a6fe9dbff4e5df350f84a9b108baac9 Mon Sep 17 00:00:00 2001 From: Ed Hastings Date: Wed, 18 Dec 2024 18:28:33 -0800 Subject: [PATCH] guardrail --- Cargo.lock | 8 ++ execution_engine/src/runtime/mod.rs | 27 +++++-- execution_engine/src/runtime_context/mod.rs | 14 ++++ .../src/test/deploy/non_standard_payment.rs | 45 ++++++++++- .../test/payment-purse-persist/Cargo.toml | 16 ++++ .../test/payment-purse-persist/src/main.rs | 28 +++++++ storage/src/system/handle_payment.rs | 1 - storage/src/system/protocol_upgrade.rs | 81 ++++++++++++++++++- types/src/system/handle_payment/error.rs | 15 +++- 9 files changed, 219 insertions(+), 16 deletions(-) create mode 100644 smart_contracts/contracts/test/payment-purse-persist/Cargo.toml create mode 100644 smart_contracts/contracts/test/payment-purse-persist/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 124ea9b57a..0fdaf1070e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4176,6 +4176,14 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "payment-purse-persist" +version = "0.1.0" +dependencies = [ + "casper-contract", + "casper-types", +] + [[package]] name = "pem" version = "0.8.3" diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 885e1c344e..f357e1c14d 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -19,7 +19,7 @@ use std::{ use casper_wasm::elements::Module; use casper_wasmi::{MemoryRef, Trap, TrapCode}; -use tracing::{debug, error}; +use tracing::{debug, error, warn}; #[cfg(feature = "test-support")] use casper_wasmi::RuntimeValue; @@ -372,6 +372,15 @@ where ) -> Result<(), Trap> { let name = self.string_from_mem(name_ptr, name_size)?; let key = self.key_from_mem(key_ptr, key_size)?; + + if let Some(payment_purse) = self.context.maybe_payment_purse() { + if Key::URef(payment_purse).normalize() == key.normalize() { + warn!("attempt to put_key payment purse"); + return Err(Into::into(ExecError::Revert(ApiError::HandlePayment( + handle_payment::Error::AttemptToPersistPaymentPurse as u8, + )))); + } + } self.context.put_key(name, key).map_err(Into::into) } @@ -920,13 +929,17 @@ where let handle_payment_costs = system_config.handle_payment_costs(); let result = match entry_point_name { - handle_payment::METHOD_GET_PAYMENT_PURSE => (|| { + handle_payment::METHOD_GET_PAYMENT_PURSE => { runtime.charge_system_contract_call(handle_payment_costs.get_payment_purse)?; - - let rights_controlled_purse = - runtime.get_payment_purse().map_err(Self::reverter)?; - CLValue::from_t(rights_controlled_purse).map_err(Self::reverter) - })(), + match self.context.maybe_payment_purse() { + Some(payment_purse) => CLValue::from_t(payment_purse).map_err(Self::reverter), + None => { + let payment_purse = runtime.get_payment_purse().map_err(Self::reverter)?; + self.context.set_payment_purse(payment_purse); + CLValue::from_t(payment_purse).map_err(Self::reverter) + } + } + } handle_payment::METHOD_SET_REFUND_PURSE => (|| { runtime.charge_system_contract_call(handle_payment_costs.set_refund_purse)?; diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index 485cc4cec6..5e441d2c62 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -90,6 +90,7 @@ pub struct RuntimeContext<'a, R> { account_hash: AccountHash, emit_message_cost: U512, allow_install_upgrade: AllowInstallUpgrade, + payment_purse: Option, } impl<'a, R> RuntimeContext<'a, R> @@ -149,6 +150,7 @@ where remaining_spending_limit, emit_message_cost, allow_install_upgrade, + payment_purse: None, } } @@ -180,6 +182,7 @@ where let remaining_spending_limit = self.remaining_spending_limit(); let transfers = self.transfers.clone(); + let payment_purse = self.payment_purse; RuntimeContext { tracking_copy, @@ -203,6 +206,7 @@ where remaining_spending_limit, emit_message_cost: self.emit_message_cost, allow_install_upgrade: self.allow_install_upgrade, + payment_purse, } } @@ -231,6 +235,16 @@ where self.named_keys.contains(name) } + /// Returns the payment purse, if set. + pub fn maybe_payment_purse(&self) -> Option { + self.payment_purse + } + + /// Sets the payment purse to the imputed uref. + pub fn set_payment_purse(&mut self, uref: URef) { + self.payment_purse = Some(uref); + } + /// Returns an instance of the engine config. pub fn engine_config(&self) -> &EngineConfig { &self.engine_config diff --git a/execution_engine_testing/tests/src/test/deploy/non_standard_payment.rs b/execution_engine_testing/tests/src/test/deploy/non_standard_payment.rs index fe8529205b..e26de529a7 100644 --- a/execution_engine_testing/tests/src/test/deploy/non_standard_payment.rs +++ b/execution_engine_testing/tests/src/test/deploy/non_standard_payment.rs @@ -3,16 +3,18 @@ use casper_engine_test_support::{ DEFAULT_PAYMENT, DEFAULT_PROTOCOL_VERSION, LOCAL_GENESIS_REQUEST, MINIMUM_ACCOUNT_CREATION_BALANCE, }; -use casper_execution_engine::engine_state::BlockInfo; +use casper_execution_engine::{engine_state::BlockInfo, execution::ExecError}; use casper_storage::data_access_layer::BalanceIdentifier; use casper_types::{ - account::AccountHash, runtime_args, BlockHash, Digest, Gas, RuntimeArgs, Timestamp, U512, + account::AccountHash, runtime_args, ApiError, BlockHash, Digest, Gas, RuntimeArgs, Timestamp, + U512, }; const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([42u8; 32]); const DO_NOTHING_WASM: &str = "do_nothing.wasm"; const CONTRACT_TRANSFER_TO_ACCOUNT: &str = "transfer_to_account_u512.wasm"; const TRANSFER_MAIN_PURSE_TO_NEW_PURSE_WASM: &str = "transfer_main_purse_to_new_purse.wasm"; +const PAYMENT_PURSE_PERSIST_WASM: &str = "payment_purse_persist.wasm"; const NAMED_PURSE_PAYMENT_WASM: &str = "named_purse_payment.wasm"; const ARG_TARGET: &str = "target"; const ARG_AMOUNT: &str = "amount"; @@ -126,3 +128,42 @@ fn should_charge_non_main_purse() { "since we zero'd out the paying purse, the final balance should be zero" ); } + +#[ignore] +#[allow(unused)] +#[test] +fn should_not_allow_custom_payment_purse_persistence() { + let mut builder = LmdbWasmTestBuilder::default(); + + builder.run_genesis(LOCAL_GENESIS_REQUEST.clone()); + + let account_hash = *DEFAULT_ACCOUNT_ADDR; + + let deploy_item = DeployItemBuilder::new() + .with_address(account_hash) + .with_session_code(DO_NOTHING_WASM, RuntimeArgs::default()) + .with_payment_code( + PAYMENT_PURSE_PERSIST_WASM, + runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT}, + ) + .with_deploy_hash([1; 32]) + .with_authorization_keys(&[account_hash]) + .build(); + let block_info = BlockInfo::new( + Digest::default(), + Timestamp::now().millis().into(), + BlockHash::default(), + 1, + ); + let limit = Gas::from(12_500_000_000_u64); + + let request = deploy_item + .new_custom_payment_from_deploy_item(block_info, limit) + .expect("should be valid req"); + + builder.exec_wasm_v1(request).expect_failure(); + + builder.assert_error(casper_execution_engine::engine_state::Error::Exec( + ExecError::Revert(ApiError::HandlePayment(40)), + )); +} diff --git a/smart_contracts/contracts/test/payment-purse-persist/Cargo.toml b/smart_contracts/contracts/test/payment-purse-persist/Cargo.toml new file mode 100644 index 0000000000..8dced261ed --- /dev/null +++ b/smart_contracts/contracts/test/payment-purse-persist/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "payment-purse-persist" +version = "0.1.0" +authors = ["Ed Hastings ", "MichaƂ Papierski "] +edition = "2021" + +[[bin]] +name = "payment_purse_persist" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +casper-contract = { path = "../../../contract" } +casper-types = { path = "../../../../types" } diff --git a/smart_contracts/contracts/test/payment-purse-persist/src/main.rs b/smart_contracts/contracts/test/payment-purse-persist/src/main.rs new file mode 100644 index 0000000000..8d2b3f80a3 --- /dev/null +++ b/smart_contracts/contracts/test/payment-purse-persist/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use casper_contract::contract_api::{runtime, runtime::put_key, system}; +use casper_types::{RuntimeArgs, URef}; + +const GET_PAYMENT_PURSE: &str = "get_payment_purse"; +const THIS_SHOULD_FAIL: &str = "this_should_fail"; + +/// This logic is intended to be used as SESSION PAYMENT LOGIC +/// It gets the payment purse and attempts and attempts to persist it, +/// which should fail. +#[no_mangle] +pub extern "C" fn call() { + // handle payment contract + let handle_payment_contract_hash = system::get_handle_payment(); + + // get payment purse for current execution + let payment_purse: URef = runtime::call_contract( + handle_payment_contract_hash, + GET_PAYMENT_PURSE, + RuntimeArgs::default(), + ); + // attempt to persist the payment purse, which should fail + put_key(THIS_SHOULD_FAIL, payment_purse.into()); +} diff --git a/storage/src/system/handle_payment.rs b/storage/src/system/handle_payment.rs index 4f2b25e520..a093e18b6e 100644 --- a/storage/src/system/handle_payment.rs +++ b/storage/src/system/handle_payment.rs @@ -24,7 +24,6 @@ pub trait HandlePayment: MintProvider + RuntimeProvider + StorageProvider + Size /// Get payment purse. fn get_payment_purse(&mut self) -> Result { let purse = internal::get_payment_purse(self)?; - println!("{purse}"); // Limit the access rights so only balance query and deposit are allowed. Ok(URef::new(purse.addr(), AccessRights::READ_ADD)) } diff --git a/storage/src/system/protocol_upgrade.rs b/storage/src/system/protocol_upgrade.rs index d83327c2f9..02890ed92e 100644 --- a/storage/src/system/protocol_upgrade.rs +++ b/storage/src/system/protocol_upgrade.rs @@ -3,7 +3,7 @@ use num_rational::Ratio; use std::{cell::RefCell, collections::BTreeSet, rc::Rc}; use thiserror::Error; -use tracing::{debug, error, info}; +use tracing::{debug, error, info, warn}; use casper_types::{ addressable_entity::{ @@ -19,17 +19,18 @@ use casper_types::{ LOCKED_FUNDS_PERIOD_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY, UNBONDING_DELAY_KEY, VALIDATOR_SLOTS_KEY, }, - handle_payment::ACCUMULATION_PURSE_KEY, + handle_payment::{ACCUMULATION_PURSE_KEY, PAYMENT_PURSE_KEY}, mint::{ MINT_GAS_HOLD_HANDLING_KEY, MINT_GAS_HOLD_INTERVAL_KEY, ROUND_SEIGNIORAGE_RATE_KEY, + TOTAL_SUPPLY_KEY, }, SystemEntityType, AUCTION, HANDLE_PAYMENT, MINT, }, AccessRights, AddressableEntity, AddressableEntityHash, ByteCode, ByteCodeAddr, ByteCodeHash, ByteCodeKind, CLValue, CLValueError, Contract, Digest, EntityAddr, EntityVersions, EntryPointAddr, EntryPointValue, EntryPoints, FeeHandling, Groups, HashAddr, Key, KeyTag, - Package, PackageHash, PackageStatus, Phase, ProtocolUpgradeConfig, ProtocolVersion, PublicKey, - StoredValue, SystemHashRegistry, URef, U512, + Motes, Package, PackageHash, PackageStatus, Phase, ProtocolUpgradeConfig, ProtocolVersion, + PublicKey, StoredValue, SystemHashRegistry, URef, U512, }; use crate::{ @@ -191,6 +192,10 @@ where self.refresh_system_contracts(&system_entity_addresses)?; } + self.handle_payment_purse_check( + system_entity_addresses.handle_payment(), + system_entity_addresses.mint(), + )?; self.handle_new_gas_hold_config(system_entity_addresses.mint())?; self.handle_new_validator_slots(system_entity_addresses.auction())?; self.handle_new_auction_delay(system_entity_addresses.auction())?; @@ -892,6 +897,74 @@ where } } + /// Check payment purse balance. + pub fn handle_payment_purse_check( + &mut self, + handle_payment: HashAddr, + mint: HashAddr, + ) -> Result<(), ProtocolUpgradeError> { + let payment_named_keys = self.get_named_keys(handle_payment)?; + let payment_purse_key = payment_named_keys + .get(PAYMENT_PURSE_KEY) + .expect("payment purse key must exist in handle payment contract's named keys"); + let balance = self + .tracking_copy + .get_total_balance(*payment_purse_key) + .expect("must be able to get payment purse balance"); + if balance <= Motes::zero() { + return Ok(()); + } + warn!("payment purse had remaining balance at upgrade {}", balance); + let balance_key = { + let uref_addr = payment_purse_key + .as_uref() + .expect("payment purse key must be uref.") + .addr(); + Key::Balance(uref_addr) + }; + + let mint_named_keys = self.get_named_keys(mint)?; + let total_supply_key = mint_named_keys + .get(TOTAL_SUPPLY_KEY) + .expect("total supply key must exist in mint contract's named keys"); + + let stored_value = self + .tracking_copy + .read(total_supply_key) + .expect("must be able to read total supply") + .expect("total supply must have a value"); + + // by convention, we only store CLValues under Key::URef + if let StoredValue::CLValue(value) = stored_value { + // Only CLTyped instances should be stored as a CLValue. + let total_supply: U512 = + CLValue::into_t(value).expect("total supply must have expected type."); + + let new_total_supply = total_supply.saturating_sub(balance.value()); + info!( + "adjusting total supply from {} to {}", + total_supply, new_total_supply + ); + let cl_value = CLValue::from_t(new_total_supply) + .expect("new total supply must convert to CLValue."); + self.tracking_copy + .write(*total_supply_key, StoredValue::CLValue(cl_value)); + info!( + "adjusting payment purse balance from {} to {}", + balance.value(), + U512::zero() + ); + let cl_value = CLValue::from_t(U512::zero()).expect("zero must convert to CLValue."); + self.tracking_copy + .write(balance_key, StoredValue::CLValue(cl_value)); + Ok(()) + } else { + Err(ProtocolUpgradeError::CLValue( + "failure to retrieve total supply".to_string(), + )) + } + } + /// Upsert gas hold interval to mint named keys. pub fn handle_new_gas_hold_config( &mut self, diff --git a/types/src/system/handle_payment/error.rs b/types/src/system/handle_payment/error.rs index 09423e87a6..4f1305b3e0 100644 --- a/types/src/system/handle_payment/error.rs +++ b/types/src/system/handle_payment/error.rs @@ -3,7 +3,6 @@ use alloc::vec::Vec; use core::{ convert::TryFrom, fmt::{self, Display, Formatter}, - result, }; use crate::{ @@ -263,6 +262,12 @@ pub enum Error { /// assert_eq!(39, Error::UnexpectedKeyVariant as u8); /// ``` UnexpectedKeyVariant = 39, + /// Attempt to persist payment purse. + /// ``` + /// # use casper_types::system::handle_payment::Error; + /// assert_eq!(40, Error::AttemptToPersistPaymentPurse as u8); + /// ``` + AttemptToPersistPaymentPurse = 40, } impl Display for Error { @@ -338,6 +343,9 @@ impl Display for Error { formatter.write_str("Incompatible payment settings") } Error::UnexpectedKeyVariant => formatter.write_str("Unexpected key variant"), + Error::AttemptToPersistPaymentPurse => { + formatter.write_str("Attempt to persist payment purse") + } } } } @@ -414,6 +422,9 @@ impl TryFrom for Error { Error::IncompatiblePaymentSettings } v if v == Error::UnexpectedKeyVariant as u8 => Error::UnexpectedKeyVariant, + v if v == Error::AttemptToPersistPaymentPurse as u8 => { + Error::AttemptToPersistPaymentPurse + } _ => return Err(()), }; Ok(error) @@ -427,7 +438,7 @@ impl CLTyped for Error { } impl ToBytes for Error { - fn to_bytes(&self) -> result::Result, bytesrepr::Error> { + fn to_bytes(&self) -> Result, bytesrepr::Error> { let value = *self as u8; value.to_bytes() }