Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Payment QoL #5045

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

59 changes: 52 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 Expand Up @@ -2017,6 +2030,22 @@ where
return Ok(Err(err));
}
let args: RuntimeArgs = bytesrepr::deserialize_from_slice(args_bytes)?;

if let Some(payment_purse) = self.context.maybe_payment_purse() {
for named_arg in args.named_args() {
if utils::extract_urefs(named_arg.cl_value())?
.into_iter()
.any(|uref| uref.remove_access_rights() == payment_purse.remove_access_rights())
{
warn!("attempt to call_contract with payment purse");

return Err(Into::into(ExecError::Revert(ApiError::HandlePayment(
handle_payment::Error::AttemptToPersistPaymentPurse as u8,
))));
}
}
}

let result = self.call_contract(contract_hash, entry_point_name, args)?;
self.manage_call_contract_host_buffer(result_size_ptr, result)
}
Expand All @@ -2034,6 +2063,22 @@ where
return Ok(Err(err));
}
let args: RuntimeArgs = bytesrepr::deserialize_from_slice(args_bytes)?;

if let Some(payment_purse) = self.context.maybe_payment_purse() {
for named_arg in args.named_args() {
if utils::extract_urefs(named_arg.cl_value())?
.into_iter()
.any(|uref| uref.remove_access_rights() == payment_purse.remove_access_rights())
{
warn!("attempt to call_versioned_contract with payment purse");

return Err(Into::into(ExecError::Revert(ApiError::HandlePayment(
handle_payment::Error::AttemptToPersistPaymentPurse as u8,
))));
}
}
}

let result = self.call_versioned_contract(
contract_package_hash,
contract_version,
Expand Down
2 changes: 1 addition & 1 deletion execution_engine/src/runtime/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub(super) fn attenuate_uref_in_args(

/// Extracts a copy of every uref able to be deserialized from `cl_value`.
pub(super) fn extract_urefs(cl_value: &CLValue) -> Result<Vec<URef>, ExecError> {
let mut vec: Vec<URef> = Vec::new();
let mut vec: Vec<URef> = Default::default();
rewrite_urefs(cl_value.clone(), |uref| {
vec.push(*uref);
})?;
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
123 changes: 120 additions & 3 deletions execution_engine_testing/tests/src/test/deploy/non_standard_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@ 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";
const ARG_PURSE_NAME: &str = "purse_name";
const ARG_DESTINATION: &str = "destination";

#[ignore]
#[allow(unused)]
#[test]
fn should_charge_non_main_purse() {
// as account_1, create & fund a new purse and use that to pay for something
Expand Down Expand Up @@ -126,3 +127,119 @@ fn should_charge_non_main_purse() {
"since we zero'd out the paying purse, the final balance should be zero"
);
}

const ARG_METHOD: &str = "method";

#[ignore]
#[test]
fn should_not_allow_custom_payment_purse_persistence_1() {
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, ARG_METHOD => "put_key"},
)
.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)),
));
}

#[ignore]
#[test]
fn should_not_allow_custom_payment_purse_persistence_2() {
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, ARG_METHOD => "call_contract"},
)
.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)),
));
}

#[ignore]
#[test]
fn should_not_allow_custom_payment_purse_persistence_3() {
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, ARG_METHOD => "call_versioned_contract"},
)
.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" }
63 changes: 63 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,63 @@
#![no_std]
#![no_main]

extern crate alloc;

use alloc::string::String;
use casper_contract::contract_api::{runtime, runtime::put_key, system};
use casper_types::{contracts::ContractPackageHash, runtime_args, ApiError, RuntimeArgs, URef};

const GET_PAYMENT_PURSE: &str = "get_payment_purse";
const THIS_SHOULD_FAIL: &str = "this_should_fail";

const ARG_METHOD: &str = "method";

/// 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() {
let method: String = runtime::get_named_arg(ARG_METHOD);

// 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(),
);

if method == "put_key" {
// attempt to persist the payment purse, which should fail
put_key(THIS_SHOULD_FAIL, payment_purse.into());
} else if method == "call_contract" {
// attempt to call a contract with the payment purse, which should fail
let _payment_purse: URef = runtime::call_contract(
handle_payment_contract_hash,
GET_PAYMENT_PURSE,
runtime_args! {
"payment_purse" => payment_purse,
},
);

// should never reach here
runtime::revert(ApiError::User(1000));
} else if method == "call_versioned_contract" {
// attempt to call a versioned contract with the payment purse, which should fail
let _payment_purse: URef = runtime::call_versioned_contract(
ContractPackageHash::new(handle_payment_contract_hash.value()),
None, // Latest
GET_PAYMENT_PURSE,
runtime_args! {
"payment_purse" => payment_purse,
},
);

// should never reach here
runtime::revert(ApiError::User(1001));
} else {
runtime::revert(ApiError::User(2000));
}
}
Loading
Loading