Skip to content

Commit

Permalink
guardrail
Browse files Browse the repository at this point in the history
  • Loading branch information
EdHastingsCasperAssociation committed Dec 19, 2024
1 parent 0793d68 commit 14c0426
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 16 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 20 additions & 7 deletions execution_engine/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)?;

Expand Down
14 changes: 14 additions & 0 deletions execution_engine/src/runtime_context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ pub struct RuntimeContext<'a, R> {
account_hash: AccountHash,
emit_message_cost: U512,
allow_install_upgrade: AllowInstallUpgrade,
payment_purse: Option<URef>,
}

impl<'a, R> RuntimeContext<'a, R>
Expand Down Expand Up @@ -149,6 +150,7 @@ where
remaining_spending_limit,
emit_message_cost,
allow_install_upgrade,
payment_purse: None,
}
}

Expand Down Expand Up @@ -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,
Expand All @@ -203,6 +206,7 @@ where
remaining_spending_limit,
emit_message_cost: self.emit_message_cost,
allow_install_upgrade: self.allow_install_upgrade,
payment_purse,
}
}

Expand Down Expand Up @@ -231,6 +235,16 @@ where
self.named_keys.contains(name)
}

/// Returns the payment purse, if set.
pub fn maybe_payment_purse(&self) -> Option<URef> {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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)),
));
}
16 changes: 16 additions & 0 deletions smart_contracts/contracts/test/payment-purse-persist/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "payment-purse-persist"
version = "0.1.0"
authors = ["Ed Hastings <[email protected]>", "Michał Papierski <[email protected]>"]
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" }
28 changes: 28 additions & 0 deletions smart_contracts/contracts/test/payment-purse-persist/src/main.rs
Original file line number Diff line number Diff line change
@@ -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());
}
1 change: 0 additions & 1 deletion storage/src/system/handle_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ pub trait HandlePayment: MintProvider + RuntimeProvider + StorageProvider + Size
/// Get payment purse.
fn get_payment_purse(&mut self) -> Result<URef, Error> {
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))
}
Expand Down
81 changes: 77 additions & 4 deletions storage/src/system/protocol_upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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::{
Expand Down Expand Up @@ -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())?;
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 13 additions & 2 deletions types/src/system/handle_payment/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use alloc::vec::Vec;
use core::{
convert::TryFrom,
fmt::{self, Display, Formatter},
result,
};

use crate::{
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}
}
}
}
Expand Down Expand Up @@ -414,6 +422,9 @@ impl TryFrom<u8> 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)
Expand All @@ -427,7 +438,7 @@ impl CLTyped for Error {
}

impl ToBytes for Error {
fn to_bytes(&self) -> result::Result<Vec<u8>, bytesrepr::Error> {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let value = *self as u8;
value.to_bytes()
}
Expand Down

0 comments on commit 14c0426

Please sign in to comment.