diff --git a/console/network/src/mainnet_v0.rs b/console/network/src/mainnet_v0.rs index 40e3013b09..b38e59b2c9 100644 --- a/console/network/src/mainnet_v0.rs +++ b/console/network/src/mainnet_v0.rs @@ -140,7 +140,7 @@ impl Network for MainnetV0 { // TODO (raychu86): Update this value based on the desired mainnet height. /// The block height from which consensus V2 rules apply. #[cfg(any(test, feature = "test"))] - const CONSENSUS_V2_HEIGHT: u32 = 0; + const CONSENSUS_V2_HEIGHT: u32 = 10; /// The network edition. const EDITION: u16 = 0; /// The genesis block coinbase target. diff --git a/ledger/query/src/query.rs b/ledger/query/src/query.rs index 07b4638c52..50e81fef17 100644 --- a/ledger/query/src/query.rs +++ b/ledger/query/src/query.rs @@ -139,6 +139,45 @@ impl> QueryTrait for Query { }, } } + + /// Returns a state path for the given `commitment`. + fn current_block_height(&self) -> Result { + match self { + Self::VM(block_store) => Ok(block_store.max_height().unwrap_or_default()), + Self::REST(url) => match N::ID { + console::network::MainnetV0::ID => { + Ok(Self::get_request(&format!("{url}/mainnet/block/height/latest"))?.into_json()?) + } + console::network::TestnetV0::ID => { + Ok(Self::get_request(&format!("{url}/testnet/block/height/latest"))?.into_json()?) + } + console::network::CanaryV0::ID => { + Ok(Self::get_request(&format!("{url}/canary/block/height/latest"))?.into_json()?) + } + _ => bail!("Unsupported network ID in inclusion query"), + }, + } + } + + /// Returns a state path for the given `commitment`. + #[cfg(feature = "async")] + async fn current_block_height_async(&self) -> Result { + match self { + Self::VM(block_store) => Ok(block_store.max_height().unwrap_or_default()), + Self::REST(url) => match N::ID { + console::network::MainnetV0::ID => { + Ok(Self::get_request_async(&format!("{url}/mainnet/block/height/latest")).await?.json().await?) + } + console::network::TestnetV0::ID => { + Ok(Self::get_request_async(&format!("{url}/testnet/block/height/latest")).await?.json().await?) + } + console::network::CanaryV0::ID => { + Ok(Self::get_request_async(&format!("{url}/canary/block/height/latest")).await?.json().await?) + } + _ => bail!("Unsupported network ID in inclusion query"), + }, + } + } } impl> Query { diff --git a/ledger/query/src/traits.rs b/ledger/query/src/traits.rs index 72b8c56868..4a5573ad23 100644 --- a/ledger/query/src/traits.rs +++ b/ledger/query/src/traits.rs @@ -30,4 +30,11 @@ pub trait QueryTrait { /// Returns a state path for the given `commitment`. #[cfg(feature = "async")] async fn get_state_path_for_commitment_async(&self, commitment: &Field) -> Result>; + + /// Returns the current block height + fn current_block_height(&self) -> Result; + + /// Returns the current block height + #[cfg(feature = "async")] + async fn current_block_height_async(&self) -> Result; } diff --git a/synthesizer/Cargo.toml b/synthesizer/Cargo.toml index 0d0ccccdad..a2f221836a 100644 --- a/synthesizer/Cargo.toml +++ b/synthesizer/Cargo.toml @@ -44,7 +44,7 @@ serial = [ "synthesizer-snark/serial" ] setup = [ ] -test = [ ] +test = [ "console/test" ] timer = [ "aleo-std/timer" ] wasm = [ "process", diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 7562aa5f57..fbb072c587 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -59,7 +59,7 @@ pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, ( } /// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, finalize cost)). -pub fn execution_cost(process: &Process, execution: &Execution) -> Result<(u64, (u64, u64))> { +pub fn execution_cost_v2(process: &Process, execution: &Execution) -> Result<(u64, (u64, u64))> { // Compute the storage cost in microcredits. let storage_cost = execution_storage_cost::(execution.size_in_bytes()?); @@ -77,6 +77,26 @@ pub fn execution_cost(process: &Process, execution: &Execution Ok((total_cost, (storage_cost, finalize_cost))) } +/// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, finalize cost)). +pub fn execution_cost_v1(process: &Process, execution: &Execution) -> Result<(u64, (u64, u64))> { + // Compute the storage cost in microcredits. + let storage_cost = execution_storage_cost::(execution.size_in_bytes()?); + + // Get the root transition. + let transition = execution.peek()?; + + // Get the finalize cost for the root transition. + let stack = process.get_stack(transition.program_id())?; + let finalize_cost = cost_in_microcredits_v1(stack, transition.function_name())?; + + // Compute the total cost in microcredits. + let total_cost = storage_cost + .checked_add(finalize_cost) + .ok_or(anyhow!("The total cost computation overflowed for an execution"))?; + + Ok((total_cost, (storage_cost, finalize_cost))) +} + /// Returns the storage cost in microcredits for a program execution. fn execution_storage_cost(size_in_bytes: u64) -> u64 { if size_in_bytes > N::EXECUTION_STORAGE_PENALTY_THRESHOLD { @@ -101,7 +121,13 @@ const HASH_BHP_PER_BYTE_COST: u64 = 300; const HASH_PSD_BASE_COST: u64 = 40_000; const HASH_PSD_PER_BYTE_COST: u64 = 75; -const MAPPING_BASE_COST: u64 = 10_000; +pub enum ConsensusFeeVersion { + V1, + V2, +} + +const MAPPING_BASE_COST_V1: u64 = 10_000; +const MAPPING_BASE_COST_V2: u64 = 1_500; const MAPPING_PER_BYTE_COST: u64 = 10; const SET_BASE_COST: u64 = 10_000; @@ -168,7 +194,17 @@ fn cost_in_size<'a, N: Network>( } /// Returns the the cost of a command in a finalize scope. -pub fn cost_per_command(stack: &Stack, finalize: &Finalize, command: &Command) -> Result { +pub fn cost_per_command( + stack: &Stack, + finalize: &Finalize, + command: &Command, + consensus_fee_version: ConsensusFeeVersion, +) -> Result { + let mapping_base_cost = match consensus_fee_version { + ConsensusFeeVersion::V1 => MAPPING_BASE_COST_V1, + ConsensusFeeVersion::V2 => MAPPING_BASE_COST_V2, + }; + match command { Command::Instruction(Instruction::Abs(_)) => Ok(500), Command::Instruction(Instruction::AbsWrapped(_)) => Ok(500), @@ -348,16 +384,16 @@ pub fn cost_per_command(stack: &Stack, finalize: &Finalize, co Command::Instruction(Instruction::Xor(_)) => Ok(500), Command::Await(_) => Ok(500), Command::Contains(command) => { - cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, MAPPING_BASE_COST) + cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) } Command::Get(command) => { - cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, MAPPING_BASE_COST) + cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) } Command::GetOrUse(command) => { - cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, MAPPING_BASE_COST) + cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) } Command::RandChaCha(_) => Ok(25_000), - Command::Remove(_) => Ok(MAPPING_BASE_COST), + Command::Remove(_) => Ok(SET_BASE_COST), Command::Set(command) => { cost_in_size(stack, finalize, [command.key(), command.value()], SET_PER_BYTE_COST, SET_BASE_COST) } @@ -367,7 +403,7 @@ pub fn cost_per_command(stack: &Stack, finalize: &Finalize, co } /// Returns the minimum number of microcredits required to run the finalize. -pub fn cost_in_microcredits(stack: &Stack, function_name: &Identifier) -> Result { +pub fn cost_in_microcredits_v2(stack: &Stack, function_name: &Identifier) -> Result { // Retrieve the finalize logic. let Some(finalize) = stack.get_function_ref(function_name)?.finalize_logic() else { // Return a finalize cost of 0, if the function does not have a finalize scope. @@ -389,7 +425,36 @@ pub fn cost_in_microcredits(stack: &Stack, function_name: &Identi finalize .commands() .iter() - .map(|command| cost_per_command(stack, finalize, command)) + .map(|command| cost_per_command(stack, finalize, command, ConsensusFeeVersion::V2)) + .try_fold(future_cost, |acc, res| { + res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed"))) + }) +} + +/// Returns the minimum number of microcredits required to run the finalize (depcrated). +pub fn cost_in_microcredits_v1(stack: &Stack, function_name: &Identifier) -> Result { + // Retrieve the finalize logic. + let Some(finalize) = stack.get_function_ref(function_name)?.finalize_logic() else { + // Return a finalize cost of 0, if the function does not have a finalize scope. + return Ok(0); + }; + // Get the cost of finalizing all futures. + let mut future_cost = 0u64; + for input in finalize.inputs() { + if let FinalizeType::Future(future) = input.finalize_type() { + // Get the external stack for the future. + let stack = stack.get_external_stack(future.program_id())?; + // Accumulate the finalize cost of the future. + future_cost = future_cost + .checked_add(cost_in_microcredits_v1(stack, future.resource())?) + .ok_or(anyhow!("Finalize cost overflowed"))?; + } + } + // Aggregate the cost of all commands in the program. + finalize + .commands() + .iter() + .map(|command| cost_per_command(stack, finalize, command, ConsensusFeeVersion::V1)) .try_fold(future_cost, |acc, res| { res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed"))) }) @@ -469,10 +534,10 @@ function over_five_thousand: // Get execution and cost data. let execution_under_5000 = get_execution(&mut process, &program, &under_5000, ["2group"].into_iter()); let execution_size_under_5000 = execution_under_5000.size_in_bytes().unwrap(); - let (_, (storage_cost_under_5000, _)) = execution_cost(&process, &execution_under_5000).unwrap(); + let (_, (storage_cost_under_5000, _)) = execution_cost_v2(&process, &execution_under_5000).unwrap(); let execution_over_5000 = get_execution(&mut process, &program, &over_5000, ["2group"].into_iter()); let execution_size_over_5000 = execution_over_5000.size_in_bytes().unwrap(); - let (_, (storage_cost_over_5000, _)) = execution_cost(&process, &execution_over_5000).unwrap(); + let (_, (storage_cost_over_5000, _)) = execution_cost_v2(&process, &execution_over_5000).unwrap(); // Ensure the sizes are below and above the threshold respectively. assert!(execution_size_under_5000 < threshold); diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index ea1d368ec0..4353733ead 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -86,7 +86,7 @@ impl Stack { stack.number_of_calls.insert(*function.name(), num_calls); // Get the finalize cost. - let finalize_cost = cost_in_microcredits(&stack, function.name())?; + let finalize_cost = cost_in_microcredits_v2(&stack, function.name())?; // Check that the finalize cost does not exceed the maximum. ensure!( finalize_cost <= N::TRANSACTION_SPEND_LIMIT, diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 5b3e627424..0161e15b16 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -37,7 +37,7 @@ mod evaluate; mod execute; mod helpers; -use crate::{CallMetrics, Process, Trace, cost_in_microcredits, traits::*}; +use crate::{CallMetrics, Process, Trace, cost_in_microcredits_v2, traits::*}; use console::{ account::{Address, PrivateKey}, network::prelude::*, diff --git a/synthesizer/src/vm/execute.rs b/synthesizer/src/vm/execute.rs index 1b6d54e5f4..97ab70d379 100644 --- a/synthesizer/src/vm/execute.rs +++ b/synthesizer/src/vm/execute.rs @@ -46,7 +46,12 @@ impl> VM { let fee = match is_fee_required || is_priority_fee_declared { true => { // Compute the minimum execution cost. - let (minimum_execution_cost, (_, _)) = execution_cost(&self.process().read(), &execution)?; + let query = query.clone().unwrap_or(Query::VM(self.block_store().clone())); + let block_height = query.current_block_height()?; + let (minimum_execution_cost, (_, _)) = match block_height < N::CONSENSUS_V2_HEIGHT { + true => execution_cost_v1(&self.process().read(), &execution)?, + false => execution_cost_v2(&self.process().read(), &execution)?, + }; // Compute the execution ID. let execution_id = execution.to_execution_id()?; // Authorize the fee. @@ -68,7 +73,7 @@ impl> VM { )?, }; // Execute the fee. - Some(self.execute_fee_authorization_raw(authorization, query, rng)?) + Some(self.execute_fee_authorization_raw(authorization, Some(query), rng)?) } false => None, }; @@ -216,7 +221,7 @@ mod tests { }; use ledger_block::Transition; use ledger_store::helpers::memory::ConsensusMemory; - use synthesizer_process::cost_per_command; + use synthesizer_process::{ConsensusFeeVersion, cost_per_command, execution_cost_v2}; use synthesizer_program::StackProgram; use indexmap::IndexMap; @@ -327,6 +332,147 @@ mod tests { } } + #[cfg(feature = "test")] + #[test] + fn test_execute_cost_fee_migration() { + let rng = &mut TestRng::default(); + let credits_program = "credits.aleo"; + let function_name = "transfer_public"; + + // Initialize a new caller. + let caller_private_key: PrivateKey = PrivateKey::new(rng).unwrap(); + let recipient_private_key: PrivateKey = PrivateKey::new(rng).unwrap(); + let recipient_address = Address::try_from(&recipient_private_key).unwrap(); + let transfer_public_amount = 1_000_000u64; + let inputs = &[recipient_address.to_string(), format!("{transfer_public_amount}_u64")]; + + // Prepare the VM and records. + let (vm, _) = prepare_vm(rng).unwrap(); + + // Prepare the inputs. + + let authorization = vm.authorize(&caller_private_key, credits_program, function_name, inputs, rng).unwrap(); + + let execution = vm.execute_authorization_raw(authorization, None, rng).unwrap(); + let (cost, _) = execution_cost_v2(&vm.process().read(), &execution).unwrap(); + let (old_cost, _) = execution_cost_v1(&vm.process().read(), &execution).unwrap(); + + assert_eq!(34_060, cost); + assert_eq!(51_060, old_cost); + + // Since transfer_public has 2 get.or_use's, the difference is (MAPPING_COST_V1 - MAPPING_BASE_COST_V2) * 2 + assert_eq!(old_cost - cost, 8_500 * 2); + } + + #[cfg(feature = "test")] + #[test] + fn test_fee_migration_occurs_at_correct_block_height() { + // This test will fail if the consensus v2 height is 0 + assert_ne!(0, CurrentNetwork::CONSENSUS_V2_HEIGHT); + + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + let address = Address::try_from(&private_key).unwrap(); + + // Prepare the VM and records. + let (vm, _) = prepare_vm(rng).unwrap(); + + // Prepare the inputs. + let inputs = [ + Value::::from_str(&address.to_string()).unwrap(), + Value::::from_str("1_000_000u64").unwrap(), + ] + .into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs.clone(), None, 0, None, rng).unwrap(); + + assert_eq!(51_060, *transaction.base_fee_amount().unwrap()); + + let transactions: [Transaction; 0] = []; + for _ in 0..CurrentNetwork::CONSENSUS_V2_HEIGHT { + // Call the function + let next_block = crate::vm::test_helpers::sample_next_block(&vm, &private_key, &transactions, rng).unwrap(); + vm.add_next_block(&next_block).unwrap(); + } + + let transaction = + vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs.clone(), None, 0, None, rng).unwrap(); + + assert_eq!(34_060, *transaction.base_fee_amount().unwrap()); + } + + #[cfg(feature = "test")] + #[test] + fn test_fee_migration_correctly_calculates_nested() { + // This test will fail if the consensus v2 height is 0 + assert_ne!(0, CurrentNetwork::CONSENSUS_V2_HEIGHT); + + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + + // Prepare the VM and records. + let (vm, _) = prepare_vm(rng).unwrap(); + + // Deploy a nested program to test the finalize cost cache + let program = Program::from_str( + r" +import credits.aleo; +program nested_call.aleo; +mapping data: + key as field.public; + value as field.public; +function test: + input r0 as u64.public; + call credits.aleo/transfer_public_as_signer nested_call.aleo r0 into r1; + async test r1 into r2; + output r2 as nested_call.aleo/test.future; +finalize test: + input r0 as credits.aleo/transfer_public_as_signer.future; + await r0; + get data[0field] into r1;", + ) + .unwrap(); + + // Deploy the program. + let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Construct the next block. + let next_block = crate::test_helpers::sample_next_block(&vm, &private_key, &[transaction], rng).unwrap(); + + // Add the next block to the VM. + vm.add_next_block(&next_block).unwrap(); + + // Prepare the inputs. + let inputs = [Value::::from_str("1_000_000u64").unwrap()].into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("nested_call.aleo", "test"), inputs.clone(), None, 0, None, rng).unwrap(); + + // This fee should be at least the old credits.aleo/transfer_public fee, 51_060 + assert_eq!(62_776, *transaction.base_fee_amount().unwrap()); + + let transactions: [Transaction; 0] = []; + for _ in 1..CurrentNetwork::CONSENSUS_V2_HEIGHT { + // Call the function + let next_block = crate::vm::test_helpers::sample_next_block(&vm, &private_key, &transactions, rng).unwrap(); + vm.add_next_block(&next_block).unwrap(); + } + + let transaction = + vm.execute(&private_key, ("nested_call.aleo", "test"), inputs.clone(), None, 0, None, rng).unwrap(); + + // The difference in old vs new fees is 8_500 * 3 = 25_500 for the three get/get.or_use's + // There are two get.or_use's in transfer_public and an additional one in the nested_call.aleo/test + assert_eq!(37_276, *transaction.base_fee_amount().unwrap()); + } + #[test] fn test_credits_bond_public_cost() { let rng = &mut TestRng::default(); @@ -350,7 +496,7 @@ mod tests { let authorization = vm.authorize(&caller_private_key, credits_program, function_name, inputs, rng).unwrap(); let execution = vm.execute_authorization_raw(authorization, None, rng).unwrap(); - let (cost, _) = execution_cost(&vm.process().read(), &execution).unwrap(); + let (cost, _) = execution_cost_v1(&vm.process().read(), &execution).unwrap(); println!("Cost: {}", cost); } @@ -735,7 +881,7 @@ finalize test: assert_eq!(execution.transitions().len(), ::MAX_INPUTS + 1); // Get the finalize cost of the execution. - let (_, (_, finalize_cost)) = execution_cost(&vm.process().read(), &execution).unwrap(); + let (_, (_, finalize_cost)) = execution_cost_v2(&vm.process().read(), &execution).unwrap(); // Compute the expected cost as the sum of the cost in microcredits of each command in each finalize block of each transition in the execution. let mut expected_cost = 0; @@ -753,7 +899,7 @@ finalize test: finalize_logic .commands() .iter() - .map(|command| cost_per_command(&stack, finalize_logic, command)) + .map(|command| cost_per_command(&stack, finalize_logic, command, ConsensusFeeVersion::V2)) .try_fold(0u64, |acc, res| { res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed"))) }) @@ -870,7 +1016,7 @@ finalize test: assert_eq!(execution.transitions().len(), Transaction::::MAX_TRANSITIONS - 1); // Get the finalize cost of the execution. - let (_, (_, finalize_cost)) = execution_cost(&vm.process().read(), &execution).unwrap(); + let (_, (_, finalize_cost)) = execution_cost_v2(&vm.process().read(), &execution).unwrap(); // Compute the expected cost as the sum of the cost in microcredits of each command in each finalize block of each transition in the execution. let mut expected_cost = 0; @@ -888,7 +1034,7 @@ finalize test: finalize_logic .commands() .iter() - .map(|command| cost_per_command(&stack, finalize_logic, command)) + .map(|command| cost_per_command(&stack, finalize_logic, command, ConsensusFeeVersion::V2)) .try_fold(0u64, |acc, res| { res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed"))) }) diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 9293b51d23..3d8f7adf58 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -47,7 +47,7 @@ use ledger_block::{ use ledger_committee::Committee; use ledger_narwhal_data::Data; use ledger_puzzle::Puzzle; -use ledger_query::Query; +use ledger_query::{Query, QueryTrait}; use ledger_store::{ BlockStore, ConsensusStorage, @@ -58,7 +58,7 @@ use ledger_store::{ TransitionStore, atomic_finalize, }; -use synthesizer_process::{Authorization, Process, Trace, deployment_cost, execution_cost}; +use synthesizer_process::{Authorization, Process, Trace, deployment_cost, execution_cost_v1, execution_cost_v2}; use synthesizer_program::{FinalizeGlobalState, FinalizeOperation, FinalizeStoreTrait, Program}; use utilities::try_vm_runtime; @@ -675,6 +675,36 @@ function compute: .clone() } + #[cfg(feature = "test")] + pub(crate) fn create_new_transaction_with_different_fee( + rng: &mut TestRng, + transaction: Transaction, + fee: u64, + ) -> Transaction { + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Get Execution + let execution = transaction.execution().unwrap().clone(); + + // Authorize the fee. + let authorization = + vm.authorize_fee_public(&caller_private_key, fee, 100, execution.to_execution_id().unwrap(), rng).unwrap(); + // Compute the fee. + let fee = vm.execute_fee_authorization(authorization, None, rng).unwrap(); + + // Construct the transaction. + Transaction::from_execution(execution, Some(fee)).unwrap() + } + pub fn sample_next_block( vm: &VM>, private_key: &PrivateKey, diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index 16f442eaa4..ab45e68444 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -233,8 +233,15 @@ impl> VM { if let Some(fee) = fee { // If the fee is required, then check that the base fee amount is satisfied. if is_fee_required { - // Compute the execution cost. - let (cost, _) = execution_cost(&self.process().read(), execution)?; + // Compute the execution cost based on the block height + let block_height = self + .block_store() + .find_block_height_from_state_root(execution.global_state_root())? + .unwrap_or_default(); + let (cost, (_, _)) = match block_height < N::CONSENSUS_V2_HEIGHT { + true => execution_cost_v1(&self.process().read(), execution)?, + false => execution_cost_v2(&self.process().read(), execution)?, + }; // Ensure the fee is sufficient to cover the cost. if *fee.base_amount()? < cost { bail!( @@ -724,4 +731,93 @@ function compute: // Ensure that the mutated transaction fails verification due to an extra output. assert!(vm.check_transaction(&mutated_transaction, None, rng).is_err()); } + + #[cfg(feature = "test")] + #[test] + fn test_fee_migration() { + // This test will fail if the consensus v2 height is 0 + assert_ne!(0, CurrentNetwork::CONSENSUS_V2_HEIGHT); + + let minimum_credits_transfer_public_fee = 34_060; + let old_minimum_credits_transfer_public_fee = 51_060; + + let rng = &mut TestRng::default(); + + // Initialize the VM. + let vm = crate::vm::test_helpers::sample_vm(); + // Initialize the genesis block. + let genesis = crate::vm::test_helpers::sample_genesis_block(rng); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Create the base transaction + let transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng); + + // Try to submit a tx with the new fee before the migration block height + let fee_too_low_transaction = crate::vm::test_helpers::create_new_transaction_with_different_fee( + rng, + transaction.clone(), + minimum_credits_transfer_public_fee, + ); + assert!(vm.check_transaction(&fee_too_low_transaction, None, rng).is_err()); + + // Try to submit a tx with the old fee before the migration block height + let old_valid_transaction = crate::vm::test_helpers::create_new_transaction_with_different_fee( + rng, + transaction.clone(), + old_minimum_credits_transfer_public_fee, + ); + assert!(vm.check_transaction(&old_valid_transaction, None, rng).is_ok()); + + // Update the VM to the migration block height + let private_key = test_helpers::sample_genesis_private_key(rng); + let transactions: [Transaction; 0] = []; + for _ in 0..CurrentNetwork::CONSENSUS_V2_HEIGHT { + // Call the function + let next_block = crate::vm::test_helpers::sample_next_block(&vm, &private_key, &transactions, rng).unwrap(); + vm.add_next_block(&next_block).unwrap(); + } + + // Create a new transaction with the new stateroot post migration block height + let transaction = { + let address = Address::try_from(&private_key).unwrap(); + let inputs = [ + Value::::from_str(&address.to_string()).unwrap(), + Value::::from_str("1u64").unwrap(), + ] + .into_iter(); + + // Execute. + let transaction_without_fee = + vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); + let execution = transaction_without_fee.execution().unwrap().clone(); + + // Authorize the fee. + let authorization = vm + .authorize_fee_public(&private_key, 10_000_000, 100, execution.to_execution_id().unwrap(), rng) + .unwrap(); + // Compute the fee. + let fee = vm.execute_fee_authorization(authorization, None, rng).unwrap(); + + // Construct the transaction. + Transaction::from_execution(execution, Some(fee)).unwrap() + }; + + // Try to submit a tx with the old fee after the migration block height + // Should work as now the fee is just too high + let fee_too_high_transaction = crate::vm::test_helpers::create_new_transaction_with_different_fee( + rng, + transaction.clone(), + old_minimum_credits_transfer_public_fee, + ); + assert!(vm.check_transaction(&fee_too_high_transaction, None, rng).is_ok()); + + // Try to submit a tx with the new fee after the migration block height + let valid_transaction = crate::vm::test_helpers::create_new_transaction_with_different_fee( + rng, + transaction.clone(), + minimum_credits_transfer_public_fee, + ); + assert!(vm.check_transaction(&valid_transaction, None, rng).is_ok()); + } }