diff --git a/contracts/transfer/Cargo.toml b/contracts/transfer/Cargo.toml index fb7b0db6f0..e1e8b8a93e 100644 --- a/contracts/transfer/Cargo.toml +++ b/contracts/transfer/Cargo.toml @@ -27,8 +27,8 @@ rusk-abi = { version = "0.13.0-rc", path = "../../rusk-abi" } rusk-profile = { version = "0.6", path = "../../rusk-profile" } rusk-abi = { version = "0.13.0-rc", path = "../../rusk-abi", default-features = false, features = ["host"] } transfer-circuits = { version = "0.5", path = "../../circuits/transfer" } -subsidy-types = { version = "0.0.1", path = "../subsidy-types", default-features = false } rkyv = { version = "0.7", default-features = false, features = ["size_32"] } +bytecheck = { version = "0.6", default-features = false } hex = "0.4" rand = "0.8" ff = { version = "0.13", default-features = false } diff --git a/contracts/transfer/src/lib.rs b/contracts/transfer/src/lib.rs index 93404d68a4..7722bc675f 100644 --- a/contracts/transfer/src/lib.rs +++ b/contracts/transfer/src/lib.rs @@ -112,20 +112,15 @@ unsafe fn spend_and_execute(arg_len: u32) -> u32 { }) } -#[no_mangle] -unsafe fn execute(arg_len: u32) -> u32 { - rusk_abi::wrap_call(arg_len, |tx| { - assert_external_caller(); - STATE.execute(tx) - }) -} - #[no_mangle] unsafe fn refund(arg_len: u32) -> u32 { - rusk_abi::wrap_call(arg_len, |(fee, gas_spent)| { - assert_external_caller(); - STATE.refund(fee, gas_spent) - }) + rusk_abi::wrap_call( + arg_len, + |(fee, gas_spent, economic_mode, contract_id)| { + assert_external_caller(); + STATE.refund(fee, gas_spent, economic_mode, contract_id) + }, + ) } #[no_mangle] diff --git a/contracts/transfer/src/state.rs b/contracts/transfer/src/state.rs index b5eeb76ac4..7b66577f12 100644 --- a/contracts/transfer/src/state.rs +++ b/contracts/transfer/src/state.rs @@ -33,9 +33,6 @@ pub const A: usize = 4; /// Number of roots stored pub const MAX_ROOTS: usize = 5000; -// Estimated cost of returning from the execute method -const SURCHARGE_POINTS: u64 = 10000; - pub struct TransferState { tree: Tree, nullifiers: BTreeSet, @@ -231,19 +228,6 @@ impl TransferState { self.var_crossover = tx.crossover; self.var_crossover_addr.replace(*tx.fee.stealth_address()); - self.execute(tx) - } - - /// Executes the contract call if present. - /// - /// # Panics - /// Any failure in the checks performed in processing will result in a - /// panic. The contract expects the environment to roll back any change - /// in state. - pub fn execute( - &mut self, - tx: Transaction, - ) -> Result, ContractError> { let mut result = Ok(rusk_abi::RawResult::empty()); if let Some((contract_id, fn_name, fn_args)) = tx.call { @@ -257,14 +241,11 @@ impl TransferState { }) = result.clone() { match economic_mode { - EconomicMode::Charge(charge) if charge != 0 => self - .apply_charge(&contract_id, charge, tx.fee.gas_price), + EconomicMode::Charge(charge) if charge != 0 => { + rusk_abi::set_charge(charge) + } EconomicMode::Allowance(allowance) if allowance != 0 => { - self.apply_allowance( - &contract_id, - allowance, - tx.fee.gas_price, - ) + rusk_abi::set_allowance(allowance) } _ => (), } @@ -274,22 +255,23 @@ impl TransferState { result.map(|r| r.data) } - /// Applies contract's charge. Caller of the contract will pay a - /// larger fee so that contract can earn the difference between - /// charge and the actual cost of the call. - /// Charge has no effect if the actual cost of the call is greater - /// than the charge. + // Applies contract's charge. Caller of the contract will pay a + // larger fee so that contract can earn the difference between + // charge and the actual cost of the call. + // Charge has no effect if the actual cost of the call is greater + // than the charge. + // Returns economic gas spent fn apply_charge( &mut self, contract_id: &ContractId, charge: u64, + gas_spent: u64, gas_price: u64, - ) { - let cost = (rusk_abi::spent() + SURCHARGE_POINTS) * gas_price; + ) -> u64 { + let cost = gas_spent * gas_price; if charge > cost { - let earning = charge - cost; + let earning = charge * gas_price - cost; self.add_balance(*contract_id, earning); - rusk_abi::set_charge(earning); rusk_abi::emit( "earning", EconomicEvent { @@ -298,38 +280,43 @@ impl TransferState { result: EconomicResult::ChargeApplied, }, ); + charge } else { rusk_abi::emit( "earning", EconomicEvent { module: contract_id.to_bytes(), - value: charge, + value: charge * gas_price, result: EconomicResult::ChargeNotSufficient, }, ); + gas_spent } } - /// Applies contract's allowance. Caller of the contract's method - /// won't pay a fee and all the cost will be covered by the contract. - /// Allowance has no effect if contract does not have enough funds or - /// if the actual cost of the call is greater than allowance. + // Applies contract's allowance. Caller of the contract's method + // won't pay a fee and all the cost will be covered by the contract. + // Allowance has no effect if contract does not have enough funds or + // if the actual cost of the call is greater than allowance. + // Returns economic gas spent fn apply_allowance( &mut self, contract_id: &ContractId, allowance: u64, + gas_spent: u64, gas_price: u64, - ) { - let spent = (rusk_abi::spent() + SURCHARGE_POINTS) * gas_price; - if allowance < spent { + ) -> u64 { + let spent = gas_spent * gas_price; + if allowance * gas_price < spent { rusk_abi::emit( "sponsoring", EconomicEvent { module: contract_id.to_bytes(), - value: allowance, + value: allowance * gas_price, result: EconomicResult::AllowanceNotSufficient, }, ); + gas_spent } else { let contract_balance = self.balance(contract_id); if spent > contract_balance { @@ -341,12 +328,12 @@ impl TransferState { result: EconomicResult::BalanceNotSufficient, }, ); + gas_spent } else { self.sub_balance(contract_id, spent).expect( "Subtracting callee contract balance should succeed", ); self.add_balance(rusk_abi::self_id(), spent); - rusk_abi::set_allowance(allowance); rusk_abi::emit( "sponsoring", EconomicEvent { @@ -355,6 +342,7 @@ impl TransferState { result: EconomicResult::AllowanceApplied, }, ); + 0u64 } } } @@ -362,12 +350,42 @@ impl TransferState { /// Refund the previously performed transaction, taking into account the /// given gas spent. The notes produced will be refunded to the address /// present in the fee structure. + /// If contract id is present, it applies economic mode to the the contract + /// and refund is based on the economic calculation. /// /// This function guarantees that it will not panic. - pub fn refund(&mut self, fee: Fee, gas_spent: u64) { + pub fn refund( + &mut self, + fee: Fee, + gas_spent: u64, + economic_mode: EconomicMode, + contract_id: Option, + ) { + let economic_gas_spent = if let Some(contract_id) = contract_id { + match economic_mode { + EconomicMode::Charge(charge) if charge != 0 => self + .apply_charge( + &contract_id, + charge, + gas_spent, + fee.gas_price, + ), + EconomicMode::Allowance(allowance) if allowance != 0 => self + .apply_allowance( + &contract_id, + allowance, + gas_spent, + fee.gas_price, + ), + _ => gas_spent, + } + } else { + gas_spent + }; + let block_height = rusk_abi::block_height(); - let remainder = fee.gen_remainder(gas_spent); + let remainder = fee.gen_remainder(economic_gas_spent); let remainder = Note::from(remainder); let remainder_value = remainder diff --git a/contracts/transfer/tests/common/utils.rs b/contracts/transfer/tests/common/utils.rs index 6ede163f85..18e9acd5b1 100644 --- a/contracts/transfer/tests/common/utils.rs +++ b/contracts/transfer/tests/common/utils.rs @@ -123,45 +123,44 @@ pub fn prover_verifier(circuit_name: &str) -> (Prover, Verifier) { (prover, verifier) } -/// Executes a regular (not call-only) transaction. +/// Executes a transaction. /// Returns result containing gas spent and economic mode. pub fn execute( session: &mut Session, tx: Transaction, ) -> Result { - let receipt = session.call::<_, Result, ContractError>>( + let mut receipt = session.call::<_, Result, ContractError>>( TRANSFER_CONTRACT, "spend_and_execute", &tx, u64::MAX, )?; - let gas_spent = receipt.gas_spent; + // Ensure all gas is consumed if there's an error in the contract call + if receipt.data.is_err() { + receipt.gas_spent = receipt.gas_limit; + } - session + let contract_id = tx + .call + .clone() + .map(|(module_id, _, _)| ContractId::from_bytes(module_id)); + + let refund_receipt = session .call::<_, ()>( TRANSFER_CONTRACT, "refund", - &(tx.fee, gas_spent), + &( + tx.fee, + receipt.gas_spent, + receipt.economic_mode.clone(), + contract_id, + ), u64::MAX, ) .expect("Refunding must succeed"); - Ok(ExecutionResult::new(gas_spent, receipt.economic_mode)) -} - -/// Executes a call-only transaction. -/// Returns result containing gas spent and economic mode. -pub fn execute_call( - session: &mut Session, - tx: Transaction, -) -> Result { - let receipt = session.call::<_, Result, ContractError>>( - TRANSFER_CONTRACT, - "execute", - &tx, - u64::MAX, - )?; + receipt.events.extend(refund_receipt.events); Ok(ExecutionResult::new( receipt.gas_spent, diff --git a/contracts/transfer/tests/scenario3.rs b/contracts/transfer/tests/scenario3.rs index 8a3079afb7..4b64b60faa 100644 --- a/contracts/transfer/tests/scenario3.rs +++ b/contracts/transfer/tests/scenario3.rs @@ -9,7 +9,7 @@ pub mod common; use crate::common::utils::*; use bls12_381_bls::{ - PublicKey as SubsidyPublicKey, SecretKey as SubsidySecretKey, + PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, Signature, }; use dusk_bytes::Serializable; use dusk_jubjub::{JubJubScalar, GENERATOR_NUMS_EXTENDED}; @@ -19,11 +19,11 @@ use phoenix_core::{Fee, Note, Ownable}; use phoenix_core::{PublicKey, SecretKey}; use rand::rngs::StdRng; use rand::{CryptoRng, RngCore, SeedableRng}; +use rkyv::{Archive, Deserialize, Serialize}; use rusk_abi::dusk::{dusk, LUX}; use rusk_abi::{ ContractData, ContractId, EconomicMode, Session, TRANSFER_CONTRACT, VM, }; -use subsidy_types::Subsidy; use transfer_circuits::{ CircuitInput, CircuitInputSignature, ExecuteCircuitOneTwo, SendToContractTransparentCircuit, @@ -40,6 +40,20 @@ const CHARLIE_CONTRACT_ID: ContractId = { const OWNER: [u8; 32] = [0; 32]; +/// Subsidy a contract with a value. +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(bytecheck::CheckBytes))] +pub struct Subsidy { + /// Public key to which the subsidy will belong. + pub public_key: BlsPublicKey, + /// Signature belonging to the given public key. + pub signature: Signature, + /// Value of the subsidy. + pub value: u64, + /// Proof of the `STCT` circuit. + pub proof: Vec, +} + fn instantiate( rng: &mut Rng, vm: &VM, @@ -108,8 +122,8 @@ fn subsidize_contract( rng: &mut R, mut session: &mut Session, contract_id: ContractId, - subsidy_keeper_pk: SubsidyPublicKey, - subsidy_keeper_sk: SubsidySecretKey, + subsidy_keeper_pk: BlsPublicKey, + subsidy_keeper_sk: BlsSecretKey, subsidizer_psk: PublicKey, subsidizer_ssk: SecretKey, input_note: Note, @@ -275,8 +289,8 @@ fn instantiate_and_subsidize_contract( let test_sponsor_ssk = SecretKey::random(rng); let test_sponsor_psk = PublicKey::from(&test_sponsor_ssk); // sponsor is Charlie's owner - let subsidy_keeper_sk = SubsidySecretKey::random(rng); - let subsidy_keeper_pk = SubsidyPublicKey::from(&subsidy_keeper_sk); + let subsidy_keeper_sk = BlsSecretKey::random(rng); + let subsidy_keeper_pk = BlsPublicKey::from(&subsidy_keeper_sk); let mut session = instantiate(rng, vm, Some(subsidizer_psk), Some(test_sponsor_psk)); @@ -495,98 +509,6 @@ fn call_contract_method_with_deposit( (execution_result, balance_before, balance_after) } -/// Creates and executes a transaction -/// which calls a given method of a given contract. -/// The transaction will not contain any notes. -/// The contract is expected to have funds in its wallet. -fn call_contract_method_without_deposit( - mut session: &mut Session, - contract_id: ContractId, - method: impl AsRef, - sponsor_ssk: SecretKey, - gas_price: u64, -) -> (ExecutionResult, u64, u64) { - let rng = &mut StdRng::seed_from_u64(0xfeeb); - let test_sponsor_psk = PublicKey::from(&sponsor_ssk); // sponsor is Charlie's owner - - // make sure the sponsoring contract is properly subsidized (has funds) - let balance_before = module_balance(&mut session, contract_id) - .expect("Module balance should succeed"); - println!( - "current balance of contract '{:X?}' is {}", - contract_id.to_bytes()[0], - balance_before - ); - assert!(balance_before > 0); - - let anchor = - root(session).expect("Getting the anchor should be successful"); - - let fee = Fee::new(rng, 0, gas_price, &test_sponsor_psk); - - let call = Some(( - contract_id.to_bytes(), - String::from(method.as_ref()), - vec![], - )); - - let tx = Transaction { - anchor, - nullifiers: vec![], - outputs: vec![], - fee, - crossover: None, - proof: vec![], - call, - }; - - println!( - "executing method '{}' - contract '{:X?}' is paying", - method.as_ref(), - contract_id.to_bytes()[0] - ); - let execution_result = - execute_call(session, tx).expect("Executing TX should succeed"); - update_root(session).expect("Updating the root should succeed"); - - println!( - "gas spent for the execution of method '{}' is {}", - method.as_ref(), - execution_result.gas_spent - ); - - let balance_after = module_balance(&mut session, contract_id) - .expect("Module balance should succeed"); - - println!( - "contract's '{:X?}' balance before the call: {}", - contract_id.as_bytes()[0], - balance_before - ); - println!( - "contract's '{:X?}' balance after the call: {}", - contract_id.as_bytes()[0], - balance_after - ); - if balance_before > balance_after { - println!( - "contract '{:X?}' has paid for this call: {}", - contract_id.as_bytes()[0], - balance_before - balance_after - ); - println!("this call was sponsored by contract '{:X?}', gas spent by the caller is: {}", contract_id.as_bytes()[0], execution_result.gas_spent); - } else { - println!( - "contract '{:X?}' has earned: {}", - contract_id.as_bytes()[0], - balance_after - balance_before - ); - println!("this call was charged by contract '{:X?}', gas spent by the caller is: {}", contract_id.as_bytes()[0], execution_result.gas_spent); - } - - (execution_result, balance_before, balance_after) -} - #[test] fn contract_sponsors_call_with_deposit() { const GAS_PRICE: u64 = 2; @@ -613,27 +535,6 @@ fn contract_sponsors_call_with_deposit() { assert!(balance_delta >= execution_result.gas_spent); } -#[test] -fn contract_sponsors_call_no_deposit() { - const GAS_PRICE: u64 = 2; - let vm = &mut rusk_abi::new_ephemeral_vm() - .expect("Creating ephemeral VM should work"); - - let (mut session, sponsor_ssk) = - instantiate_and_subsidize_contract(vm, CHARLIE_CONTRACT_ID); - let (execution_result, balance_before, balance_after) = - call_contract_method_without_deposit( - &mut session, - CHARLIE_CONTRACT_ID, - "pay", - sponsor_ssk, - GAS_PRICE, - ); - assert!(balance_after < balance_before); - let balance_delta = balance_before - balance_after; - assert!(execution_result.gas_spent < balance_delta); -} - #[test] fn contract_sponsors_not_enough_allowance() { const GAS_PRICE: u64 = 2; @@ -655,7 +556,7 @@ fn contract_sponsors_not_enough_allowance() { } #[test] -fn contract_earns_a_fee() { +fn contract_earns_fee() { const GAS_PRICE: u64 = 2; let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); @@ -674,10 +575,10 @@ fn contract_earns_a_fee() { let balance_delta = balance_after - balance_before; assert_eq!( execution_result.economic_mode, - EconomicMode::Charge(balance_delta) + EconomicMode::Charge( + balance_delta / GAS_PRICE + execution_result.gas_spent + ) ); - let effective_gas_spent = execution_result.gas_spent + balance_delta; - assert!(balance_delta <= effective_gas_spent); } #[test]