From b08736a2d9d1292c6a5c50a2d1b4b12530986a4d Mon Sep 17 00:00:00 2001 From: kp2pml30 Date: Thu, 24 Sep 2026 21:00:50 +0900 Subject: [PATCH] =?UTF-8?q?fix(executor):=20forward=20opaque=20subtrees=20?= =?UTF-8?q?and=20fund=20descendants=20=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- executor/install/config/genvm.yaml | 4 +- executor/src/domain/fees.rs | 32 +-- executor/src/domain/fees/abi.rs | 170 ------------ executor/src/lib.rs | 7 +- executor/src/rt/fees.rs | 5 + executor/src/wasi/genlayer_sdk.rs | 260 +++++++++++++----- executor/src/wasi/genlayer_sdk_test.rs | 249 +++++++++++++++++ executor/tests/fees_abi.rs | 59 ---- .../stable/py/balances/balance.0.stdout | 2 +- .../py/balances/sandbox_overspend.0.stdout | 2 +- .../py/balances/sandbox_overspend_2.0.stdout | 2 +- .../stable/py/intercontract/deploy.0.stdout | 2 +- .../py/intercontract/deploy_salt.0.stdout | 2 +- .../py/intercontract/send_message.0.hash | 2 +- .../py/intercontract/send_message.0.stdout | 2 +- .../py/intercontract/send_message.1.hash | 1 + .../py/intercontract/send_message.1.stdout | 1 + .../py/intercontract/send_message.jsonnet | 27 +- .../intercontract/send_message_on.0_0.stdout | 2 +- 19 files changed, 489 insertions(+), 342 deletions(-) delete mode 100644 executor/src/domain/fees/abi.rs create mode 100644 executor/src/wasi/genlayer_sdk_test.rs delete mode 100644 executor/tests/fees_abi.rs create mode 100644 tests/integration/stable/py/intercontract/send_message.1.hash create mode 100644 tests/integration/stable/py/intercontract/send_message.1.stdout diff --git a/executor/install/config/genvm.yaml b/executor/install/config/genvm.yaml index 3db6b4c3..24e82cfb 100644 --- a/executor/install/config/genvm.yaml +++ b/executor/install/config/genvm.yaml @@ -8,7 +8,7 @@ modules: log_level: info -# This legacy executor line intentionally charges every fee category at zero +# Execution fees are zero; descendant funding still consumes the message pool fees: expr_prelude: "" storage: @@ -30,7 +30,7 @@ fees: delta_expr: | \a = 0 message_fee: - buckets: execution_data_gas + buckets: message_fee subtract_on_start_expr: | 0 delta_expr: | diff --git a/executor/src/domain/fees.rs b/executor/src/domain/fees.rs index 713f2caa..c3d3a155 100644 --- a/executor/src/domain/fees.rs +++ b/executor/src/domain/fees.rs @@ -1,13 +1,6 @@ use genlayer_sdk::abi::gl_call::On; use primitive_types::U256; -mod abi; - -pub const CALL_KEY_WILDCARD: genlayer_sdk::abi::CallKey = genlayer_sdk::abi::CallKey([ - 0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x03, 0xc0, - 0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04, 0x5d, 0x85, 0xa4, 0x70, -]); - #[derive( Debug, Clone, @@ -25,12 +18,7 @@ pub struct InternalMessageParams { /// Per-round rotation allocations; `rotations[0]` is the initial round, the /// rest are appeal rounds. Must be non-empty. /// - /// The chain's `InternalMessageFeeParams` carries an explicit `appealRounds` - /// field, but it is not stored here: the chain enforces - /// `appealRounds == rotations.length - 1` (`FeesVerifier.InvalidAppealRounds`), - /// so we derive `appeal_rounds = rotations.len() - 1` instead. The ABI - /// encoder re-inserts it so the encoded bytes (and their `keccak256` fee-param - /// pin) match the chain's field layout. + /// The fee evaluator derives `appeal_rounds = rotations.len() - 1`. pub rotations: Vec, /// Per-time-unit GEN price cap locked at activation (consensus CON-549, /// v0.6-dev). The chain charges at this cap as the funding multiplier and @@ -74,7 +62,7 @@ pub enum MessageAllocationNodeParams { External(ExternalMessageParams), } -/// One node of the message-fee allocation tree. +/// One allocation available to messages emitted by this execution. #[derive( Debug, Clone, @@ -86,24 +74,18 @@ pub enum MessageAllocationNodeParams { pub struct MessageAllocationNode { /// Target contract address; `None` means wildcard (any recipient). pub recipient: Option, - /// `None` = wildcard: all call keys for this recipient - /// (chain sentinel: `CALL_KEY_WILDCARD` = `keccak256("")`). + /// `None` means any call key; the node converts the chain's wildcard sentinel. pub call_key: Option, - /// Max budget for matching messages. - pub budget: U256, + /// Available allowance; zero is exhausted, `None` is uncapped. + pub budget: Option, pub on: On, /// Same structure as TX-level params. pub fee_params: MessageAllocationNodeParams, - pub children: Vec, + pub children_budget: U256, + pub subtree: bytes::Bytes, } impl MessageAllocationNode { - /// ABI-encodes this matched node and its descendants for transport to the chain. - /// The matched node is element 0 and descendants are in BFS order. - pub fn abi_encode(&self) -> Vec { - abi::encode(self) - } - #[allow(clippy::if_same_then_else)] pub fn matches_internal( &self, diff --git a/executor/src/domain/fees/abi.rs b/executor/src/domain/fees/abi.rs deleted file mode 100644 index f0db6472..00000000 --- a/executor/src/domain/fees/abi.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! EVM ABI encoding of the message-fee allocation tree into the chain's flat -//! parent-pointer `MessageAllocationNode[]` representation. - -use genlayer_sdk::abi::gl_call::On; -use primitive_types::U256; - -use super::{ - ExternalMessageParams, InternalMessageParams, MessageAllocationNode, - MessageAllocationNodeParams, -}; - -/// Parent pointer used by top-level (root-layer) allocation nodes in the flat -/// chain representation: `type(uint256).max`. Any other value is a 0-based index -/// into the submitted array. -const NODE_ROOT_SENTINEL: U256 = U256::MAX; - -/// Solidity `enum MessageType { External, Internal }`. -const MESSAGE_TYPE_EXTERNAL: u64 = 0; -const MESSAGE_TYPE_INTERNAL: u64 = 1; - -/// Number of head words in the Solidity `MessageAllocationNode` tuple -/// (messageType, onAcceptance, parentIndex, recipient, callKey, budget, feeParams). -const NODE_HEAD_WORDS: usize = 7; - -/// Number of head words in the Solidity `InternalMessageParams` tuple -/// (leader, validator, appealRounds, executionBudgetPerRound, rotations, -/// maxPriceGenPerTimeUnit, storageFeeMaxGasPrice, receiptFeeMaxGasPrice). -/// `rotations` is the only dynamic field; the three price caps (v0.6-dev, -/// CON-549) are static and follow it in the head region. -const INTERNAL_PARAMS_HEAD_WORDS: usize = 8; - -fn push_u256(buf: &mut Vec, value: U256) { - buf.extend_from_slice(&value.to_big_endian()); -} - -fn push_word(buf: &mut Vec, value: u64) { - push_u256(buf, U256::from(value)); -} - -fn push_bool(buf: &mut Vec, value: bool) { - push_word(buf, value as u64); -} - -/// Left-pads a 20-byte address into a 32-byte ABI word. -fn push_address(buf: &mut Vec, addr: Option<&genlayer_sdk::calldata::Address>) { - buf.extend_from_slice(&[0u8; 12]); - match addr { - Some(addr) => buf.extend_from_slice(&addr.raw()), - None => buf.extend_from_slice(&[0u8; 20]), - } -} - -/// `callKey` is already a 32-byte word; `None` is `CALL_KEY_WILDCARD`. -fn push_call_key(buf: &mut Vec, call_key: Option<&genlayer_sdk::abi::CallKey>) { - match call_key { - Some(ck) => buf.extend_from_slice(&ck.0), - None => buf.extend_from_slice(&super::CALL_KEY_WILDCARD.0), - } -} - -/// Encodes the transport payload consumed by `decodeAllocationSubtree`. -pub(super) fn encode(root: &MessageAllocationNode) -> Vec { - let mut flat: Vec<(&MessageAllocationNode, U256)> = Vec::new(); - let mut queue = std::collections::VecDeque::from([(root, NODE_ROOT_SENTINEL)]); - while let Some((node, parent_index)) = queue.pop_front() { - let node_index = U256::from(flat.len()); - flat.push((node, parent_index)); - for child in &node.children { - queue.push_back((child, node_index)); - } - } - - let elements: Vec> = flat - .iter() - .map(|(node, parent_index)| encode_node(node, *parent_index)) - .collect(); - - let mut buf = Vec::new(); - push_word(&mut buf, 0x20); - - push_word(&mut buf, elements.len() as u64); - let mut offset = elements.len() * 32; - for element in &elements { - push_word(&mut buf, offset as u64); - offset += element.len(); - } - for element in &elements { - buf.extend_from_slice(element); - } - - buf -} - -/// Encodes a single node as the Solidity `MessageAllocationNode` dynamic -/// tuple (offsets are relative to the start of this tuple). -fn encode_node(node: &MessageAllocationNode, parent_index: U256) -> Vec { - let (message_type, on_acceptance, fee_params) = match &node.fee_params { - MessageAllocationNodeParams::Internal(params) => ( - MESSAGE_TYPE_INTERNAL, - matches!(node.on, On::Accepted), - encode_internal_params(params), - ), - // External messages have no acceptance/finalize lifecycle. - MessageAllocationNodeParams::External(params) => { - (MESSAGE_TYPE_EXTERNAL, false, encode_external_params(params)) - } - }; - - let mut buf = Vec::new(); - push_word(&mut buf, message_type); - push_bool(&mut buf, on_acceptance); - push_u256(&mut buf, parent_index); - push_address(&mut buf, node.recipient.as_ref()); - push_call_key(&mut buf, node.call_key.as_ref()); - push_u256(&mut buf, node.budget); - // `feeParams` is the only dynamic field; its offset follows the head words. - push_word(&mut buf, (NODE_HEAD_WORDS * 32) as u64); - // Tail: `bytes feeParams` = length + 32-aligned data. - push_word(&mut buf, fee_params.len() as u64); - buf.extend_from_slice(&fee_params); - let pad = fee_params.len().next_multiple_of(32) - fee_params.len(); - buf.extend(std::iter::repeat_n(0u8, pad)); - - buf -} - -/// `abi.encode(InternalMessageParams)` — a dynamic tuple (contains -/// `uint256[] rotations`), so it is prefixed with the offset word. `appealRounds` -/// is reconstructed as `len(rotations) - 1` (the chain-derived value). -/// -/// Field order matches the chain's `InternalMessageFeeParams` (v0.6-dev): -/// leader, validator, appealRounds, executionBudgetPerRound, rotations, -/// maxPriceGenPerTimeUnit, storageFeeMaxGasPrice, receiptFeeMaxGasPrice. The -/// three price caps are static and sit in the head *after* the `rotations` -/// offset word, so the rotations tail starts past all 8 head words. -fn encode_internal_params(params: &InternalMessageParams) -> Vec { - let appeal_rounds = U256::from(params.rotations.len().saturating_sub(1) as u64); - - let mut buf = Vec::new(); - // `abi.encode` of a single dynamic struct prefixes the tuple with its offset. - push_word(&mut buf, 0x20); - push_u256(&mut buf, params.leader_timeunits_allocation); - push_u256(&mut buf, params.validator_timeunits_allocation); - push_u256(&mut buf, appeal_rounds); - push_u256(&mut buf, params.execution_budget_per_round); - // `rotations` is the only dynamic field; its offset follows all head words. - push_word(&mut buf, (INTERNAL_PARAMS_HEAD_WORDS * 32) as u64); - // Static price caps occupy head words 6-8 (between the rotations offset and - // the rotations tail), mirroring the chain struct field order. - push_u256(&mut buf, params.max_price_gen_per_time_unit); - push_u256(&mut buf, params.storage_fee_max_gas_price); - push_u256(&mut buf, params.receipt_fee_max_gas_price); - // Tail: `rotations` length + data, after the 8 head words. - push_word(&mut buf, params.rotations.len() as u64); - for rotation in ¶ms.rotations { - push_u256(&mut buf, *rotation); - } - - buf -} - -/// `abi.encode(ExternalMessageParams)` — a fully static tuple, so it is just -/// the two inline words with no leading offset. -fn encode_external_params(params: &ExternalMessageParams) -> Vec { - let mut buf = Vec::new(); - push_u256(&mut buf, params.gas_limit); - push_u256(&mut buf, params.max_gas_price); - - buf -} diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 69da6e89..419e4ac4 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -274,11 +274,8 @@ fn convert_message_allocation_node( budget: node.budget, on: convert_on(node.on), fee_params: convert_message_allocation_node_params(node.fee_params), - children: node - .children - .into_iter() - .map(convert_message_allocation_node) - .collect(), + children_budget: node.children_budget, + subtree: node.subtree, } } diff --git a/executor/src/rt/fees.rs b/executor/src/rt/fees.rs index eddead0d..27170f40 100644 --- a/executor/src/rt/fees.rs +++ b/executor/src/rt/fees.rs @@ -571,6 +571,11 @@ impl DataLimit { .context("calculating message fee internal") } + pub async fn consume_message_receipt_only(&self, cost_receipt: &CostVec) -> bool { + self.consume_bucket_raw(&self.message_receipt, &cost_receipt.0) + .await + } + pub async fn consume_message_fee(&self, cost_fee: &CostVec, cost_receipt: &CostVec) -> bool { let mut buckets = self.buckets.lock().await; diff --git a/executor/src/wasi/genlayer_sdk.rs b/executor/src/wasi/genlayer_sdk.rs index 14a60f9e..fef485d3 100644 --- a/executor/src/wasi/genlayer_sdk.rs +++ b/executor/src/wasi/genlayer_sdk.rs @@ -17,6 +17,80 @@ use genlayer_sdk::abi::{self, gl_call}; use super::{base, vfs}; +#[cfg(test)] +#[path = "genlayer_sdk_test.rs"] +mod tests; + +fn allocation_match_priority( + node: &domain::fees::MessageAllocationNode, + recipient: calldata::Address, + call_key: abi::CallKey, +) -> Option { + let recipient_priority = match node.recipient { + Some(candidate) if candidate == recipient => 0, + Some(_) => return None, + None => 2, + }; + let call_key_priority = match node.call_key { + Some(candidate) if candidate == call_key => 0, + Some(_) => return None, + None => 1, + }; + Some(recipient_priority + call_key_priority) +} + +fn resolve_internal_allocation( + nodes: &[domain::fees::MessageAllocationNode], + on: gl_call::On, + recipient: calldata::Address, + call_key: abi::CallKey, +) -> Option<(usize, Arc)> { + let priority = nodes + .iter() + .filter(|node| { + matches!( + &node.fee_params, + domain::fees::MessageAllocationNodeParams::Internal(_) + ) + }) + .filter_map(|node| allocation_match_priority(node, recipient, call_key)) + .min()?; + let index = nodes.iter().position(|node| { + allocation_match_priority(node, recipient, call_key) == Some(priority) + && (node.recipient.is_some() || node.on == on) + && matches!( + &node.fee_params, + domain::fees::MessageAllocationNodeParams::Internal(_) + ) + })?; + Some(( + index, + nodes[index].matches_internal(on, recipient, call_key)?, + )) +} + +fn external_allocation_candidates( + nodes: &[domain::fees::MessageAllocationNode], + recipient: calldata::Address, + call_key: abi::CallKey, +) -> Vec { + let mut candidates = nodes + .iter() + .enumerate() + .filter(|(_, node)| { + matches!( + &node.fee_params, + domain::fees::MessageAllocationNodeParams::External(_) + ) + }) + .filter_map(|(index, node)| { + allocation_match_priority(node, recipient, call_key).map(|priority| (priority, index)) + }) + .collect::>(); + candidates.sort_by_key(|(priority, _)| *priority); + candidates.into_iter().map(|(_, index)| index).collect() +} + fn default_entry_stage_data() -> calldata::Value { calldata::Value::Null } @@ -106,17 +180,28 @@ async fn consume_message_fee_internal( on: gl_call::On, args: ConsumeInternalArgs, ) -> Result { - let fee_cost = shared_data + let mut fee_cost = shared_data .data_fees_limit .calculate_message_fee_internal(on, &fee_params) .map_err(|x| generated::types::Error::trap(anyhow_to_wasmtime(x)))?; + fee_cost.0[0] = fee_cost.0[0] + .checked_add(node.children_budget) + .ok_or_else(|| { + generated::types::Error::trap(anyhow_to_wasmtime(anyhow::anyhow!( + "message declared budget overflow" + ))) + })?; let fee_total = fee_cost.sum(); - let remaining_budget = node.budget.checked_sub(*consumed).ok_or_else(|| { - generated::types::Error::trap(anyhow_to_wasmtime(anyhow::anyhow!( - "message allocation consumed budget exceeds its total" - ))) - })?; + let remaining_budget = node + .budget + .unwrap_or(primitive_types::U256::MAX) + .checked_sub(*consumed) + .ok_or_else(|| { + generated::types::Error::trap(anyhow_to_wasmtime(anyhow::anyhow!( + "message allocation consumed budget exceeds its total" + ))) + })?; if fee_total > remaining_budget { log_warn!( node:cd = *node, @@ -172,7 +257,7 @@ struct ConsumeExternalArgs { async fn consume_message_fee_external( shared_data: &rt::SharedData, - node: &domain::fees::MessageAllocationNode, + node: Option<&domain::fees::MessageAllocationNode>, consumed: &mut primitive_types::U256, params: domain::fees::ExternalMessageParams, // External messages are always emitted on finalization; carried for signature @@ -180,17 +265,25 @@ async fn consume_message_fee_external( _on: gl_call::On, args: ConsumeExternalArgs, ) -> Result { - let fee_cost = shared_data - .data_fees_limit - .calculate_message_fee_external(¶ms) - .map_err(|x| generated::types::Error::trap(anyhow_to_wasmtime(x)))?; + let fee_cost = if node.is_some() { + shared_data + .data_fees_limit + .calculate_message_fee_external(¶ms) + .map_err(|x| generated::types::Error::trap(anyhow_to_wasmtime(x)))? + } else { + rt::fees::CostVec(Vec::new()) + }; let fee_total = fee_cost.sum(); - let remaining_budget = node.budget.checked_sub(*consumed).ok_or_else(|| { - generated::types::Error::trap(anyhow_to_wasmtime(anyhow::anyhow!( - "message allocation consumed budget exceeds its total" - ))) - })?; + let remaining_budget = node + .and_then(|node| node.budget) + .unwrap_or(primitive_types::U256::MAX) + .checked_sub(*consumed) + .ok_or_else(|| { + generated::types::Error::trap(anyhow_to_wasmtime(anyhow::anyhow!( + "message allocation consumed budget exceeds its total" + ))) + })?; if fee_total > remaining_budget { return Err(oom_trap(abi::consts::VmError::oom().fees().external())); } @@ -206,11 +299,18 @@ async fn consume_message_fee_external( }) .map_err(|x| generated::types::Error::trap(anyhow_to_wasmtime(x)))?; - if !shared_data - .data_fees_limit - .consume_message_fee(&fee_cost, &receipt_cost) - .await - { + let funded = if node.is_some() { + shared_data + .data_fees_limit + .consume_message_fee(&fee_cost, &receipt_cost) + .await + } else { + shared_data + .data_fees_limit + .consume_message_receipt_only(&receipt_cost) + .await + }; + if !funded { return Err(oom_trap(abi::consts::VmError::oom().fees().external())); } @@ -590,7 +690,7 @@ impl ContextVFS<'_> { rt::vm::RunOk::VMError(e, cause) => { return Err(generated::types::Error::trap(crate::anyhow_to_wasmtime( rt::errors::Error::vm_cause(e, cause).into(), - ))) + ))); } data => data, }; @@ -717,36 +817,64 @@ impl generated::genlayer_sdk::GenlayerSdk for ContextVFS<'_> { call_key.0[..4].copy_from_slice(&calldata[..4]); } - let Some((matched_index, matched_params)) = self - .context - .data - .accumulator - .message_fee_allocation - .iter() - .enumerate() - .find_map(|(index, node)| { - node.matches_external(address, call_key) - .map(|params| (index, params)) - }) - else { - log_warn!( - recipient = address, - call_key:? = call_key; - "no matching node for message fee allocation" - ); - + let shared_data = self.context.data.supervisor.shared_data.clone(); + let accumulator = &mut self.context.data.accumulator; + let candidates = external_allocation_candidates( + &accumulator.message_fee_allocation, + address, + call_key, + ); + let has_candidates = !candidates.is_empty(); + let mut matched = None; + for index in candidates { + let node = &accumulator.message_fee_allocation[index]; + let params = node + .matches_external(address, call_key) + .expect("matching external allocation"); + let fee = shared_data + .data_fees_limit + .calculate_message_fee_external(¶ms) + .map_err(|x| generated::types::Error::trap(anyhow_to_wasmtime(x)))?; + let remaining = node + .budget + .unwrap_or(primitive_types::U256::MAX) + .checked_sub(accumulator.message_fee_allocation_consumed[index]) + .ok_or_else(|| { + generated::types::Error::trap(anyhow_to_wasmtime(anyhow::anyhow!( + "message allocation consumed budget exceeds its total" + ))) + })?; + if fee.sum() <= remaining { + matched = Some((index, params)); + break; + } + } + if has_candidates && matched.is_none() { return Err(oom_trap(abi::consts::VmError::oom().fees().external())); - }; + } let calldata_length = calldata.len() as u64; - let shared_data = self.context.data.supervisor.shared_data.clone(); - let accumulator = &mut self.context.data.accumulator; - let matched_node = &accumulator.message_fee_allocation[matched_index]; + let mut unallocated_consumed = primitive_types::U256::zero(); + let (matched_node, consumed, matched_params) = match matched { + Some((index, params)) => ( + Some(&accumulator.message_fee_allocation[index]), + &mut accumulator.message_fee_allocation_consumed[index], + params, + ), + None => ( + None, + &mut unallocated_consumed, + domain::fees::ExternalMessageParams { + gas_limit: primitive_types::U256::zero(), + max_gas_price: primitive_types::U256::zero(), + }, + ), + }; let fees = consume_message_fee_external( &shared_data, matched_node, - &mut accumulator.message_fee_allocation_consumed[matched_index], + consumed, matched_params, gl_call::On::Finalized, ConsumeExternalArgs { @@ -1165,18 +1293,12 @@ impl generated::genlayer_sdk::GenlayerSdk for ContextVFS<'_> { } } - let Some((matched_index, matched_params)) = self - .context - .data - .accumulator - .message_fee_allocation - .iter() - .enumerate() - .find_map(|(index, node)| { - node.matches_internal(on, address, call_key) - .map(|params| (index, params)) - }) - else { + let Some((matched_index, matched_params)) = resolve_internal_allocation( + &self.context.data.accumulator.message_fee_allocation, + on, + address, + call_key, + ) else { log_warn!( recipient = address, call_key:? = call_key, @@ -1201,7 +1323,7 @@ impl generated::genlayer_sdk::GenlayerSdk for ContextVFS<'_> { let fee_params = (*matched_params).clone(); let accumulator = &mut self.context.data.accumulator; let matched_node = &accumulator.message_fee_allocation[matched_index]; - let subtree = bytes::Bytes::from(matched_node.abi_encode()); + let subtree = matched_node.subtree.clone(); let fees = consume_message_fee_internal( &sd, @@ -1278,18 +1400,12 @@ impl generated::genlayer_sdk::GenlayerSdk for ContextVFS<'_> { } } - let Some((matched_index, matched_params)) = self - .context - .data - .accumulator - .message_fee_allocation - .iter() - .enumerate() - .find_map(|(index, node)| { - node.matches_internal(on, calldata::Address::zero(), abi::CallKey::DEPLOY) - .map(|params| (index, params)) - }) - else { + let Some((matched_index, matched_params)) = resolve_internal_allocation( + &self.context.data.accumulator.message_fee_allocation, + on, + calldata::Address::zero(), + abi::CallKey::DEPLOY, + ) else { log_warn!( recipient = calldata::Address::zero(), call_key:? = abi::CallKey::DEPLOY, @@ -1308,7 +1424,7 @@ impl generated::genlayer_sdk::GenlayerSdk for ContextVFS<'_> { let fee_params = (*matched_params).clone(); let accumulator = &mut self.context.data.accumulator; let matched_node = &accumulator.message_fee_allocation[matched_index]; - let subtree = bytes::Bytes::from(matched_node.abi_encode()); + let subtree = matched_node.subtree.clone(); let fees = consume_message_fee_internal( &sd, @@ -2053,7 +2169,7 @@ impl ContextVFS<'_> { None => { return Err(generated::types::Error::trap(crate::anyhow_to_wasmtime( anyhow::anyhow!("absent leader result in sync mode, call_no: {}", call_no), - ))) + ))); } Some(v) => v, } diff --git a/executor/src/wasi/genlayer_sdk_test.rs b/executor/src/wasi/genlayer_sdk_test.rs new file mode 100644 index 00000000..c22ca3b1 --- /dev/null +++ b/executor/src/wasi/genlayer_sdk_test.rs @@ -0,0 +1,249 @@ +use super::{external_allocation_candidates, resolve_internal_allocation}; +use crate::{calldata, domain}; +use genlayer_sdk::abi::{self, gl_call}; +use primitive_types::U256; +use std::sync::Arc; + +#[tokio::test] +async fn unallocated_external_does_not_evaluate_allocation_fee() { + let bucket = |delta: &str| crate::config::FeesBucketConfig { + buckets: vec![symbol_table::GlobalSymbol::from("test")], + subtract_on_start_expr: "0".to_owned(), + delta_expr: delta.to_owned(), + }; + let shared = crate::rt::SharedData { + is_sync: false, + genvm_id: genvm_modules_interfaces::GenVMId(0), + debug_mode: genvm_common::DebugMode::Disabled, + metrics: Default::default(), + data_fees_limit: crate::rt::fees::DataLimit::new( + std::collections::HashMap::from([("test".to_owned(), U256::from(10))]), + crate::config::FeesConfig { + expr_prelude: String::new(), + storage: bucket("\\a = 0"), + message_receipt: bucket("\\a = 1"), + nondet_output: bucket("\\a = 0"), + message_fee: bucket("\\a = 1 / a.matchedFeeParams.gasLimit"), + event: bucket("\\a = 0"), + }, + Default::default(), + ) + .unwrap(), + det_fuel_budget: crate::rt::DetFuelBudget::new(None), + llm_consumption: tokio::sync::Mutex::new(U256::zero()), + }; + let mut consumed = U256::zero(); + let fees = super::consume_message_fee_external( + &shared, + None, + &mut consumed, + domain::fees::ExternalMessageParams { + gas_limit: U256::zero(), + max_gas_price: U256::zero(), + }, + gl_call::On::Finalized, + super::ConsumeExternalArgs { + is_deploy: false, + calldata_length: 0, + }, + ) + .await + .unwrap(); + assert_eq!(fees.message_fee.sum(), U256::zero()); + assert_eq!(fees.receipt_fee.sum(), U256::one()); + assert_eq!(consumed, U256::zero()); + assert_eq!( + shared.data_fees_limit.remaining().await["test"], + U256::from(9) + ); +} + +fn external_message_allocation() -> domain::fees::MessageAllocationNode { + domain::fees::MessageAllocationNode { + recipient: None, + call_key: None, + budget: Some(U256::from(100)), + on: gl_call::On::Finalized, + fee_params: domain::fees::MessageAllocationNodeParams::External( + domain::fees::ExternalMessageParams { + gas_limit: U256::zero(), + max_gas_price: U256::zero(), + }, + ), + children_budget: U256::zero(), + subtree: bytes::Bytes::new(), + } +} + +fn internal_message_allocation() -> domain::fees::MessageAllocationNode { + domain::fees::MessageAllocationNode { + recipient: None, + call_key: None, + budget: Some(U256::from(100)), + on: gl_call::On::Finalized, + fee_params: domain::fees::MessageAllocationNodeParams::Internal(Arc::new( + domain::fees::InternalMessageParams { + leader_timeunits_allocation: U256::one(), + validator_timeunits_allocation: U256::one(), + execution_budget_per_round: U256::one(), + rotations: vec![U256::one()], + max_price_gen_per_time_unit: U256::one(), + storage_fee_max_gas_price: U256::one(), + receipt_fee_max_gas_price: U256::one(), + }, + )), + children_budget: U256::zero(), + subtree: bytes::Bytes::new(), + } +} + +#[test] +fn internal_allocation_prefers_exact_key_over_earlier_wildcard() { + let recipient = calldata::Address::from([7; 20]); + let call_key = abi::CallKey([8; 32]); + let mut wildcard = internal_message_allocation(); + wildcard.recipient = Some(recipient); + wildcard.budget = Some(U256::one()); + let mut exact = wildcard.clone(); + exact.call_key = Some(call_key); + exact.budget = Some(U256::from(2)); + let nodes = vec![wildcard, exact]; + + let (matched, _) = + resolve_internal_allocation(&nodes, gl_call::On::Finalized, recipient, call_key) + .expect("exact allocation should match"); + + assert_eq!(nodes[matched].budget, Some(U256::from(2))); +} + +#[test] +fn internal_allocation_keeps_exhausted_exact_key() { + let recipient = calldata::Address::from([7; 20]); + let call_key = abi::CallKey([8; 32]); + let mut wildcard = internal_message_allocation(); + wildcard.recipient = Some(recipient); + wildcard.budget = Some(U256::one()); + let mut exact = wildcard.clone(); + exact.call_key = Some(call_key); + exact.budget = Some(U256::zero()); + let nodes = vec![wildcard, exact]; + + let (matched, _) = + resolve_internal_allocation(&nodes, gl_call::On::Finalized, recipient, call_key) + .expect("exhausted exact allocation should still match"); + + assert_eq!(matched, 1); + assert_eq!(nodes[matched].budget, Some(U256::zero())); +} + +#[test] +fn internal_allocation_keeps_uncapped_exact_key() { + let recipient = calldata::Address::from([7; 20]); + let call_key = abi::CallKey([8; 32]); + let wildcard = internal_message_allocation(); + let mut exact = wildcard.clone(); + exact.recipient = Some(recipient); + exact.call_key = Some(call_key); + exact.budget = None; + let nodes = vec![wildcard, exact]; + assert_eq!( + resolve_internal_allocation(&nodes, gl_call::On::Finalized, recipient, call_key) + .unwrap() + .0, + 1 + ); +} + +#[test] +fn internal_allocation_does_not_search_duplicate_chain_keys_by_phase() { + let recipient = calldata::Address::from([7; 20]); + let call_key = abi::CallKey([8; 32]); + let mut first = internal_message_allocation(); + first.recipient = Some(recipient); + first.call_key = Some(call_key); + let mut duplicate = first.clone(); + duplicate.on = gl_call::On::Accepted; + assert!(resolve_internal_allocation( + &[first, duplicate], + gl_call::On::Accepted, + recipient, + call_key + ) + .is_none()); +} + +#[test] +fn internal_allocation_phase_is_checked_after_key_resolution() { + let recipient = calldata::Address::from([7; 20]); + let call_key = abi::CallKey([8; 32]); + let mut wildcard = internal_message_allocation(); + wildcard.recipient = Some(recipient); + let mut exact = wildcard.clone(); + exact.call_key = Some(call_key); + exact.on = gl_call::On::Accepted; + let nodes = vec![wildcard, exact]; + + assert!( + resolve_internal_allocation(&nodes, gl_call::On::Finalized, recipient, call_key,).is_none() + ); +} + +#[test] +fn internal_allocation_selects_phase_within_equal_keys() { + let recipient = calldata::Address::from([7; 20]); + let call_key = abi::CallKey([8; 32]); + let mut finalized = internal_message_allocation(); + finalized.budget = Some(U256::one()); + let mut decided = finalized.clone(); + decided.on = gl_call::On::Accepted; + decided.budget = Some(U256::from(2)); + let nodes = vec![finalized, decided]; + + let (matched, _) = + resolve_internal_allocation(&nodes, gl_call::On::Accepted, recipient, call_key) + .expect("decided allocation should match"); + + assert_eq!(nodes[matched].budget, Some(U256::from(2))); +} + +#[test] +fn external_allocation_candidates_follow_consensus_precedence() { + let recipient = calldata::Address::from([7; 20]); + let call_key = abi::CallKey([8; 32]); + let mut global_wildcard = external_message_allocation(); + global_wildcard.budget = Some(U256::one()); + let mut recipient_wildcard = global_wildcard.clone(); + recipient_wildcard.recipient = Some(recipient); + recipient_wildcard.budget = Some(U256::from(2)); + let mut exact = recipient_wildcard.clone(); + exact.call_key = Some(call_key); + exact.budget = Some(U256::from(3)); + let nodes = vec![global_wildcard, recipient_wildcard, exact]; + + let candidates = external_allocation_candidates(&nodes, recipient, call_key); + let budgets = candidates + .into_iter() + .map(|index| nodes[index].budget) + .collect::>(); + + assert_eq!( + budgets, + vec![Some(U256::from(3)), Some(U256::from(2)), Some(U256::one())] + ); +} + +#[test] +fn external_allocation_candidates_include_zero_budget_nodes() { + let recipient = calldata::Address::from([7; 20]); + let call_key = abi::CallKey([8; 32]); + let mut node = external_message_allocation(); + node.recipient = Some(recipient); + node.call_key = Some(call_key); + node.budget = Some(U256::zero()); + let nodes = vec![node]; + + assert_eq!( + external_allocation_candidates(&nodes, recipient, call_key), + vec![0] + ); +} diff --git a/executor/tests/fees_abi.rs b/executor/tests/fees_abi.rs deleted file mode 100644 index e1cc4492..00000000 --- a/executor/tests/fees_abi.rs +++ /dev/null @@ -1,59 +0,0 @@ -use genvm::domain::fees::{ - ExternalMessageParams, MessageAllocationNode, MessageAllocationNodeParams, CALL_KEY_WILDCARD, -}; -use primitive_types::U256; - -fn word(buf: &[u8], index: usize) -> U256 { - U256::from_big_endian(&buf[index * 32..index * 32 + 32]) -} - -fn node(budget: u64, children: Vec) -> MessageAllocationNode { - MessageAllocationNode { - recipient: Some(genlayer_sdk::calldata::Address::from([budget as u8; 20])), - call_key: None, - budget: U256::from(budget), - on: genlayer_sdk::abi::gl_call::On::Finalized, - fee_params: MessageAllocationNodeParams::External(ExternalMessageParams { - gas_limit: U256::one(), - max_gas_price: U256::one(), - }), - children, - } -} - -fn element_index(encoded: &[u8], index: usize) -> usize { - let heads_base = 2 * 32; - (heads_base + word(encoded, 2 + index).as_usize()) / 32 -} - -#[test] -fn subtree_uses_raw_array_transport_and_wildcard() { - let encoded = node(7, vec![]).abi_encode(); - let root = element_index(&encoded, 0); - - assert_eq!(word(&encoded, 0), U256::from(0x20), "array offset"); - assert_eq!(word(&encoded, 1), U256::one(), "array node count"); - assert_eq!(word(&encoded, root + 2), U256::MAX, "root sentinel"); - assert_eq!( - word(&encoded, root + 4), - U256::from_big_endian(&CALL_KEY_WILDCARD.0), - "call-key wildcard" - ); -} - -#[test] -fn subtree_contains_matched_node_then_descendants_in_bfs_order() { - let root = node(10, vec![node(20, vec![node(40, vec![])]), node(30, vec![])]); - let encoded = root.abi_encode(); - - assert_eq!(word(&encoded, 1), U256::from(4)); - let budgets = (0..4) - .map(|index| word(&encoded, element_index(&encoded, index) + 5)) - .collect::>(); - assert_eq!(budgets, [10, 20, 30, 40].map(U256::from), "BFS node order"); - assert_eq!( - word(&encoded, element_index(&encoded, 3) + 2), - U256::one(), - "grandchild parent index" - ); -} diff --git a/tests/integration/stable/py/balances/balance.0.stdout b/tests/integration/stable/py/balances/balance.0.stdout index c36ff288..e59e2aa0 100644 --- a/tests/integration/stable/py/balances/balance.0.stdout +++ b/tests/integration/stable/py/balances/balance.0.stdout @@ -7,4 +7,4 @@ main At(self) 10 nested self 10 nested At(self) 10 executed with `Return(null)` -{"address":addr#0200000000000000000000000000000000000000,"call_key":b#0000000000000000000000000000000000000000000000000000000000000000,"calldata":{},"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"subtree":b#00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004,"type":"InternalMessage","use_balance":false,"value":5} +{"address":addr#0200000000000000000000000000000000000000,"call_key":b#0000000000000000000000000000000000000000000000000000000000000000,"calldata":{},"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"subtree":b#,"type":"InternalMessage","use_balance":false,"value":5} diff --git a/tests/integration/stable/py/balances/sandbox_overspend.0.stdout b/tests/integration/stable/py/balances/sandbox_overspend.0.stdout index cf9d656f..fc1e2894 100644 --- a/tests/integration/stable/py/balances/sandbox_overspend.0.stdout +++ b/tests/integration/stable/py/balances/sandbox_overspend.0.stdout @@ -2,4 +2,4 @@ balance before=100 after_first_send=40 sandbox result=VMError(message='exit_code 1') balance final=40 executed with `Return(null)` -{"address":addr#0200000000000000000000000000000000000000,"call_key":b#0000000000000000000000000000000000000000000000000000000000000000,"calldata":{},"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"subtree":b#00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004,"type":"InternalMessage","use_balance":false,"value":60} +{"address":addr#0200000000000000000000000000000000000000,"call_key":b#0000000000000000000000000000000000000000000000000000000000000000,"calldata":{},"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"subtree":b#,"type":"InternalMessage","use_balance":false,"value":60} diff --git a/tests/integration/stable/py/balances/sandbox_overspend_2.0.stdout b/tests/integration/stable/py/balances/sandbox_overspend_2.0.stdout index 713ec074..2da76acc 100644 --- a/tests/integration/stable/py/balances/sandbox_overspend_2.0.stdout +++ b/tests/integration/stable/py/balances/sandbox_overspend_2.0.stdout @@ -3,4 +3,4 @@ sandbox result=Return(calldata=40) balance after sandbox=40 transfer failed with error: 7: inbalance balance final=40 executed with `Return(null)` -{"address":addr#0200000000000000000000000000000000000000,"call_key":b#0000000000000000000000000000000000000000000000000000000000000000,"calldata":{},"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"subtree":b#00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004,"type":"InternalMessage","use_balance":false,"value":60} +{"address":addr#0200000000000000000000000000000000000000,"call_key":b#0000000000000000000000000000000000000000000000000000000000000000,"calldata":{},"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"subtree":b#,"type":"InternalMessage","use_balance":false,"value":60} diff --git a/tests/integration/stable/py/intercontract/deploy.0.stdout b/tests/integration/stable/py/intercontract/deploy.0.stdout index 290e9750..74469cbc 100644 --- a/tests/integration/stable/py/intercontract/deploy.0.stdout +++ b/tests/integration/stable/py/intercontract/deploy.0.stdout @@ -1,2 +1,2 @@ executed with `Return(null)` -{"calldata":{},"code":b#6e6f74207265616c6c79206120636f6e7472616374,"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"salt_nonce":0,"subtree":b#00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004,"type":"InternalDeployMessage","use_balance":false,"value":0} +{"calldata":{},"code":b#6e6f74207265616c6c79206120636f6e7472616374,"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"salt_nonce":0,"subtree":b#,"type":"InternalDeployMessage","use_balance":false,"value":0} diff --git a/tests/integration/stable/py/intercontract/deploy_salt.0.stdout b/tests/integration/stable/py/intercontract/deploy_salt.0.stdout index 4b1a01e0..e5e2f3e7 100644 --- a/tests/integration/stable/py/intercontract/deploy_salt.0.stdout +++ b/tests/integration/stable/py/intercontract/deploy_salt.0.stdout @@ -1,3 +1,3 @@ 0xf539Cb83f077Cd01BDd1a4E002866dCC0D15D633 executed with `Return(null)` -{"calldata":{},"code":b#6e6f74207265616c6c79206120636f6e7472616374,"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"salt_nonce":1,"subtree":b#00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004,"type":"InternalDeployMessage","use_balance":false,"value":0} +{"calldata":{},"code":b#6e6f74207265616c6c79206120636f6e7472616374,"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"salt_nonce":1,"subtree":b#,"type":"InternalDeployMessage","use_balance":false,"value":0} diff --git a/tests/integration/stable/py/intercontract/send_message.0.hash b/tests/integration/stable/py/intercontract/send_message.0.hash index 78190cbf..07645ee5 100644 --- a/tests/integration/stable/py/intercontract/send_message.0.hash +++ b/tests/integration/stable/py/intercontract/send_message.0.hash @@ -1 +1 @@ -hBKWwN4bJVnitIwkEATYlbEikUfb5VmvFK5qbEVZOQc= +RKQjm5XN5bqnQSw34Mh7sUHEVdTduF7nyDPlc8rZh6E= diff --git a/tests/integration/stable/py/intercontract/send_message.0.stdout b/tests/integration/stable/py/intercontract/send_message.0.stdout index 139d25f5..a5e58096 100644 --- a/tests/integration/stable/py/intercontract/send_message.0.stdout +++ b/tests/integration/stable/py/intercontract/send_message.0.stdout @@ -1,2 +1,2 @@ executed with `Return(null)` -{"address":addr#3030303030303030303030303030303030303030,"call_key":b#666f6f0000000000000000000000000000000000000000000000000000000000,"calldata":{"":"foo","args":[1,2]},"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":20,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":20,"validator_timeunits_allocation":5},"message_fee":0,"on":"finalized","receipt_fee":0,"subtree":b#00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004,"type":"InternalMessage","use_balance":false,"value":0} +{"address":addr#3030303030303030303030303030303030303030,"call_key":b#666f6f0000000000000000000000000000000000000000000000000000000000,"calldata":{"":"foo","args":[1,2]},"fee_params":{"execution_budget_per_round":1,"leader_timeunits_allocation":1,"max_price_gen_per_time_unit":1,"receipt_fee_max_gas_price":1,"rotations":[0],"storage_fee_max_gas_price":1,"validator_timeunits_allocation":1},"message_fee":60,"on":"finalized","receipt_fee":0,"subtree":b#00ff010203,"type":"InternalMessage","use_balance":false,"value":0} diff --git a/tests/integration/stable/py/intercontract/send_message.1.hash b/tests/integration/stable/py/intercontract/send_message.1.hash new file mode 100644 index 00000000..23cbea96 --- /dev/null +++ b/tests/integration/stable/py/intercontract/send_message.1.hash @@ -0,0 +1 @@ +cYRIoFIX+coyA621S+UI6LOM0C0sSijvNJzBRrOOf5A= diff --git a/tests/integration/stable/py/intercontract/send_message.1.stdout b/tests/integration/stable/py/intercontract/send_message.1.stdout new file mode 100644 index 00000000..720b1907 --- /dev/null +++ b/tests/integration/stable/py/intercontract/send_message.1.stdout @@ -0,0 +1 @@ +executed with `VMError("OOM fees internal")` diff --git a/tests/integration/stable/py/intercontract/send_message.jsonnet b/tests/integration/stable/py/intercontract/send_message.jsonnet index 69040f97..f4f819d3 100644 --- a/tests/integration/stable/py/intercontract/send_message.jsonnet +++ b/tests/integration/stable/py/intercontract/send_message.jsonnet @@ -1,3 +1,28 @@ local simple_deploy = import 'templates/simple_deploy.jsonnet'; local util = import 'templates/util.jsonnet'; -{tags: ["feature-message-send", "python"], entry: util.addPaths([simple_deploy.run('${jsonnetDir}/send_message.py')])} +{ + tags: ["feature-message-send", "python"], + entry: util.addPaths([ + simple_deploy.run('${jsonnetDir}/send_message.py') { + bucket_totals: {execution_data_gas: 0, message_fee: messageBudget}, + message_fee_allocation: [{ + recipient: null, + call_key: null, + budget: 100, + on: 'finalized', + fee_params: {Internal: { + leader_timeunits_allocation: 1, + validator_timeunits_allocation: 1, + execution_budget_per_round: 1, + rotations: [0], + max_price_gen_per_time_unit: 1, + storage_fee_max_gas_price: 1, + receipt_fee_max_gas_price: 1, + }}, + children_budget: 60, + subtree: [0, 255, 1, 2, 3], + }], + } + for messageBudget in [60, 59] + ]), +} diff --git a/tests/integration/stable/py/intercontract/send_message_on.0_0.stdout b/tests/integration/stable/py/intercontract/send_message_on.0_0.stdout index b6ba3825..6b062848 100644 --- a/tests/integration/stable/py/intercontract/send_message_on.0_0.stdout +++ b/tests/integration/stable/py/intercontract/send_message_on.0_0.stdout @@ -1,2 +1,2 @@ executed with `Return(null)` -{"address":addr#3030303030303030303030303030303030303030,"call_key":b#666f6f0000000000000000000000000000000000000000000000000000000000,"calldata":{"":"foo","args":[1,2]},"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":1606938044258990275541962092341162602522202993782792835301376,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":1606938044258990275541962092341162602522202993782792835301376,"validator_timeunits_allocation":5},"message_fee":0,"on":"decided","receipt_fee":0,"subtree":b#00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004,"type":"InternalMessage","use_balance":false,"value":0} +{"address":addr#3030303030303030303030303030303030303030,"call_key":b#666f6f0000000000000000000000000000000000000000000000000000000000,"calldata":{"":"foo","args":[1,2]},"fee_params":{"execution_budget_per_round":1024,"leader_timeunits_allocation":5,"max_price_gen_per_time_unit":1606938044258990275541962092341162602522202993782792835301376,"receipt_fee_max_gas_price":1606938044258990275541962092341162602522202993782792835301376,"rotations":[4,4,4,4,4],"storage_fee_max_gas_price":1606938044258990275541962092341162602522202993782792835301376,"validator_timeunits_allocation":5},"message_fee":0,"on":"decided","receipt_fee":0,"subtree":b#,"type":"InternalMessage","use_balance":false,"value":0}