diff --git a/contracts/stake/benches/get_provisioners.rs b/contracts/stake/benches/get_provisioners.rs index cb4d8a9933..164952c5c3 100644 --- a/contracts/stake/benches/get_provisioners.rs +++ b/contracts/stake/benches/get_provisioners.rs @@ -22,6 +22,7 @@ const NUM_STAKES: usize = 1000; const OWNER: [u8; 32] = [0; 32]; const POINT_LIMIT: u64 = 0x100000000; const TEST_STAKE: u64 = 500_000_000_000_000; +const CHAIN_ID: u8 = 42; fn config() -> Criterion { Criterion::default().sample_size(SAMPLE_SIZE) @@ -41,7 +42,7 @@ fn instantiate(vm: &VM) -> Session { "../../../target/dusk/wasm32-unknown-unknown/release/stake_contract.wasm" ); - let mut session = abi::new_genesis_session(vm); + let mut session = vm.genesis_session(CHAIN_ID); session .deploy( @@ -63,7 +64,7 @@ fn instantiate(vm: &VM) -> Session { let base = session.commit().expect("Committing should succeed"); - abi::new_session(vm, base, 1) + vm.session(base, CHAIN_ID, 1) .expect("Instantiating new session should succeed") } diff --git a/contracts/stake/tests/common/init.rs b/contracts/stake/tests/common/init.rs index 3e0ec49d66..e697b8460f 100644 --- a/contracts/stake/tests/common/init.rs +++ b/contracts/stake/tests/common/init.rs @@ -27,7 +27,7 @@ pub fn instantiate( pk: &PhoenixPublicKey, genesis_value: u64, ) -> Session { - let mut session = dusk_vm::new_genesis_session(vm, CHAIN_ID); + let mut session = vm.genesis_session(CHAIN_ID); // deploy transfer-contract let transfer_bytecode = include_bytes!( @@ -81,6 +81,6 @@ pub fn instantiate( // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - dusk_vm::new_session(vm, base, CHAIN_ID, 1) + vm.session(base, CHAIN_ID, 1) .expect("Instantiating new session should succeed") } diff --git a/contracts/stake/tests/common/utils.rs b/contracts/stake/tests/common/utils.rs index ac4b672e36..8ba7956908 100644 --- a/contracts/stake/tests/common/utils.rs +++ b/contracts/stake/tests/common/utils.rs @@ -6,7 +6,6 @@ use std::sync::mpsc; -use dusk_core::abi::ContractError; use dusk_core::transfer::data::TransactionData; use dusk_core::transfer::phoenix::{ Note, NoteLeaf, NoteOpening, NoteTreeItem, PublicKey as PhoenixPublicKey, @@ -15,7 +14,7 @@ use dusk_core::transfer::phoenix::{ }; use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT}; use dusk_core::{BlsScalar, LUX}; -use dusk_vm::{CallReceipt, Error as VMError, Session}; +use dusk_vm::{Error as VMError, Session}; use rand::rngs::StdRng; use rusk_prover::LocalProver; @@ -98,44 +97,6 @@ pub fn filter_notes_owned_by>( .collect() } -/// Executes a transaction, returning the call receipt -pub fn execute( - session: &mut Session, - tx: impl Into, -) -> Result, ContractError>>, VMError> { - let tx = tx.into(); - - // Spend the inputs and execute the call. If this errors the transaction is - // unspendable. - let mut receipt = session.call::<_, Result, ContractError>>( - TRANSFER_CONTRACT, - "spend_and_execute", - &tx, - tx.gas_limit(), - )?; - - // 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; - } - - // Refund the appropriate amount to the transaction. This call is guaranteed - // to never error. If it does, then a programming error has occurred. As - // such, the call to `Result::expect` is warranted. - let refund_receipt = session - .call::<_, ()>( - TRANSFER_CONTRACT, - "refund", - &receipt.gas_spent, - u64::MAX, - ) - .expect("Refunding must succeed"); - - receipt.events.extend(refund_receipt.events); - - Ok(receipt) -} - /// Generate a TxCircuit given the sender secret-key, receiver public-key, the /// input note positions in the transaction tree and the new output-notes. pub fn create_transaction( diff --git a/contracts/stake/tests/partial_stake.rs b/contracts/stake/tests/partial_stake.rs index 0db5c20efc..e4759b8679 100644 --- a/contracts/stake/tests/partial_stake.rs +++ b/contracts/stake/tests/partial_stake.rs @@ -10,10 +10,7 @@ use dusk_core::signatures::bls::{ }; use dusk_core::stake::{Reward, RewardReason, EPOCH, STAKE_CONTRACT}; use dusk_core::transfer::TRANSFER_CONTRACT; -use dusk_vm::{ - new_genesis_session, new_session, ContractData, Error as VMError, Session, - VM, -}; +use dusk_vm::{execute, ContractData, Error as VMError, Session, VM}; use rand::rngs::StdRng; use rand::SeedableRng; use wallet_core::transaction::{ @@ -21,7 +18,6 @@ use wallet_core::transaction::{ }; pub mod common; - use crate::common::assert::*; use crate::common::init::CHAIN_ID; use crate::common::utils::*; @@ -63,7 +59,7 @@ fn stake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 1st stake transaction let gas_spent_1 = receipt.gas_spent; @@ -91,7 +87,7 @@ fn stake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 2nd stake transaction let gas_spent_2 = receipt.gas_spent; @@ -109,7 +105,7 @@ fn stake() -> Result<(), VMError> { // in order to test the locking some of the stake during a top-up, we need // to start a new session at a block-height on which the stake is eligible let base = session.commit()?; - let mut session = new_session(&vm, base, CHAIN_ID, 2 * EPOCH)?; + let mut session = vm.session(base, CHAIN_ID, 2 * EPOCH)?; // execute 3rd stake transaction let stake_3 = STAKE_VALUE - stake_1 - stake_2; @@ -125,7 +121,7 @@ fn stake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 3rd stake transaction let gas_spent_3 = receipt.gas_spent; @@ -160,7 +156,7 @@ fn stake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - assert!(execute(&mut session, tx,).is_err()); + assert!(execute(&mut session, &tx, 0, 0, 0).is_err()); Ok(()) } @@ -194,7 +190,7 @@ fn unstake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; let mut moonlight_balance = GENESIS_VALUE - STAKE_VALUE - receipt.gas_spent; assert_moonlight(&mut session, &moonlight_pk, moonlight_balance, nonce); @@ -216,7 +212,7 @@ fn unstake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 1st unstake transaction let gas_spent_1 = receipt.gas_spent; @@ -233,7 +229,7 @@ fn unstake() -> Result<(), VMError> { // re-stake the unstaked value after the stake has become eligible let base = session.commit()?; - let mut session = new_session(&vm, base, CHAIN_ID, 2 * EPOCH)?; + let mut session = vm.session(base, CHAIN_ID, 2 * EPOCH)?; nonce += 1; let tx = moonlight_stake( &moonlight_sk, @@ -246,7 +242,7 @@ fn unstake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; total_stake = STAKE_VALUE; let mut locked = unstake_1 / 10; assert_stake(&mut session, &stake_pk, total_stake, locked, 0); @@ -272,7 +268,7 @@ fn unstake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 2nd unstake transaction let gas_spent_2 = receipt.gas_spent; @@ -310,7 +306,7 @@ fn unstake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 3rd unstake transaction let gas_spent_3 = receipt.gas_spent; @@ -353,7 +349,7 @@ fn withdraw_reward() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; let mut moonlight_balance = GENESIS_VALUE - STAKE_VALUE - receipt.gas_spent; assert_moonlight(&mut session, &moonlight_pk, moonlight_balance, nonce); // add a reward to the staked key @@ -378,7 +374,7 @@ fn withdraw_reward() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 1st reward withdrawal let gas_spent_1 = receipt.gas_spent; @@ -413,7 +409,7 @@ fn withdraw_reward() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 1st reward withdrawal let gas_spent_2 = receipt.gas_spent; @@ -458,7 +454,7 @@ fn add_reward( /// genesis-value. fn instantiate(vm: &mut VM, moonlight_pk: &BlsPublicKey) -> Session { // create a new session using an ephemeral vm - let mut session = new_genesis_session(vm, CHAIN_ID); + let mut session = vm.genesis_session(CHAIN_ID); // deploy transfer-contract const OWNER: [u8; 32] = [0; 32]; @@ -502,7 +498,8 @@ fn instantiate(vm: &mut VM, moonlight_pk: &BlsPublicKey) -> Session { // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - let mut session = new_session(vm, base, CHAIN_ID, 1) + let mut session = vm + .session(base, CHAIN_ID, 1) .expect("Instantiating new session should succeed"); // check that the moonlight account is initialized as expected diff --git a/contracts/stake/tests/stake.rs b/contracts/stake/tests/stake.rs index aed9e0f1c0..18ba44bc02 100644 --- a/contracts/stake/tests/stake.rs +++ b/contracts/stake/tests/stake.rs @@ -4,8 +4,6 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -pub mod common; - use dusk_core::signatures::bls::{ PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, }; @@ -21,11 +19,12 @@ use dusk_core::transfer::withdraw::{ Withdraw, WithdrawReceiver, WithdrawReplayToken, }; use dusk_core::{dusk, JubJubScalar}; -use dusk_vm::{new_session, VM}; +use dusk_vm::{execute, VM}; use ff::Field; use rand::rngs::StdRng; use rand::SeedableRng; +pub mod common; use crate::common::assert::{ assert_reward_event, assert_stake, assert_stake_event, }; @@ -86,8 +85,8 @@ fn stake_withdraw_unstake() { contract_call, ); - let receipt = - execute(&mut session, tx).expect("Executing TX should succeed"); + let receipt = execute(&mut session, &tx, 0, 0, 0) + .expect("Executing TX should succeed"); let gas_spent = receipt.gas_spent; receipt.data.expect("Executed TX should not error"); @@ -178,11 +177,12 @@ fn stake_withdraw_unstake() { // set different block height so that the new notes are easily located and // filtered let base = session.commit().expect("Committing should succeed"); - let mut session = new_session(vm, base, CHAIN_ID, 2) + let mut session = vm + .session(base, CHAIN_ID, 2) .expect("Instantiating new session should succeed"); - let receipt = - execute(&mut session, tx).expect("Executing TX should succeed"); + let receipt = execute(&mut session, &tx, 0, 0, 0) + .expect("Executing TX should succeed"); let gas_spent = receipt.gas_spent; receipt.data.expect("Executed TX should not error"); @@ -274,11 +274,12 @@ fn stake_withdraw_unstake() { // filtered // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - let mut session = new_session(vm, base, CHAIN_ID, 3) + let mut session = vm + .session(base, CHAIN_ID, 3) .expect("Instantiating new session should succeed"); - let receipt = - execute(&mut session, tx).expect("Executing TX should succeed"); + let receipt = execute(&mut session, &tx, 0, 0, 0) + .expect("Executing TX should succeed"); update_root(&mut session).expect("Updating the root should succeed"); let gas_spent = receipt.gas_spent; diff --git a/contracts/transfer/tests/common/utils.rs b/contracts/transfer/tests/common/utils.rs index c42bfd22b5..82f26c52e8 100644 --- a/contracts/transfer/tests/common/utils.rs +++ b/contracts/transfer/tests/common/utils.rs @@ -6,13 +6,13 @@ use std::sync::mpsc; -use dusk_core::abi::{ContractError, ContractId}; +use dusk_core::abi::ContractId; use dusk_core::signatures::bls::PublicKey as AccountPublicKey; use dusk_core::transfer::moonlight::AccountData; use dusk_core::transfer::phoenix::{Note, NoteLeaf, ViewKey as PhoenixViewKey}; -use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT}; +use dusk_core::transfer::TRANSFER_CONTRACT; use dusk_core::BlsScalar; -use dusk_vm::{CallReceipt, Error as VMError, Session}; +use dusk_vm::{Error as VMError, Session}; const GAS_LIMIT: u64 = 0x10_000_000; @@ -31,40 +31,6 @@ pub fn chain_id(session: &mut Session) -> Result { .map(|r| r.data) } -/// Executes a transaction. -/// Returns result containing gas spent. -pub fn execute( - session: &mut Session, - tx: impl Into, -) -> Result, ContractError>>, VMError> { - let tx = tx.into(); - - let mut receipt = session.call::<_, Result, ContractError>>( - TRANSFER_CONTRACT, - "spend_and_execute", - &tx, - tx.gas_limit(), - )?; - - // 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; - } - - let refund_receipt = session - .call::<_, ()>( - TRANSFER_CONTRACT, - "refund", - &receipt.gas_spent, - u64::MAX, - ) - .expect("Refunding must succeed"); - - receipt.events.extend(refund_receipt.events); - - Ok(receipt) -} - // moonlight helper functions pub fn account( diff --git a/contracts/transfer/tests/moonlight.rs b/contracts/transfer/tests/moonlight.rs index 5f8f0dfc19..b2ab55c4fa 100644 --- a/contracts/transfer/tests/moonlight.rs +++ b/contracts/transfer/tests/moonlight.rs @@ -4,17 +4,6 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -pub mod common; - -use crate::common::utils::{ - account, chain_id, contract_balance, execute, existing_nullifiers, - filter_notes_owned_by, leaves_from_height, owned_notes_value, update_root, -}; - -use ff::Field; -use rand::rngs::StdRng; -use rand::SeedableRng; - use dusk_core::abi::{ContractError, ContractId}; use dusk_core::signatures::bls::{ PublicKey as AccountPublicKey, SecretKey as AccountSecretKey, @@ -29,10 +18,19 @@ use dusk_core::transfer::withdraw::{ Withdraw, WithdrawReceiver, WithdrawReplayToken, }; use dusk_core::transfer::{ - ContractToAccount, ContractToContract, TRANSFER_CONTRACT, + ContractToAccount, ContractToContract, Transaction, TRANSFER_CONTRACT, }; use dusk_core::{dusk, JubJubScalar, LUX}; -use dusk_vm::{new_genesis_session, new_session, ContractData, Session, VM}; +use dusk_vm::{execute, ContractData, Session, VM}; +use ff::Field; +use rand::rngs::StdRng; +use rand::SeedableRng; + +pub mod common; +use crate::common::utils::{ + account, chain_id, contract_balance, existing_nullifiers, + filter_notes_owned_by, leaves_from_height, owned_notes_value, update_root, +}; const MOONLIGHT_GENESIS_VALUE: u64 = dusk(1_000.0); const MOONLIGHT_GENESIS_NONCE: u64 = 0; @@ -70,7 +68,7 @@ fn instantiate(moonlight_pk: &AccountPublicKey) -> Session { let vm = &mut VM::ephemeral().expect("Creating ephemeral VM should work"); - let mut session = new_genesis_session(vm, CHAIN_ID); + let mut session = vm.genesis_session(CHAIN_ID); session .deploy( @@ -125,7 +123,8 @@ fn instantiate(moonlight_pk: &AccountPublicKey) -> Session { // operations to 1 let base = session.commit().expect("Committing should succeed"); // start a new session from that base-commit - let mut session = new_session(vm, base, CHAIN_ID, 1) + let mut session = vm + .session(base, CHAIN_ID, 1) .expect("Instantiating new session should succeed"); // check genesis state @@ -171,7 +170,7 @@ fn transfer() { let session = &mut instantiate(&moonlight_sender_pk); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sender_sk, Some(moonlight_receiver_pk), TRANSFER_VALUE, @@ -184,7 +183,7 @@ fn transfer() { ) .expect("Creating moonlight transaction should succeed"); - let gas_spent = execute(session, transaction) + let gas_spent = execute(session, &transaction, 0, 0, 0) .expect("Transaction should succeed") .gas_spent; @@ -233,7 +232,7 @@ fn transfer_with_refund() { "The receiver account should be empty" ); - let transaction = MoonlightTransaction::new_with_refund( + let transaction: Transaction = MoonlightTransaction::new_with_refund( &moonlight_sender_sk, &moonlight_refund_pk, Some(moonlight_receiver_pk), @@ -245,10 +244,11 @@ fn transfer_with_refund() { CHAIN_ID, None::, ) - .expect("Creating moonlight transaction should succeed"); + .expect("Creating moonlight transaction should succeed") + .into(); let max_gas = GAS_LIMIT * LUX; - let gas_spent = execute(session, transaction) + let gas_spent = execute(session, &transaction, 0, 0, 0) .expect("Transaction should succeed") .gas_spent; let gas_refund = max_gas - gas_spent; @@ -300,7 +300,7 @@ fn transfer_gas_fails() { "The receiver account should be empty" ); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sender_sk, Some(moonlight_receiver_pk), TRANSFER_VALUE, @@ -313,7 +313,7 @@ fn transfer_gas_fails() { ) .expect("Creating moonlight transaction should succeed"); - let result = execute(session, transaction); + let result = execute(session, &transaction, 0, 0, 0); assert!( result.is_err(), @@ -352,7 +352,7 @@ fn alice_ping() { fn_args: vec![], }); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sk, None, 0, @@ -365,7 +365,7 @@ fn alice_ping() { ) .expect("Creating moonlight transaction should succeed"); - let gas_spent = execute(session, transaction) + let gas_spent = execute(session, &transaction, 0, 0, 0) .expect("Transaction should succeed") .gas_spent; @@ -432,7 +432,7 @@ fn convert_to_phoenix() { .to_vec(), }; - let tx = MoonlightTransaction::new( + let tx = Transaction::moonlight( &moonlight_sk, None, 0, @@ -446,7 +446,7 @@ fn convert_to_phoenix() { ) .expect("Creating moonlight transaction should succeed"); - let gas_spent = execute(&mut session, tx) + let gas_spent = execute(&mut session, &tx, 0, 0, 0) .expect("Executing transaction should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -552,7 +552,7 @@ fn convert_to_moonlight_fails() { .to_vec(), }; - let tx = MoonlightTransaction::new( + let tx = Transaction::moonlight( &moonlight_sk, None, 0, @@ -566,8 +566,8 @@ fn convert_to_moonlight_fails() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = - execute(&mut session, tx).expect("Executing TX should succeed"); + let receipt = execute(&mut session, &tx, 0, 0, 0) + .expect("Executing TX should succeed"); // check that the transaction execution panicked with the correct message assert!(receipt.data.is_err()); @@ -659,7 +659,7 @@ fn convert_wrong_contract_targeted() { .to_vec(), }; - let tx = MoonlightTransaction::new( + let tx = Transaction::moonlight( &moonlight_sk, None, 0, @@ -672,7 +672,7 @@ fn convert_wrong_contract_targeted() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = execute(&mut session, tx) + let receipt = execute(&mut session, &tx, 0, 0, 0) .expect("Executing transaction should succeed"); update_root(session).expect("Updating the root should succeed"); @@ -731,7 +731,7 @@ fn contract_to_contract() { .to_vec(), }); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sk, None, 0, @@ -744,8 +744,8 @@ fn contract_to_contract() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = - execute(session, transaction).expect("Transaction should succeed"); + let receipt = execute(session, &transaction, 0, 0, 0) + .expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!("SEND TO CONTRACT: {:?}", receipt.data); @@ -798,7 +798,7 @@ fn contract_to_account() { .to_vec(), }); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sk, None, 0, @@ -811,8 +811,8 @@ fn contract_to_account() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = - execute(session, transaction).expect("Transaction should succeed"); + let receipt = execute(session, &transaction, 0, 0, 0) + .expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!("SEND TO ACCOUNT: {:?}", receipt.data); @@ -862,7 +862,7 @@ fn contract_to_account_insufficient_funds() { .to_vec(), }); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sk, None, 0, @@ -875,8 +875,8 @@ fn contract_to_account_insufficient_funds() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = - execute(session, transaction).expect("Transaction should succeed"); + let receipt = execute(session, &transaction, 0, 0, 0) + .expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!("SEND TO ACCOUNT (insufficient funds): {:?}", receipt.data); @@ -933,7 +933,7 @@ fn contract_to_account_direct_call() { .to_vec(), }); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sk, None, 0, @@ -946,8 +946,8 @@ fn contract_to_account_direct_call() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = - execute(session, transaction).expect("Transaction should succeed"); + let receipt = execute(session, &transaction, 0, 0, 0) + .expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!( diff --git a/contracts/transfer/tests/phoenix.rs b/contracts/transfer/tests/phoenix.rs index 97496751cb..e481c57164 100644 --- a/contracts/transfer/tests/phoenix.rs +++ b/contracts/transfer/tests/phoenix.rs @@ -24,20 +24,17 @@ use dusk_core::transfer::withdraw::{ Withdraw, WithdrawReceiver, WithdrawReplayToken, }; use dusk_core::transfer::{ - ContractToAccount, ContractToContract, TRANSFER_CONTRACT, + ContractToAccount, ContractToContract, Transaction, TRANSFER_CONTRACT, }; use dusk_core::{BlsScalar, JubJubScalar, LUX}; -use dusk_vm::{ - new_genesis_session, new_session, ContractData, Error as VMError, Session, - VM, -}; +use dusk_vm::{execute, ContractData, Error as VMError, Session, VM}; use ff::Field; use rand::rngs::StdRng; use rand::{CryptoRng, RngCore, SeedableRng}; use rusk_prover::LocalProver; use crate::common::utils::{ - account, chain_id, contract_balance, execute, existing_nullifiers, + account, chain_id, contract_balance, existing_nullifiers, filter_notes_owned_by, leaves_from_height, new_owned_notes_value, owned_notes_value, update_root, }; @@ -84,7 +81,7 @@ fn instantiate( let vm = &mut VM::ephemeral().expect("Creating ephemeral VM should work"); - let mut session = new_genesis_session(vm, CHAIN_ID); + let mut session = vm.genesis_session(CHAIN_ID); session .deploy( @@ -160,7 +157,8 @@ fn instantiate( // operations to 1 let base = session.commit().expect("Committing should succeed"); // start a new session from that base-commit - let mut session = new_session(vm, base, CHAIN_ID, 1) + let mut session = vm + .session(base, CHAIN_ID, 1) .expect("Instantiating new session should succeed"); // check that the genesis state is correct: @@ -254,7 +252,7 @@ fn transfer_1_2() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -361,7 +359,7 @@ fn transfer_2_2() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -469,7 +467,7 @@ fn transfer_3_2() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -577,7 +575,7 @@ fn transfer_4_2() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -681,7 +679,7 @@ fn transfer_gas_fails() { let total_num_notes_before_tx = num_notes(session).expect("Getting num_notes should succeed"); - let result = execute(session, tx); + let result = execute(session, &tx, 0, 0, 0); assert!( result.is_err(), @@ -752,7 +750,7 @@ fn alice_ping() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -822,7 +820,7 @@ fn contract_deposit() { contract_call, ); - let gas_spent = execute(session, tx.clone()) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -835,7 +833,7 @@ fn contract_deposit() { PHOENIX_GENESIS_VALUE, transfer_value + tx.deposit() - + tx.max_fee() + + tx.gas_limit() * tx.gas_price() + tx.outputs()[1] .value(Some(&PhoenixViewKey::from(&phoenix_sender_sk))) .unwrap() @@ -930,7 +928,7 @@ fn contract_withdraw() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -1056,7 +1054,8 @@ fn convert_to_phoenix_fails() { Some(contract_call), ); - let receipt = execute(session, tx).expect("Executing TX should succeed"); + let receipt = + execute(session, &tx, 0, 0, 0).expect("Executing TX should succeed"); // check that the transaction execution panicked with the correct message assert!(receipt.data.is_err()); @@ -1176,7 +1175,7 @@ fn convert_to_moonlight() { Some(contract_call), ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -1286,7 +1285,7 @@ fn convert_wrong_contract_targeted() { Some(contract_call), ); - let receipt = execute(&mut session, tx) + let receipt = execute(&mut session, &tx, 0, 0, 0) .expect("Executing transaction should succeed"); update_root(session).expect("Updating the root should succeed"); @@ -1377,7 +1376,8 @@ fn contract_to_contract() { Some(contract_call), ); - let receipt = execute(session, tx).expect("Transaction should succeed"); + let receipt = + execute(session, &tx, 0, 0, 0).expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!("CONTRACT TO CONTRACT: {gas_spent} gas"); @@ -1470,7 +1470,8 @@ fn contract_to_account() { Some(contract_call), ); - let receipt = execute(session, tx).expect("Transaction should succeed"); + let receipt = + execute(session, &tx, 0, 0, 0).expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!("CONTRACT TO ACCOUNT: {gas_spent} gas"); @@ -1585,7 +1586,7 @@ fn create_phoenix_transaction( obfuscated_transaction: bool, deposit: u64, data: Option>, -) -> PhoenixTransaction { +) -> Transaction { // Get the root of the tree of phoenix-notes. let root = root(session).expect("Getting the anchor should be successful"); @@ -1629,4 +1630,5 @@ fn create_phoenix_transaction( &LocalProver, ) .expect("creating the creation shouldn't fail") + .into() } diff --git a/core/src/abi.rs b/core/src/abi.rs index fd22be42e4..94a5232d4c 100644 --- a/core/src/abi.rs +++ b/core/src/abi.rs @@ -46,8 +46,6 @@ impl Query { #[cfg(feature = "abi")] pub(crate) mod host_queries { - use alloc::vec::Vec; - #[cfg(feature = "abi-debug")] pub use piecrust_uplink::debug as piecrust_debug; pub use piecrust_uplink::{ @@ -57,6 +55,8 @@ pub(crate) mod host_queries { * spend_and_execute */ }; + use alloc::vec::Vec; + use dusk_bytes::Serializable; use piecrust_uplink::{host_query, meta_data}; diff --git a/core/src/error.rs b/core/src/error.rs index d4e5bfcd65..66a1a0a3cc 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -39,7 +39,7 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Execution-Core Error: {:?}", &self) + write!(f, "Dusk-Core Error: {:?}", &self) } } diff --git a/core/src/transfer.rs b/core/src/transfer.rs index 47cedd9a2b..4b2d8f8914 100644 --- a/core/src/transfer.rs +++ b/core/src/transfer.rs @@ -9,6 +9,7 @@ use alloc::string::String; use alloc::vec::Vec; +use core::cmp::max; use core::fmt::Debug; use bytecheck::CheckBytes; @@ -339,6 +340,24 @@ impl Transaction { Self::Moonlight(tx) => tx.hash(), } } + + /// Returns the charge for a contract deployment. The deployment of a + /// contract will cost at least `min_deploy_points`. + /// If the transaction is not a deploy-transaction, the deploy-charge will + /// be 0. + #[must_use] + pub fn deploy_charge( + &self, + gas_per_deploy_byte: u64, + min_deploy_points: u64, + ) -> u64 { + if let Some(deploy) = self.deploy() { + let bytecode_len = deploy.bytecode.bytes.len() as u64; + max(bytecode_len * gas_per_deploy_byte, min_deploy_points) + } else { + 0 + } + } } impl From for Transaction { diff --git a/node/src/mempool.rs b/node/src/mempool.rs index 2d0470399e..a1ec8593d0 100644 --- a/node/src/mempool.rs +++ b/node/src/mempool.rs @@ -212,7 +212,7 @@ impl MempoolSrv { return Err(TxAcceptanceError::GasPriceTooLow(1)); } - if let Some(deploy) = tx.inner.deploy() { + if tx.inner.deploy().is_some() { let vm = vm.read().await; let min_deployment_gas_price = vm.min_deployment_gas_price(); if tx.gas_price() < min_deployment_gas_price { @@ -222,13 +222,11 @@ impl MempoolSrv { } let gas_per_deploy_byte = vm.gas_per_deploy_byte(); - let min_gas_limit = vm::bytecode_charge( - &deploy.bytecode, - gas_per_deploy_byte, - vm.min_deploy_points(), - ); - if tx.inner.gas_limit() < min_gas_limit { - return Err(TxAcceptanceError::GasLimitTooLow(min_gas_limit)); + let deploy_charge = tx + .inner + .deploy_charge(gas_per_deploy_byte, vm.min_deploy_points()); + if tx.inner.gas_limit() < deploy_charge { + return Err(TxAcceptanceError::GasLimitTooLow(deploy_charge)); } } else { let vm = vm.read().await; diff --git a/node/src/vm.rs b/node/src/vm.rs index dec91117e9..bc104b0a4b 100644 --- a/node/src/vm.rs +++ b/node/src/vm.rs @@ -9,11 +9,9 @@ use dusk_consensus::operations::{CallParams, VerificationOutput, Voter}; use dusk_consensus::user::provisioners::Provisioners; use dusk_consensus::user::stake::Stake; use dusk_core::signatures::bls::PublicKey as BlsPublicKey; -use dusk_core::transfer::data::ContractBytecode; use dusk_core::transfer::moonlight::AccountData; use node_data::events::contract::ContractEvent; use node_data::ledger::{Block, SpentTransaction, Transaction}; -use std::cmp::max; #[derive(Default)] pub struct Config {} @@ -102,15 +100,3 @@ pub enum PreverificationResult { nonce_used: u64, }, } - -// Returns gas charge for bytecode deployment. -pub fn bytecode_charge( - bytecode: &ContractBytecode, - gas_per_deploy_byte: u64, - min_deploy_points: u64, -) -> u64 { - max( - bytecode.bytes.len() as u64 * gas_per_deploy_byte, - min_deploy_points, - ) -} diff --git a/rusk-recovery/src/state.rs b/rusk-recovery/src/state.rs index ea60b9bef7..71a286557b 100644 --- a/rusk-recovery/src/state.rs +++ b/rusk-recovery/src/state.rs @@ -15,7 +15,7 @@ use dusk_core::stake::{StakeAmount, StakeData, StakeKeys, STAKE_CONTRACT}; use dusk_core::transfer::phoenix::{Note, PublicKey, Sender}; use dusk_core::transfer::TRANSFER_CONTRACT; use dusk_core::JubJubScalar; -use dusk_vm::{new_genesis_session, new_session, ContractData, Session, VM}; +use dusk_vm::{ContractData, Session, VM}; use ff::Field; use once_cell::sync::Lazy; use rand::rngs::StdRng; @@ -177,7 +177,7 @@ fn generate_empty_state>( let state_dir = state_dir.as_ref(); let vm = VM::new(state_dir)?; - let mut session = new_genesis_session(&vm, GENESIS_CHAIN_ID); + let mut session = vm.genesis_session(GENESIS_CHAIN_ID); let transfer_code = include_bytes!( "../../target/dusk/wasm64-unknown-unknown/release/transfer_contract.wasm" @@ -260,12 +260,8 @@ where None => generate_empty_state(state_dir, snapshot), }?; - let mut session = new_session( - &vm, - old_commit_id, - GENESIS_CHAIN_ID, - GENESIS_BLOCK_HEIGHT, - )?; + let mut session = + vm.session(old_commit_id, GENESIS_CHAIN_ID, GENESIS_BLOCK_HEIGHT)?; generate_transfer_state(&mut session, snapshot)?; generate_stake_state(&mut session, snapshot)?; diff --git a/rusk/src/lib/gen_id.rs b/rusk/src/lib/gen_id.rs deleted file mode 100644 index f93a0963cd..0000000000 --- a/rusk/src/lib/gen_id.rs +++ /dev/null @@ -1,57 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) DUSK NETWORK. All rights reserved. - -use blake2b_simd::Params; -use dusk_core::abi::{ContractId, CONTRACT_ID_BYTES}; - -/// Generate a [`ContractId`] address from: -/// - slice of bytes, -/// - nonce -/// - owner -pub fn gen_contract_id( - bytes: impl AsRef<[u8]>, - nonce: u64, - owner: impl AsRef<[u8]>, -) -> ContractId { - let mut hasher = Params::new().hash_length(CONTRACT_ID_BYTES).to_state(); - hasher.update(bytes.as_ref()); - hasher.update(&nonce.to_le_bytes()[..]); - hasher.update(owner.as_ref()); - let hash_bytes: [u8; CONTRACT_ID_BYTES] = hasher - .finalize() - .as_bytes() - .try_into() - .expect("the hash result is exactly `CONTRACT_ID_BYTES` long"); - ContractId::from_bytes(hash_bytes) -} - -#[cfg(test)] -mod tests { - use super::*; - use rand::rngs::StdRng; - use rand::{RngCore, SeedableRng}; - - #[test] - fn test_gen_contract_id() { - let mut rng = StdRng::seed_from_u64(42); - - let mut bytes = vec![0; 1000]; - rng.fill_bytes(&mut bytes); - - let nonce = rng.next_u64(); - - let mut owner = vec![0, 100]; - rng.fill_bytes(&mut owner); - - let contract_id = - gen_contract_id(bytes.as_slice(), nonce, owner.as_slice()); - - assert_eq!( - hex::encode(contract_id.as_bytes()), - "2da8b6277789a88c7215789e227ef4dd97486db252e554805c7b874a17e07785" - ); - } -} diff --git a/rusk/src/lib/lib.rs b/rusk/src/lib/lib.rs index 541c42bc2b..603cef789f 100644 --- a/rusk/src/lib/lib.rs +++ b/rusk/src/lib/lib.rs @@ -8,7 +8,6 @@ mod bloom; mod error; -pub mod gen_id; pub mod http; #[cfg(feature = "chain")] pub mod node; diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs index 7d39c289c2..e0b2ec3043 100644 --- a/rusk/src/lib/node/rusk.rs +++ b/rusk/src/lib/node/rusk.rs @@ -16,19 +16,16 @@ use dusk_consensus::config::{ RATIFICATION_COMMITTEE_CREDITS, VALIDATION_COMMITTEE_CREDITS, }; use dusk_consensus::operations::{CallParams, VerificationOutput, Voter}; -use dusk_core::abi::{ContractError, Event}; +use dusk_core::abi::Event; use dusk_core::signatures::bls::PublicKey as BlsPublicKey; use dusk_core::stake::{ Reward, RewardReason, StakeData, StakeKeys, STAKE_CONTRACT, }; use dusk_core::transfer::{ - data::ContractDeploy, moonlight::AccountData, - Transaction as ProtocolTransaction, PANIC_NONCE_NOT_READY, - TRANSFER_CONTRACT, + moonlight::AccountData, PANIC_NONCE_NOT_READY, TRANSFER_CONTRACT, }; use dusk_core::{BlsScalar, Dusk}; -use dusk_vm::{new_session, CallReceipt, Error as VMError, Session, VM}; -use node::vm::bytecode_charge; +use dusk_vm::{execute, CallReceipt, Error as VMError, Session, VM}; use node::DUSK_CONSENSUS_KEY; use node_data::events::contract::{ContractEvent, ContractTxEvent}; use node_data::ledger::{Hash, Slash, SpentTransaction, Transaction}; @@ -40,7 +37,6 @@ use tracing::info; use {node_data::archive::ArchivalData, tokio::sync::mpsc::Sender}; use crate::bloom::Bloom; -use crate::gen_id::gen_contract_id; use crate::http::RuesEvent; use crate::node::{coinbase_value, Rusk, RuskTip}; use crate::Error::InvalidCreditsCount; @@ -530,8 +526,7 @@ impl Rusk { tip.current }); - let session = - new_session(&self.vm, commit, self.chain_id, block_height)?; + let session = self.vm.session(commit, self.chain_id, block_height)?; Ok(session) } @@ -550,7 +545,7 @@ impl Rusk { for d in to_merge { if d == base { // Don't finalize the new tip, otherwise it will not be - // aceessible anymore + // accessible anymore continue; }; self.vm.finalize_commit(d)?; @@ -665,163 +660,6 @@ fn accept( )) } -// Contract deployment will fail and charge full gas limit in the -// following cases: -// 1) Transaction gas limit is smaller than deploy charge plus gas used for -// spending funds. -// 2) Transaction's bytecode's bytes are not consistent with bytecode's hash. -// 3) Deployment fails for deploy-specific reasons like e.g.: -// - contract already deployed -// - corrupted bytecode -// - sufficient gas to spend funds yet insufficient for deployment -fn contract_deploy( - session: &mut Session, - deploy: &ContractDeploy, - gas_limit: u64, - gas_per_deploy_byte: u64, - min_deploy_points: u64, - receipt: &mut CallReceipt, ContractError>>, -) { - let deploy_charge = bytecode_charge( - &deploy.bytecode, - gas_per_deploy_byte, - min_deploy_points, - ); - let min_gas_limit = receipt.gas_spent + deploy_charge; - let hash = blake3::hash(deploy.bytecode.bytes.as_slice()); - if gas_limit < min_gas_limit { - receipt.data = Err(ContractError::OutOfGas); - } else if hash != deploy.bytecode.hash { - receipt.data = - Err(ContractError::Panic("failed bytecode hash check".into())) - } else { - let result = session.deploy_raw( - Some(gen_contract_id( - &deploy.bytecode.bytes, - deploy.nonce, - &deploy.owner, - )), - deploy.bytecode.bytes.as_slice(), - deploy.init_args.clone(), - deploy.owner.clone(), - gas_limit, - ); - match result { - // Should the gas spent by the INIT method charged too? - Ok(_) => receipt.gas_spent += deploy_charge, - Err(err) => { - info!("Tx caused deployment error {err:?}"); - let msg = format!("failed deployment: {err:?}"); - receipt.data = Err(ContractError::Panic(msg)) - } - } - } -} - -/// Executes a transaction, returning the receipt of the call and the gas spent. -/// The following steps are performed: -/// -/// 1. Check if the transaction contains contract deployment data, and if so, -/// verifies if gas limit is enough for deployment and if the gas price is -/// sufficient for deployment. If either gas price or gas limit is not -/// sufficient for deployment, transaction is discarded. -/// -/// 2. Call the "spend_and_execute" function on the transfer contract with -/// unlimited gas. If this fails, an error is returned. If an error is -/// returned the transaction should be considered unspendable/invalid, but no -/// re-execution of previous transactions is required. -/// -/// 3. If the transaction contains contract deployment data, additional checks -/// are performed and if they pass, deployment is executed. The following -/// checks are performed: -/// - gas limit should be is smaller than deploy charge plus gas used for -/// spending funds -/// - transaction's bytecode's bytes are consistent with bytecode's hash -/// Deployment execution may fail for deployment-specific reasons, such as -/// for example: -/// - contract already deployed -/// - corrupted bytecode -/// If deployment execution fails, the entire gas limit is consumed and error -/// is returned. -/// -/// 4. Call the "refund" function on the transfer contract with unlimited gas. -/// The amount charged depends on the gas spent by the transaction, and the -/// optional contract call in steps 2 or 3. -/// -/// Note that deployment transaction will never be re-executed for reasons -/// related to deployment, as it is either discarded or it charges the -/// full gas limit. It might be re-executed only if some other transaction -/// failed to fit the block. -fn execute( - session: &mut Session, - tx: &ProtocolTransaction, - gas_per_deploy_byte: u64, - min_deploy_points: u64, - min_deployment_gas_price: u64, -) -> Result, ContractError>>, VMError> { - // Transaction will be discarded if it is a deployment transaction - // with gas limit smaller than deploy charge. - if let Some(deploy) = tx.deploy() { - let deploy_charge = bytecode_charge( - &deploy.bytecode, - gas_per_deploy_byte, - min_deploy_points, - ); - if tx.gas_price() < min_deployment_gas_price { - return Err(VMError::Panic("gas price too low to deploy".into())); - } - if tx.gas_limit() < deploy_charge { - return Err(VMError::Panic("not enough gas to deploy".into())); - } - } - - let tx_stripped = tx.strip_off_bytecode(); - // Spend the inputs and execute the call. If this errors the transaction is - // unspendable. - let mut receipt = session.call::<_, Result, ContractError>>( - TRANSFER_CONTRACT, - "spend_and_execute", - tx_stripped.as_ref().unwrap_or(tx), - tx.gas_limit(), - )?; - - // Deploy if this is a deployment transaction and spend part is successful. - if let Some(deploy) = tx.deploy() { - let gas_left = tx.gas_limit() - receipt.gas_spent; - if receipt.data.is_ok() { - contract_deploy( - session, - deploy, - gas_left, - gas_per_deploy_byte, - min_deploy_points, - &mut receipt, - ); - } - }; - - // 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; - } - - // Refund the appropriate amount to the transaction. This call is guaranteed - // to never error. If it does, then a programming error has occurred. As - // such, the call to `Result::expect` is warranted. - let refund_receipt = session - .call::<_, ()>( - TRANSFER_CONTRACT, - "refund", - &receipt.gas_spent, - u64::MAX, - ) - .expect("Refunding must succeed"); - - receipt.events.extend(refund_receipt.events); - - Ok(receipt) -} - fn reward_slash_and_update_root( session: &mut Session, block_height: u64, diff --git a/rusk/tests/rusk-state.rs b/rusk/tests/rusk-state.rs index 505978624d..d9d687d296 100644 --- a/rusk/tests/rusk-state.rs +++ b/rusk/tests/rusk-state.rs @@ -83,9 +83,9 @@ where rusk.with_tip(|mut tip, vm| { let current_commit = tip.current; - let mut session = - dusk_vm::new_session(vm, current_commit, CHAIN_ID, BLOCK_HEIGHT) - .expect("current commit should exist"); + let mut session = vm + .session(current_commit, CHAIN_ID, BLOCK_HEIGHT) + .expect("current commit should exist"); session .call::<_, Note>( diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs index 83c0fdd09d..66701e8374 100644 --- a/rusk/tests/services/contract_deployment.rs +++ b/rusk/tests/services/contract_deployment.rs @@ -12,10 +12,9 @@ use dusk_core::abi::ContractId; use dusk_core::transfer::data::{ ContractBytecode, ContractDeploy, TransactionData, }; -use dusk_vm::{new_session, ContractData, Error as VMError, VM}; +use dusk_vm::{gen_contract_id, ContractData, Error as VMError, VM}; use rand::prelude::*; use rand::rngs::StdRng; -use rusk::gen_id::gen_contract_id; use rusk::{Result, Rusk}; use rusk_recovery_tools::state; use tempfile::tempdir; @@ -232,7 +231,8 @@ impl Fixture { let commit = self.rusk.state_root(); let vm = VM::new(self.path.as_path()).expect("VM creation should succeed"); - let mut session = new_session(&vm, commit, CHAIN_ID, 0) + let mut session = vm + .session(commit, CHAIN_ID, 0) .expect("Session creation should succeed"); let result = session.call::<_, u64>( self.contract_id, @@ -250,7 +250,8 @@ impl Fixture { let commit = self.rusk.state_root(); let vm = VM::new(self.path.as_path()).expect("VM creation should succeed"); - let mut session = new_session(&vm, commit, CHAIN_ID, 0) + let mut session = vm + .session(commit, CHAIN_ID, 0) .expect("Session creation should succeed"); let result = session.call::<_, u64>( self.contract_id, diff --git a/rusk/tests/services/contract_stake.rs b/rusk/tests/services/contract_stake.rs index 83a853f4b8..90bee3616e 100644 --- a/rusk/tests/services/contract_stake.rs +++ b/rusk/tests/services/contract_stake.rs @@ -13,10 +13,10 @@ use dusk_bytes::Serializable; use dusk_core::abi::ContractId; use dusk_core::transfer::data::ContractCall; use dusk_core::transfer::{self, Transaction}; +use dusk_vm::gen_contract_id; use node_data::ledger::SpentTransaction; use rand::prelude::*; use rand::rngs::StdRng; -use rusk::gen_id::gen_contract_id; use rusk::{Result, Rusk}; use std::collections::HashMap; use tempfile::tempdir; diff --git a/rusk/tests/services/owner_calls.rs b/rusk/tests/services/owner_calls.rs index 4516141501..a4d450c397 100644 --- a/rusk/tests/services/owner_calls.rs +++ b/rusk/tests/services/owner_calls.rs @@ -19,8 +19,7 @@ use dusk_core::signatures::bls::{ PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, Signature as BlsSignature, }; -use dusk_vm::{new_session, CallReceipt, ContractData, Session, VM}; -use rusk::gen_id::gen_contract_id; +use dusk_vm::{gen_contract_id, CallReceipt, ContractData, Session, VM}; use rusk::{Error, Result, Rusk}; use rusk_recovery_tools::state; use tempfile::tempdir; @@ -146,7 +145,8 @@ impl Fixture { let commit = self.rusk.state_root(); let vm = VM::new(self.path.as_path()).expect("VM creation should succeed"); - let mut session = new_session(&vm, commit, CHAIN_ID, 0) + let mut session = vm + .session(commit, CHAIN_ID, 0) .expect("Session creation should succeed"); let result = session.call::<_, u64>( self.contract_id, @@ -171,7 +171,7 @@ impl Fixture { let vm = VM::new(self.path.as_path()).expect("VM creation should succeed"); self.session = Some( - new_session(&vm, commit, CHAIN_ID, 0) + vm.session(commit, CHAIN_ID, 0) .expect("Session creation should succeed"), ); } diff --git a/vm/Cargo.toml b/vm/Cargo.toml index b5fb1ab8fd..cab154ef56 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -14,6 +14,7 @@ dusk-bytes = { workspace = true } piecrust = { workspace = true } lru = { workspace = true } blake2b_simd = { workspace = true } +blake3 = { workspace = true } dusk-poseidon = { workspace = true } rkyv = { workspace = true, features = ["size_32"] } diff --git a/vm/rustfmt.toml b/vm/rustfmt.toml deleted file mode 100644 index 3450fc407a..0000000000 --- a/vm/rustfmt.toml +++ /dev/null @@ -1,2 +0,0 @@ -max_width = 80 -wrap_comments = true diff --git a/vm/src/cache.rs b/vm/src/cache.rs index 103b73b607..a70ea16335 100644 --- a/vm/src/cache.rs +++ b/vm/src/cache.rs @@ -76,7 +76,7 @@ define_cache!( with_plonk_cache, bool, 512, - "RUSK_ABI_PLONK_CACHE_SIZE" + "DUSK_VM_PLONK_CACHE_SIZE" ); define_cache!( get_groth16_verification, @@ -84,7 +84,7 @@ define_cache!( with_groth16_cache, bool, 512, - "RUSK_ABI_GROTH16_CACHE_SIZE" + "DUSK_VM_GROTH16_CACHE_SIZE" ); define_cache!( get_bls_verification, @@ -92,5 +92,5 @@ define_cache!( with_bls_cache, bool, 512, - "RUSK_ABI_BLS_CACHE_SIZE" + "DUSK_VM_BLS_CACHE_SIZE" ); diff --git a/vm/src/execute.rs b/vm/src/execute.rs new file mode 100644 index 0000000000..6d6900e06a --- /dev/null +++ b/vm/src/execute.rs @@ -0,0 +1,243 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use blake2b_simd::Params; +use dusk_core::abi::{ContractError, ContractId, CONTRACT_ID_BYTES}; +use dusk_core::transfer::{ + data::ContractBytecode, Transaction, TRANSFER_CONTRACT, +}; +use piecrust::{CallReceipt, Error, Session}; + +/// Executes a transaction, returning the receipt of the call and the gas spent. +/// The following steps are performed: +/// +/// 1. Check if the transaction contains contract deployment data, and if so, +/// verifies if gas limit is enough for deployment and if the gas price is +/// sufficient for deployment. If either gas price or gas limit is not +/// sufficient for deployment, transaction is discarded. +/// +/// 2. Call the "spend_and_execute" function on the transfer contract with +/// unlimited gas. If this fails, an error is returned. If an error is +/// returned the transaction should be considered unspendable/invalid, but no +/// re-execution of previous transactions is required. +/// +/// 3. If the transaction contains contract deployment data, additional checks +/// are performed and if they pass, deployment is executed. The following +/// checks are performed: +/// - gas limit should be is smaller than deploy charge plus gas used for +/// spending funds +/// - transaction's bytecode's bytes are consistent with bytecode's hash +/// Deployment execution may fail for deployment-specific reasons, such as +/// for example: +/// - contract already deployed +/// - corrupted bytecode +/// If deployment execution fails, the entire gas limit is consumed and error +/// is returned. +/// +/// 4. Call the "refund" function on the transfer contract with unlimited gas. +/// The amount charged depends on the gas spent by the transaction, and the +/// optional contract call in steps 2 or 3. +/// +/// Note that deployment transaction will never be re-executed for reasons +/// related to deployment, as it is either discarded or it charges the +/// full gas limit. It might be re-executed only if some other transaction +/// failed to fit the block. +pub fn execute( + session: &mut Session, + tx: &Transaction, + gas_per_deploy_byte: u64, + min_deploy_points: u64, + min_deploy_gas_price: u64, +) -> Result, ContractError>>, Error> { + // Transaction will be discarded if it is a deployment transaction + // with gas limit smaller than deploy charge. + deploy_check(tx, gas_per_deploy_byte, min_deploy_gas_price)?; + + // Spend the inputs and execute the call. If this errors the transaction is + // unspendable. + let mut receipt = session.call::<_, Result, ContractError>>( + TRANSFER_CONTRACT, + "spend_and_execute", + tx.strip_off_bytecode().as_ref().unwrap_or(tx), + tx.gas_limit(), + )?; + + // Deploy if this is a deployment transaction and spend part is successful. + contract_deploy( + session, + tx, + gas_per_deploy_byte, + min_deploy_points, + &mut receipt, + ); + + // 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; + } + + // Refund the appropriate amount to the transaction. This call is guaranteed + // to never error. If it does, then a programming error has occurred. As + // such, the call to `Result::expect` is warranted. + let refund_receipt = session + .call::<_, ()>( + TRANSFER_CONTRACT, + "refund", + &receipt.gas_spent, + u64::MAX, + ) + .expect("Refunding must succeed"); + + receipt.events.extend(refund_receipt.events); + + Ok(receipt) +} + +fn deploy_check( + tx: &Transaction, + gas_per_deploy_byte: u64, + min_deploy_gas_price: u64, +) -> Result<(), Error> { + if tx.deploy().is_some() { + let deploy_charge = + tx.deploy_charge(gas_per_deploy_byte, min_deploy_gas_price); + + if tx.gas_price() < min_deploy_gas_price { + return Err(Error::Panic("gas price too low to deploy".into())); + } + if tx.gas_limit() < deploy_charge { + return Err(Error::Panic("not enough gas to deploy".into())); + } + } + + Ok(()) +} + +// Contract deployment will fail and charge full gas limit in the +// following cases: +// 1) Transaction gas limit is smaller than deploy charge plus gas used for +// spending funds. +// 2) Transaction's bytecode's bytes are not consistent with bytecode's hash. +// 3) Deployment fails for deploy-specific reasons like e.g.: +// - contract already deployed +// - corrupted bytecode +// - sufficient gas to spend funds yet insufficient for deployment +fn contract_deploy( + session: &mut Session, + tx: &Transaction, + gas_per_deploy_byte: u64, + min_deploy_points: u64, + receipt: &mut CallReceipt, ContractError>>, +) { + if let Some(deploy) = tx.deploy() { + let gas_left = tx.gas_limit() - receipt.gas_spent; + if receipt.data.is_ok() { + let deploy_charge = + tx.deploy_charge(gas_per_deploy_byte, min_deploy_points); + let min_gas_limit = receipt.gas_spent + deploy_charge; + if gas_left < min_gas_limit { + receipt.data = Err(ContractError::OutOfGas); + } else if !verify_bytecode_hash(&deploy.bytecode) { + receipt.data = Err(ContractError::Panic( + "failed bytecode hash check".into(), + )) + } else { + let result = session.deploy_raw( + Some(gen_contract_id( + &deploy.bytecode.bytes, + deploy.nonce, + &deploy.owner, + )), + deploy.bytecode.bytes.as_slice(), + deploy.init_args.clone(), + deploy.owner.clone(), + gas_left, + ); + match result { + // Should the gas spent by the INIT method charged too? + Ok(_) => receipt.gas_spent += deploy_charge, + Err(err) => { + let msg = format!("failed deployment: {err:?}"); + receipt.data = Err(ContractError::Panic(msg)) + } + } + } + } + } +} + +// Verifies that the stored contract bytecode hash is correct. +fn verify_bytecode_hash(bytecode: &ContractBytecode) -> bool { + let computed: [u8; 32] = blake3::hash(bytecode.bytes.as_slice()).into(); + + bytecode.hash == computed +} + +/// Generate a [`ContractId`] address from: +/// - slice of bytes, +/// - nonce +/// - owner +/// +/// # Panics +/// Panics if [blake2b-hasher] doesn't produce a [`CONTRACT_ID_BYTES`] +/// bytes long hash. +/// +/// [blake2b-hasher]: [`blake2b_simd::Params.finalize`] +pub fn gen_contract_id( + bytes: impl AsRef<[u8]>, + nonce: u64, + owner: impl AsRef<[u8]>, +) -> ContractId { + let mut hasher = Params::new().hash_length(CONTRACT_ID_BYTES).to_state(); + hasher.update(bytes.as_ref()); + hasher.update(&nonce.to_le_bytes()[..]); + hasher.update(owner.as_ref()); + let hash_bytes: [u8; CONTRACT_ID_BYTES] = hasher + .finalize() + .as_bytes() + .try_into() + .expect("the hash result is exactly `CONTRACT_ID_BYTES` long"); + ContractId::from_bytes(hash_bytes) +} + +#[cfg(test)] +mod tests { + use alloc::vec; + + // the `unused_crate_dependencies` lint complains for dev-dependencies that + // are only used in integration tests, so adding this work-around here + use ff as _; + use once_cell as _; + use rand::rngs::StdRng; + use rand::{RngCore, SeedableRng}; + + use super::*; + + #[test] + fn test_gen_contract_id() { + let mut rng = StdRng::seed_from_u64(42); + + let mut bytes = vec![0; 1000]; + rng.fill_bytes(&mut bytes); + + let nonce = rng.next_u64(); + + let mut owner = vec![0, 100]; + rng.fill_bytes(&mut owner); + + let contract_id = + gen_contract_id(bytes.as_slice(), nonce, owner.as_slice()); + + assert_eq!( + contract_id.as_bytes(), + [ + 45, 168, 182, 39, 119, 137, 168, 140, 114, 21, 120, 158, 34, + 126, 244, 221, 151, 72, 109, 178, 82, 229, 84, 128, 92, 123, + 135, 74, 23, 224, 119, 133 + ] + ); + } +} diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 4de3468896..444c1fb2bc 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -13,6 +13,7 @@ extern crate alloc; +pub use self::execute::{execute, gen_contract_id}; pub use piecrust::{ CallReceipt, CallTree, CallTreeElem, ContractData, Error, PageOpening, Session, @@ -32,35 +33,9 @@ use self::host_queries::{ }; pub(crate) mod cache; +mod execute; pub mod host_queries; -/// Create a new session based on the given `VM`. -pub fn new_session( - vm: &VM, - base: [u8; 32], - chain_id: u8, - block_height: u64, -) -> Result { - vm.session( - SessionData::builder() - .base(base) - .insert(Metadata::CHAIN_ID, chain_id)? - .insert(Metadata::BLOCK_HEIGHT, block_height)?, - ) -} - -/// Create a new genesis session based on the given [`VM`]. -pub fn new_genesis_session(vm: &VM, chain_id: u8) -> Session { - vm.session( - SessionData::builder() - .insert(Metadata::CHAIN_ID, chain_id) - .expect("Inserting chain ID in metadata should succeed") - .insert(Metadata::BLOCK_HEIGHT, 0) - .expect("Inserting block height in metadata should succeed"), - ) - .expect("Creating a genesis session should always succeed") -} - /// Dusk VM is a [`PiecrustVM`] enriched with the host functions specified in /// Dusk's ABI. pub struct VM(PiecrustVM); @@ -107,7 +82,8 @@ impl VM { Ok(vm) } - /// Spawn a [`Session`]. + /// Spawn a [`Session`] on top of the given `commit`, given the `chain_id` + /// and `block_height`. /// /// # Errors /// If base commit is provided but does not exist. @@ -115,9 +91,32 @@ impl VM { /// [`Session`]: Session pub fn session( &self, - data: impl Into, + base: [u8; 32], + chain_id: u8, + block_height: u64, ) -> Result { - self.0.session(data) + self.0.session( + SessionData::builder() + .base(base) + .insert(Metadata::CHAIN_ID, chain_id)? + .insert(Metadata::BLOCK_HEIGHT, block_height)?, + ) + } + + /// Spawn a new genesis-[`Session`] given the `chain_id` with a + /// `block_height` of 0. + pub fn genesis_session(&self, chain_id: u8) -> Session { + self.0 + .session( + SessionData::builder() + .insert(Metadata::CHAIN_ID, chain_id) + .expect("Inserting chain ID in metadata should succeed") + .insert(Metadata::BLOCK_HEIGHT, 0) + .expect( + "Inserting block height in metadata should succeed", + ), + ) + .expect("Creating a genesis session should always succeed") } /// Return all existing commits. @@ -171,12 +170,3 @@ impl VM { ); } } - -#[cfg(test)] -mod tests { - // the `unused_crate_dependencies` lint complains for dev-dependencies that - // are only used in integration tests, so adding this work-around here - use ff as _; - use once_cell as _; - use rand as _; -} diff --git a/vm/tests/vm.rs b/vm/tests/vm.rs index 2a17e6c17d..3478d10c31 100644 --- a/vm/tests/vm.rs +++ b/vm/tests/vm.rs @@ -66,7 +66,7 @@ fn instantiate(vm: &VM, height: u64) -> (Session, ContractId) { "../../target/dusk/wasm32-unknown-unknown/release/host_fn.wasm" ); - let mut session = dusk_vm::new_genesis_session(vm, CHAIN_ID); + let mut session = vm.genesis_session(CHAIN_ID); let contract_id = session .deploy( @@ -78,7 +78,8 @@ fn instantiate(vm: &VM, height: u64) -> (Session, ContractId) { let base = session.commit().expect("Committing should succeed"); - let session = dusk_vm::new_session(vm, base, CHAIN_ID, height) + let session = vm + .session(base, CHAIN_ID, height) .expect("Instantiating new session should succeed"); (session, contract_id)