Repository: dusk-network/rusk (language: rust)

Issue:
vm: Add transaction execution function
#### Summary
Add transaction execution function to our `VM`.

#### Relevant Context
The transaction execution function is currently defined in the rusk-node and not exposed.
This function would move to our `VM` so that it can be used in multiple places while ensuring the same functionality.
Some functions related to the contract-deployments also needed to move due to this effort.



Discussion / hints:

Fix patch:
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 2c31741493..a70f4910dd 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -18,6 +18,8 @@ rkyv = { workspace = true,  features = ["size_32"] }
 bytecheck = { workspace = true }
 rand = { workspace = true }
 ff = { workspace = true }
+blake3 = { workspace = true }
+blake2b_simd = { workspace = true } 
 
 # plonk dependencies
 dusk-plonk = { workspace = true, features = ["rkyv-impl", "alloc"], optional = true }
diff --git a/core/src/abi.rs b/core/src/abi.rs
index fd22be42e4..aca5e3f989 100644
--- a/core/src/abi.rs
+++ b/core/src/abi.rs
@@ -14,6 +14,8 @@ pub use piecrust_uplink::{
 #[cfg(feature = "abi")]
 pub use self::host_queries::*;
 
+use blake2b_simd::Params;
+
 /// Enum storing the metadata identifiers.
 pub enum Metadata {}
 
@@ -44,10 +46,70 @@ impl Query {
     pub const VERIFY_BLS_MULTISIG: &'static str = "verify_bls_multisig";
 }
 
+/// 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;
+
+    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
+            ]
+        );
+    }
+}
+
 #[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 +119,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/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<PhoenixTransaction> for Transaction {
diff --git a/core/src/transfer/data.rs b/core/src/transfer/data.rs
index a2685c100c..b4ebbcaa26 100644
--- a/core/src/transfer/data.rs
+++ b/core/src/transfer/data.rs
@@ -213,6 +213,14 @@ impl ContractBytecode {
         self.hash.to_vec()
     }
 
+    /// Verifies that the stored bytes-hash is correct.
+    #[must_use]
+    pub fn verify_hash(&self) -> bool {
+        let computed: [u8; 32] = blake3::hash(self.bytes.as_slice()).into();
+
+        self.hash == computed
+    }
+
     /// Serializes this object into a variable length buffer
     #[must_use]
     pub fn to_var_bytes(&self) -> Vec<u8> {
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/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..df707b2fc9 100644
--- a/rusk/src/lib/node/rusk.rs
+++ b/rusk/src/lib/node/rusk.rs
@@ -16,19 +16,18 @@ 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, new_session, 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 +39,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;
@@ -665,163 +663,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<Result<Vec<u8>, 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<CallReceipt<Result<Vec<u8>, 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<Vec<u8>, 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/vm/src/lib.rs b/vm/src/lib.rs
index 4de3468896..6c3bfdd432 100644
--- a/vm/src/lib.rs
+++ b/vm/src/lib.rs
@@ -23,7 +23,7 @@ use std::fmt::{self, Debug, Formatter};
 use std::path::{Path, PathBuf};
 use std::thread;
 
-use dusk_core::abi::{Metadata, Query};
+use dusk_core::abi::Query;
 use piecrust::{SessionData, VM as PiecrustVM};
 
 use self::host_queries::{
@@ -33,33 +33,10 @@ use self::host_queries::{
 
 pub(crate) mod cache;
 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<Session, Error> {
-    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")
-}
+pub(crate) mod session;
+pub use session::{
+    execute, genesis as new_genesis_session, new as new_session,
+};
 
 /// Dusk VM is a [`PiecrustVM`] enriched with the host functions specified in
 /// Dusk's ABI.
diff --git a/vm/src/session.rs b/vm/src/session.rs
new file mode 100644
index 0000000000..078a7eaa23
--- /dev/null
+++ b/vm/src/session.rs
@@ -0,0 +1,196 @@
+// 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 dusk_core::abi::{gen_contract_id, ContractError, Metadata};
+use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT};
+use piecrust::{CallReceipt, Error, Session, SessionData};
+
+use crate::VM;
+
+/// Create a new session based on the given `VM`.
+pub fn new(
+    vm: &VM,
+    base: [u8; 32],
+    chain_id: u8,
+    block_height: u64,
+) -> Result<Session, Error> {
+    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 genesis(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")
+}
+
+/// 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<CallReceipt<Result<Vec<u8>, 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<Vec<u8>, 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<Result<Vec<u8>, 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 !deploy.bytecode.verify_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_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))
+                    }
+                }
+            }
+        }
+    }
+}


Test patch:
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<I: IntoIterator<Item = Note>>(
         .collect()
 }
 
-/// Executes a transaction, returning the call receipt
-pub fn execute(
-    session: &mut Session,
-    tx: impl Into<Transaction>,
-) -> Result<CallReceipt<Result<Vec<u8>, 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<Vec<u8>, 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<const I: usize>(
diff --git a/contracts/stake/tests/partial_stake.rs b/contracts/stake/tests/partial_stake.rs
index 0db5c20efc..e4beb8d4ab 100644
--- a/contracts/stake/tests/partial_stake.rs
+++ b/contracts/stake/tests/partial_stake.rs
@@ -11,8 +11,8 @@ 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,
+    execute, new_genesis_session, new_session, ContractData, Error as VMError,
+    Session, VM,
 };
 use rand::rngs::StdRng;
 use rand::SeedableRng;
@@ -21,7 +21,6 @@ use wallet_core::transaction::{
 };
 
 pub mod common;
-
 use crate::common::assert::*;
 use crate::common::init::CHAIN_ID;
 use crate::common::utils::*;
@@ -63,7 +62,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 +90,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;
@@ -125,7 +124,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 +159,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 +193,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 +215,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;
@@ -246,7 +245,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 +271,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 +309,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 +352,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 +377,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 +412,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;
diff --git a/contracts/stake/tests/stake.rs b/contracts/stake/tests/stake.rs
index aed9e0f1c0..7088920763 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, new_session, 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");
@@ -181,8 +180,8 @@ fn stake_withdraw_unstake() {
     let mut session = new_session(vm, 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");
@@ -277,8 +276,8 @@ fn stake_withdraw_unstake() {
     let mut session = new_session(vm, 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<u8, VMError> {
         .map(|r| r.data)
 }
 
-/// Executes a transaction.
-/// Returns result containing gas spent.
-pub fn execute(
-    session: &mut Session,
-    tx: impl Into<Transaction>,
-) -> Result<CallReceipt<Result<Vec<u8>, ContractError>>, VMError> {
-    let tx = tx.into();
-
-    let mut receipt = session.call::<_, Result<Vec<u8>, 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..a42d80c3bb 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,21 @@ 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, new_genesis_session, new_session, 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;
@@ -171,7 +171,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 +184,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 +233,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 +245,11 @@ fn transfer_with_refund() {
         CHAIN_ID,
         None::<TransactionData>,
     )
-    .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 +301,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 +314,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 +353,7 @@ fn alice_ping() {
         fn_args: vec![],
     });
 
-    let transaction = MoonlightTransaction::new(
+    let transaction = Transaction::moonlight(
         &moonlight_sk,
         None,
         0,
@@ -365,7 +366,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 +433,7 @@ fn convert_to_phoenix() {
         .to_vec(),
     };
 
-    let tx = MoonlightTransaction::new(
+    let tx = Transaction::moonlight(
         &moonlight_sk,
         None,
         0,
@@ -446,7 +447,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 +553,7 @@ fn convert_to_moonlight_fails() {
         .to_vec(),
     };
 
-    let tx = MoonlightTransaction::new(
+    let tx = Transaction::moonlight(
         &moonlight_sk,
         None,
         0,
@@ -566,8 +567,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 +660,7 @@ fn convert_wrong_contract_targeted() {
         .to_vec(),
     };
 
-    let tx = MoonlightTransaction::new(
+    let tx = Transaction::moonlight(
         &moonlight_sk,
         None,
         0,
@@ -672,7 +673,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 +732,7 @@ fn contract_to_contract() {
         .to_vec(),
     });
 
-    let transaction = MoonlightTransaction::new(
+    let transaction = Transaction::moonlight(
         &moonlight_sk,
         None,
         0,
@@ -744,8 +745,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 +799,7 @@ fn contract_to_account() {
         .to_vec(),
     });
 
-    let transaction = MoonlightTransaction::new(
+    let transaction = Transaction::moonlight(
         &moonlight_sk,
         None,
         0,
@@ -811,8 +812,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 +863,7 @@ fn contract_to_account_insufficient_funds() {
         .to_vec(),
     });
 
-    let transaction = MoonlightTransaction::new(
+    let transaction = Transaction::moonlight(
         &moonlight_sk,
         None,
         0,
@@ -875,8 +876,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 +934,7 @@ fn contract_to_account_direct_call() {
         .to_vec(),
     });
 
-    let transaction = MoonlightTransaction::new(
+    let transaction = Transaction::moonlight(
         &moonlight_sk,
         None,
         0,
@@ -946,8 +947,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..0b3bbc0fb3 100644
--- a/contracts/transfer/tests/phoenix.rs
+++ b/contracts/transfer/tests/phoenix.rs
@@ -24,12 +24,12 @@ 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,
+    execute, new_genesis_session, new_session, ContractData, Error as VMError,
+    Session, VM,
 };
 use ff::Field;
 use rand::rngs::StdRng;
@@ -37,7 +37,7 @@ 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,
 };
@@ -254,7 +254,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 +361,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 +469,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 +577,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 +681,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 +752,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 +822,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 +835,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 +930,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 +1056,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 +1177,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 +1287,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 +1378,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 +1472,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 +1588,7 @@ fn create_phoenix_transaction<const I: usize>(
     obfuscated_transaction: bool,
     deposit: u64,
     data: Option<impl Into<TransactionData>>,
-) -> PhoenixTransaction {
+) -> Transaction {
     // Get the root of the tree of phoenix-notes.
     let root = root(session).expect("Getting the anchor should be successful");
 
@@ -1629,4 +1632,5 @@ fn create_phoenix_transaction<const I: usize>(
         &LocalProver,
     )
     .expect("creating the creation shouldn't fail")
+    .into()
 }
diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs
index 83c0fdd09d..4225ed43f6 100644
--- a/rusk/tests/services/contract_deployment.rs
+++ b/rusk/tests/services/contract_deployment.rs
@@ -8,14 +8,13 @@ use std::collections::HashMap;
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, RwLock};
 
-use dusk_core::abi::ContractId;
+use dusk_core::abi::{gen_contract_id, ContractId};
 use dusk_core::transfer::data::{
     ContractBytecode, ContractDeploy, TransactionData,
 };
 use dusk_vm::{new_session, 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;
diff --git a/rusk/tests/services/contract_stake.rs b/rusk/tests/services/contract_stake.rs
index 83a853f4b8..e9b37fcdd7 100644
--- a/rusk/tests/services/contract_stake.rs
+++ b/rusk/tests/services/contract_stake.rs
@@ -10,13 +10,12 @@ use std::sync::{Arc, RwLock};
 use dusk_core::stake::{self, Stake, DEFAULT_MINIMUM_STAKE, EPOCH};
 
 use dusk_bytes::Serializable;
-use dusk_core::abi::ContractId;
+use dusk_core::abi::{gen_contract_id, ContractId};
 use dusk_core::transfer::data::ContractCall;
 use dusk_core::transfer::{self, Transaction};
 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..8151c590c5 100644
--- a/rusk/tests/services/owner_calls.rs
+++ b/rusk/tests/services/owner_calls.rs
@@ -14,13 +14,12 @@ use std::collections::HashMap;
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, RwLock};
 
-use dusk_core::abi::ContractId;
+use dusk_core::abi::{gen_contract_id, ContractId};
 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 rusk::{Error, Result, Rusk};
 use rusk_recovery_tools::state;
 use tempfile::tempdir;