From 3135f8344a9104a11ac02f7d8b7d0e49f39bab79 Mon Sep 17 00:00:00 2001 From: Gregory Sobol Date: Tue, 5 Mar 2024 21:10:12 +0300 Subject: [PATCH] fix!(pallet-gear): append outgoing bytes limit to prevent runtime allocator overflow (#3743) --- core-errors/src/lib.rs | 6 + core-processor/src/common.rs | 12 + core-processor/src/configs.rs | 2 + core-processor/src/executor.rs | 48 ++- core-processor/src/ext.rs | 78 ++--- core-processor/src/processing.rs | 25 +- core/src/message/context.rs | 329 +++++++++++++----- core/src/message/handle.rs | 4 + core/src/message/init.rs | 4 + core/src/message/mod.rs | 14 +- core/src/message/reply.rs | 4 + gtest/src/manager.rs | 2 + node/service/src/lib.rs | 2 + pallets/gear-builtin/src/lib.rs | 9 +- pallets/gear-builtin/src/mock.rs | 1 + .../gear-builtin/src/tests/bad_builtin_ids.rs | 1 + pallets/gear-debug/src/mock.rs | 1 + pallets/gear-scheduler/src/mock.rs | 1 + pallets/gear/src/benchmarking/mod.rs | 7 +- pallets/gear/src/benchmarking/utils.rs | 1 + pallets/gear/src/lib.rs | 7 +- pallets/gear/src/mock.rs | 1 + pallets/gear/src/pallet_tests.rs | 1 + pallets/gear/src/queue.rs | 2 +- pallets/gear/src/tests.rs | 131 ++++++- pallets/payment/src/mock.rs | 1 + runtime/vara/src/lib.rs | 4 + utils/wasm-gen/src/tests.rs | 5 +- 28 files changed, 527 insertions(+), 176 deletions(-) diff --git a/core-errors/src/lib.rs b/core-errors/src/lib.rs index 87d07cb0e13..c932970fbe8 100644 --- a/core-errors/src/lib.rs +++ b/core-errors/src/lib.rs @@ -157,6 +157,11 @@ pub enum MessageError { )] IncorrectMessageForReplyDeposit = 310, + /// The error occurs when program tries to send messages + /// with total size bigger than allowed. + #[display(fmt = "Outgoing messages bytes limit exceeded")] + OutgoingMessagesBytesLimitExceeded = 311, + // TODO: remove after delay refactoring is done /// An error occurs in attempt to charge gas for dispatch stash hold. #[display(fmt = "Not enough gas to hold dispatch message")] @@ -262,6 +267,7 @@ impl ExtError { 308 => Some(MessageError::InsufficientGasLimit.into()), 309 => Some(MessageError::DuplicateReplyDeposit.into()), 310 => Some(MessageError::IncorrectMessageForReplyDeposit.into()), + 311 => Some(MessageError::OutgoingMessagesBytesLimitExceeded.into()), 399 => Some(MessageError::InsufficientGasForDelayedSending.into()), // 500 => Some(ReservationError::InvalidReservationId.into()), diff --git a/core-processor/src/common.rs b/core-processor/src/common.rs index 8820c4e56e5..df84dfa68bc 100644 --- a/core-processor/src/common.rs +++ b/core-processor/src/common.rs @@ -462,6 +462,13 @@ pub enum ActorExecutionErrorReplyReason { /// Trap explanation #[display(fmt = "{_0}")] Trap(TrapExplanation), + // TODO: move this to SystemExecutionError after runtime upgrade, + // if wait-list does not contain messages with total outgoing bytes more than `OutgoingBytesLimit` #3751. + /// Message is not supported now + #[display( + fmt = "Message is not supported: outgoing bytes limit is exceeded after runtime-upgrade" + )] + UnsupportedMessage, } impl ActorExecutionErrorReplyReason { @@ -480,6 +487,7 @@ impl ActorExecutionErrorReplyReason { TrapExplanation::StackLimitExceeded => SimpleExecutionError::StackLimitExceeded, TrapExplanation::Unknown => SimpleExecutionError::UnreachableInstruction, }, + Self::UnsupportedMessage => SimpleExecutionError::Unsupported, } } } @@ -501,6 +509,10 @@ pub enum SystemExecutionError { /// Error during `into_ext_info()` call #[display(fmt = "`into_ext_info()` error: {_0}")] IntoExtInfo(MemoryError), + // TODO: uncomment when #3751 + // /// Incoming dispatch store has too many outgoing messages total bytes. + // #[display(fmt = "Incoming dispatch store has too many outgoing messages total bytes")] + // MessageStoreOutgoingBytesOverflow, } /// Actor. diff --git a/core-processor/src/configs.rs b/core-processor/src/configs.rs index 8fe65dddbc9..9b2512e8871 100644 --- a/core-processor/src/configs.rs +++ b/core-processor/src/configs.rs @@ -188,6 +188,8 @@ pub struct BlockConfig { pub existential_deposit: u128, /// Outgoing limit. pub outgoing_limit: u32, + /// Outgoing bytes limit. + pub outgoing_bytes_limit: u32, /// Host function weights. pub host_fn_weights: HostFnWeights, /// Forbidden functions. diff --git a/core-processor/src/executor.rs b/core-processor/src/executor.rs index ca39e413b0d..98df95b8ef5 100644 --- a/core-processor/src/executor.rs +++ b/core-processor/src/executor.rs @@ -182,7 +182,14 @@ where AllocationsContext::new(allocations.clone(), static_pages, settings.max_pages); // Creating message context. - let message_context = MessageContext::new(dispatch.clone(), program_id, msg_ctx_settings); + let Some(message_context) = MessageContext::new(dispatch.clone(), program_id, msg_ctx_settings) + else { + return Err(ActorExecutionError { + gas_amount: gas_counter.to_amount(), + reason: ActorExecutionErrorReplyReason::UnsupportedMessage, + } + .into()); + }; // Creating value counter. // @@ -380,30 +387,33 @@ where 0.into() }; + let message_context = MessageContext::new( + IncomingDispatch::new( + DispatchKind::Handle, + IncomingMessage::new( + Default::default(), + Default::default(), + payload + .try_into() + .map_err(|e| format!("Failed to create payload: {e:?}"))?, + gas_limit, + Default::default(), + Default::default(), + ), + None, + ), + program.id(), + Default::default(), + ) + .ok_or("Incorrect message store context: out of outgoing bytes limit")?; + let context = ProcessorContext { gas_counter: GasCounter::new(gas_limit), gas_allowance_counter: GasAllowanceCounter::new(gas_limit), gas_reserver: GasReserver::new(&Default::default(), Default::default(), Default::default()), value_counter: ValueCounter::new(Default::default()), allocations_context: AllocationsContext::new(allocations, static_pages, 512.into()), - message_context: MessageContext::new( - IncomingDispatch::new( - DispatchKind::Handle, - IncomingMessage::new( - Default::default(), - Default::default(), - payload - .try_into() - .map_err(|e| format!("Failed to create payload: {e:?}"))?, - gas_limit, - Default::default(), - Default::default(), - ), - None, - ), - program.id(), - ContextSettings::new(0, 0, 0, 0, 0, 0), - ), + message_context, block_info, performance_multiplier: gsys::Percent::new(100), max_pages: 512.into(), diff --git a/core-processor/src/ext.rs b/core-processor/src/ext.rs index 130633a2374..3dd6122ee93 100644 --- a/core-processor/src/ext.rs +++ b/core-processor/src/ext.rs @@ -114,7 +114,7 @@ pub struct ProcessorContext { impl ProcessorContext { /// Create new mock [`ProcessorContext`] for usage in tests. pub fn new_mock() -> ProcessorContext { - use gear_core::message::{ContextSettings, IncomingDispatch}; + use gear_core::message::IncomingDispatch; ProcessorContext { gas_counter: GasCounter::new(0), @@ -134,8 +134,9 @@ impl ProcessorContext { message_context: MessageContext::new( Default::default(), Default::default(), - ContextSettings::new(0, 0, 0, 0, 0, 0), - ), + Default::default(), + ) + .unwrap(), block_info: Default::default(), performance_multiplier: gsys::Percent::new(100), max_pages: 512.into(), @@ -626,13 +627,13 @@ impl Ext { fn charge_sending_fee(&mut self, delay: u32) -> Result<(), ChargeError> { if delay == 0 { - self.charge_gas_if_enough(self.context.message_context.settings().sending_fee()) + self.charge_gas_if_enough(self.context.message_context.settings().sending_fee) } else { self.charge_gas_if_enough( self.context .message_context .settings() - .scheduled_sending_fee(), + .scheduled_sending_fee, ) } } @@ -1023,7 +1024,7 @@ impl Externalities for Ext { amount: u64, duration: u32, ) -> Result { - self.charge_gas_if_enough(self.context.message_context.settings().reservation_fee())?; + self.charge_gas_if_enough(self.context.message_context.settings().reservation_fee)?; if duration == 0 { return Err(ReservationError::ZeroReservationDuration.into()); @@ -1090,7 +1091,7 @@ impl Externalities for Ext { } fn wait(&mut self) -> Result<(), Self::UnrecoverableError> { - self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?; + self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee)?; if self.context.message_context.reply_sent() { return Err(UnrecoverableWaitError::WaitAfterReply.into()); @@ -1107,7 +1108,7 @@ impl Externalities for Ext { } fn wait_for(&mut self, duration: u32) -> Result<(), Self::UnrecoverableError> { - self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?; + self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee)?; if self.context.message_context.reply_sent() { return Err(UnrecoverableWaitError::WaitAfterReply.into()); @@ -1128,7 +1129,7 @@ impl Externalities for Ext { } fn wait_up_to(&mut self, duration: u32) -> Result { - self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?; + self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee)?; if self.context.message_context.reply_sent() { return Err(UnrecoverableWaitError::WaitAfterReply.into()); @@ -1153,7 +1154,7 @@ impl Externalities for Ext { } fn wake(&mut self, waker_id: MessageId, delay: u32) -> Result<(), Self::FallibleError> { - self.charge_gas_if_enough(self.context.message_context.settings().waking_fee())?; + self.charge_gas_if_enough(self.context.message_context.settings().waking_fee)?; self.context.message_context.wake(waker_id, delay)?; Ok(()) @@ -1223,12 +1224,7 @@ mod tests { struct MessageContextBuilder { incoming_dispatch: IncomingDispatch, program_id: ProgramId, - sending_fee: u64, - scheduled_sending_fee: u64, - waiting_fee: u64, - waking_fee: u64, - reservation_fee: u64, - outgoing_limit: u32, + context_settings: ContextSettings, } impl MessageContextBuilder { @@ -1236,12 +1232,7 @@ mod tests { Self { incoming_dispatch: Default::default(), program_id: Default::default(), - sending_fee: 0, - scheduled_sending_fee: 0, - waiting_fee: 0, - waking_fee: 0, - reservation_fee: 0, - outgoing_limit: 0, + context_settings: ContextSettings::with_outgoing_limits(u32::MAX, u32::MAX), } } @@ -1249,19 +1240,14 @@ mod tests { MessageContext::new( self.incoming_dispatch, self.program_id, - ContextSettings::new( - self.sending_fee, - self.scheduled_sending_fee, - self.waiting_fee, - self.waking_fee, - self.reservation_fee, - self.outgoing_limit, - ), + self.context_settings, ) + .unwrap() } fn with_outgoing_limit(mut self, outgoing_limit: u32) -> Self { - self.outgoing_limit = outgoing_limit; + self.context_settings.outgoing_limit = outgoing_limit; + self } } @@ -1461,11 +1447,7 @@ mod tests { fn test_send_push() { let mut ext = Ext::new( ProcessorContextBuilder::new() - .with_message_context( - MessageContextBuilder::new() - .with_outgoing_limit(u32::MAX) - .build(), - ) + .with_message_context(MessageContextBuilder::new().build()) .build(), ); @@ -1529,11 +1511,7 @@ mod tests { fn test_send_push_input() { let mut ext = Ext::new( ProcessorContextBuilder::new() - .with_message_context( - MessageContextBuilder::new() - .with_outgoing_limit(u32::MAX) - .build(), - ) + .with_message_context(MessageContextBuilder::new().build()) .build(), ); @@ -1595,11 +1573,7 @@ mod tests { let mut ext = Ext::new( ProcessorContextBuilder::new() .with_gas(GasCounter::new(u64::MAX)) - .with_message_context( - MessageContextBuilder::new() - .with_outgoing_limit(u32::MAX) - .build(), - ) + .with_message_context(MessageContextBuilder::new().build()) .build(), ); @@ -1636,11 +1610,7 @@ mod tests { let mut ext = Ext::new( ProcessorContextBuilder::new() .with_gas(GasCounter::new(u64::MAX)) - .with_message_context( - MessageContextBuilder::new() - .with_outgoing_limit(u32::MAX) - .build(), - ) + .with_message_context(MessageContextBuilder::new().build()) .build(), ); @@ -1691,11 +1661,7 @@ mod tests { fn test_reply_push_input() { let mut ext = Ext::new( ProcessorContextBuilder::new() - .with_message_context( - MessageContextBuilder::new() - .with_outgoing_limit(u32::MAX) - .build(), - ) + .with_message_context(MessageContextBuilder::new().build()) .build(), ); diff --git a/core-processor/src/processing.rs b/core-processor/src/processing.rs index fc836c44d72..592dffd3151 100644 --- a/core-processor/src/processing.rs +++ b/core-processor/src/processing.rs @@ -66,6 +66,7 @@ where page_costs, existential_deposit, outgoing_limit, + outgoing_bytes_limit, host_fn_weights, forbidden_funcs, mailbox_threshold, @@ -117,14 +118,21 @@ where // // Waking fee: double write cost for removal from waitlist // and further enqueueing. - let msg_ctx_settings = ContextSettings::new( - write_cost.saturating_mul(2), - write_cost.saturating_mul(4), - write_cost.saturating_mul(3), - write_cost.saturating_mul(2), - write_cost.saturating_mul(2), + let msg_ctx_settings = ContextSettings { + sending_fee: write_cost.saturating_mul(2), + scheduled_sending_fee: write_cost.saturating_mul(4), + waiting_fee: write_cost.saturating_mul(3), + waking_fee: write_cost.saturating_mul(2), + reservation_fee: write_cost.saturating_mul(2), outgoing_limit, - ); + outgoing_bytes_limit, + }; + + // TODO: add tests that system reservation is successfully unreserved after + // actor execution error #3756. + + // Get system reservation context in order to use it if actor execution error occurs. + let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch); let exec_result = executor::execute_wasm::( balance, @@ -158,12 +166,11 @@ where process_allowance_exceed(dispatch, program_id, res.gas_amount.burned()) } }), - // TODO: we must use message reservation context here instead of default #3718 Err(ExecutionError::Actor(e)) => Ok(process_execution_error( dispatch, program_id, e.gas_amount.burned(), - SystemReservationContext::default(), + system_reservation_ctx, e.reason, )), Err(ExecutionError::System(e)) => Err(e), diff --git a/core/src/message/context.rs b/core/src/message/context.rs index d08b102b8c9..157cf214107 100644 --- a/core/src/message/context.rs +++ b/core/src/message/context.rs @@ -34,76 +34,36 @@ use scale_info::{ TypeInfo, }; -use super::{DispatchKind, IncomingDispatch}; +use super::{DispatchKind, IncomingDispatch, Packet}; /// Context settings. -#[derive( - Copy, Clone, Default, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Decode, Encode, TypeInfo, -)] +#[derive(Copy, Clone, Debug, Default)] pub struct ContextSettings { /// Fee for sending message. - sending_fee: u64, + pub sending_fee: u64, /// Fee for sending scheduled message. - scheduled_sending_fee: u64, + pub scheduled_sending_fee: u64, /// Fee for calling wait. - waiting_fee: u64, + pub waiting_fee: u64, /// Fee for waking messages. - waking_fee: u64, + pub waking_fee: u64, /// Fee for creating reservation. - reservation_fee: u64, - /// Limit of outgoing messages that program can send during execution of current message. - outgoing_limit: u32, + pub reservation_fee: u64, + /// Limit of outgoing messages, that program can send in current message processing. + pub outgoing_limit: u32, + /// Limit of bytes in outgoing messages during current execution. + pub outgoing_bytes_limit: u32, } impl ContextSettings { - /// Create new ContextSettings. - pub fn new( - sending_fee: u64, - scheduled_sending_fee: u64, - waiting_fee: u64, - waking_fee: u64, - reservation_fee: u64, - outgoing_limit: u32, - ) -> Self { + /// Returns default settings with specified outgoing messages limits. + pub fn with_outgoing_limits(outgoing_limit: u32, outgoing_bytes_limit: u32) -> Self { Self { - sending_fee, - scheduled_sending_fee, - waiting_fee, - waking_fee, - reservation_fee, outgoing_limit, + outgoing_bytes_limit, + ..Default::default() } } - - /// Getter for inner sending fee field. - pub fn sending_fee(&self) -> u64 { - self.sending_fee - } - - /// Getter for inner scheduled sending fee field. - pub fn scheduled_sending_fee(&self) -> u64 { - self.scheduled_sending_fee - } - - /// Getter for inner waiting fee field. - pub fn waiting_fee(&self) -> u64 { - self.waiting_fee - } - - /// Getter for inner waking fee field. - pub fn waking_fee(&self) -> u64 { - self.waking_fee - } - - /// Getter for inner reservation fee field. - pub fn reservation_fee(&self) -> u64 { - self.reservation_fee - } - - /// Getter for inner outgoing limit field. - pub fn outgoing_limit(&self) -> u32 { - self.outgoing_limit - } } /// Dispatch or message with additional information. @@ -125,7 +85,7 @@ pub struct ContextOutcomeDrain { /// Context outcome. /// /// Contains all outgoing messages and wakes that should be done after execution. -#[derive(Default, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Decode, Encode, TypeInfo)] +#[derive(Default, Debug)] pub struct ContextOutcome { init: Vec>, handle: Vec>, @@ -241,31 +201,50 @@ impl ContextStore { } /// Context of currently processing incoming message. -#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Decode, Encode, TypeInfo)] +#[derive(Debug)] pub struct MessageContext { kind: DispatchKind, current: IncomingMessage, outcome: ContextOutcome, store: ContextStore, settings: ContextSettings, + outgoing_bytes_counter: u32, } impl MessageContext { - /// Create new MessageContext with given ContextSettings. + /// Create new message context. + /// Returns `None` if outgoing messages bytes limit exceeded. pub fn new( dispatch: IncomingDispatch, program_id: ProgramId, settings: ContextSettings, - ) -> Self { + ) -> Option { let (kind, message, store) = dispatch.into_parts(); - Self { + let outgoing_bytes_counter = match &store { + Some(store) => { + let mut counter = 0u32; + for payload in store.outgoing.values().filter_map(|x| x.as_ref()) { + counter = counter.checked_add(payload.len_u32())?; + } + counter + } + None => 0, + }; + + if outgoing_bytes_counter > settings.outgoing_bytes_limit { + // Outgoing messages bytes limit exceeded. + return None; + } + + Some(Self { kind, outcome: ContextOutcome::new(program_id, message.source(), message.id()), current: message, store: store.unwrap_or_default(), settings, - } + outgoing_bytes_counter, + }) } /// Getter for inner settings. @@ -330,6 +309,18 @@ impl MessageContext { ) -> Result { if let Some(payload) = self.store.outgoing.get_mut(&handle) { if let Some(data) = payload.take() { + let Some(new_outgoing_bytes) = self + .outgoing_bytes_counter + .checked_add(packet.payload_len()) + .and_then(|counter| { + (counter <= self.settings.outgoing_bytes_limit).then_some(counter) + }) + else { + *payload = Some(data); + return Err(Error::OutgoingMessagesBytesLimitExceeded); + }; + + // TODO: set data back if error #3779 let packet = { let mut packet = packet; packet @@ -343,6 +334,15 @@ impl MessageContext { self.outcome.handle.push((message, delay, reservation)); + // Increasing `outgoing_bytes_counter`, instead of decreasing it, because + // this counter takes into account also messages, that are already committed + // during this execution. + // The message subsequent executions will recalculate this counter from + // store outgoing messages (see `Self::new`), + // so committed during this execution messages won't be taken into account + // during next executions. + self.outgoing_bytes_counter = new_outgoing_bytes; + Ok(message_id) } else { Err(Error::LateAccess) @@ -371,8 +371,17 @@ impl MessageContext { pub fn send_push(&mut self, handle: u32, buffer: &[u8]) -> Result<(), Error> { match self.store.outgoing.get_mut(&handle) { Some(Some(data)) => { + let new_outgoing_bytes = u32::try_from(buffer.len()) + .ok() + .and_then(|bytes_amount| self.outgoing_bytes_counter.checked_add(bytes_amount)) + .and_then(|counter| { + (counter <= self.settings.outgoing_bytes_limit).then_some(counter) + }) + .ok_or(Error::OutgoingMessagesBytesLimitExceeded)?; + data.try_extend_from_slice(buffer) .map_err(|_| Error::MaxMessageSizeExceed)?; + self.outgoing_bytes_counter = new_outgoing_bytes; Ok(()) } Some(None) => Err(Error::LateAccess), @@ -395,8 +404,19 @@ impl MessageContext { excluded_end, } = range; + let bytes_amount = excluded_end.checked_sub(offset).unwrap_or_else(|| { + unreachable!("`CheckedRange` must guarantee that `excluded_end` >= `offset`") + }); + + let new_outgoing_bytes = u32::try_from(bytes_amount) + .ok() + .and_then(|bytes_amount| self.outgoing_bytes_counter.checked_add(bytes_amount)) + .and_then(|counter| (counter <= self.settings.outgoing_bytes_limit).then_some(counter)) + .ok_or(Error::OutgoingMessagesBytesLimitExceeded)?; + data.try_extend_from_slice(&self.current.payload_bytes()[offset..excluded_end]) .map_err(|_| Error::MaxMessageSizeExceed)?; + self.outgoing_bytes_counter = new_outgoing_bytes; Ok(()) } @@ -439,6 +459,7 @@ impl MessageContext { if !self.reply_sent() { let data = self.store.reply.take().unwrap_or_default(); + // TODO: set data back if error #3779 let packet = { let mut packet = packet; packet @@ -601,13 +622,18 @@ mod tests { }; } + // Set of constants for clarity of a part of the test + const INCOMING_MESSAGE_ID: u64 = 3; + const INCOMING_MESSAGE_SOURCE: u64 = 4; + #[test] fn duplicated_init() { let mut message_context = MessageContext::new( Default::default(), Default::default(), - ContextSettings::new(0, 0, 0, 0, 0, 1024), - ); + ContextSettings::with_outgoing_limits(1024, u32::MAX), + ) + .expect("Outgoing messages bytes limit exceeded"); // first init to default ProgramId. assert_ok!(message_context.init_program(Default::default(), 0)); @@ -619,6 +645,144 @@ mod tests { ); } + #[test] + fn send_push_bytes_exceeded() { + let mut message_context = MessageContext::new( + Default::default(), + Default::default(), + ContextSettings::with_outgoing_limits(1024, 10), + ) + .expect("Outgoing messages bytes limit exceeded"); + + let handle = message_context.send_init().unwrap(); + + // push 5 bytes + assert_ok!(message_context.send_push(handle, &[1, 2, 3, 4, 5])); + + // push 5 bytes + assert_ok!(message_context.send_push(handle, &[1, 2, 3, 4, 5])); + + // push 1 byte should get error. + assert_err!( + message_context.send_push(handle, &[1]), + Error::OutgoingMessagesBytesLimitExceeded, + ); + } + + #[test] + fn send_commit_bytes_exceeded() { + let mut message_context = MessageContext::new( + Default::default(), + Default::default(), + ContextSettings::with_outgoing_limits(1024, 10), + ) + .expect("Outgoing messages bytes limit exceeded"); + + let handle = message_context.send_init().unwrap(); + + // push 5 bytes + assert_ok!(message_context.send_push(handle, &[1, 2, 3, 4, 5])); + + // commit 6 bytes should get error. + assert_err!( + message_context.send_commit( + handle, + HandlePacket::new( + Default::default(), + Payload::try_from([1, 2, 3, 4, 5, 6].to_vec()).unwrap(), + 0 + ), + 0, + None + ), + Error::OutgoingMessagesBytesLimitExceeded, + ); + } + + #[test] + fn send_push_input_bytes_exceeded() { + let incoming_message = IncomingMessage::new( + MessageId::from(INCOMING_MESSAGE_ID), + ProgramId::from(INCOMING_MESSAGE_SOURCE), + vec![1, 2, 3, 4, 5].try_into().unwrap(), + 0, + 0, + None, + ); + + let incoming_dispatch = IncomingDispatch::new(DispatchKind::Handle, incoming_message, None); + + // Creating a message context + let mut message_context = MessageContext::new( + incoming_dispatch, + Default::default(), + ContextSettings::with_outgoing_limits(1024, 10), + ) + .expect("Outgoing messages bytes limit exceeded"); + + let handle = message_context.send_init().unwrap(); + + // push 5 bytes + assert_ok!(message_context.send_push_input( + handle, + CheckedRange { + offset: 0, + excluded_end: 5, + } + )); + + // push 5 bytes + assert_ok!(message_context.send_push_input( + handle, + CheckedRange { + offset: 0, + excluded_end: 5, + } + )); + + // push 1 byte should get error. + assert_err!( + message_context.send_push_input( + handle, + CheckedRange { + offset: 0, + excluded_end: 1, + } + ), + Error::OutgoingMessagesBytesLimitExceeded, + ); + } + + #[test] + fn create_wrong_context() { + let context_store = ContextStore { + outgoing: [(1, Some(vec![1, 2].try_into().unwrap()))] + .iter() + .cloned() + .collect(), + reply: None, + initialized: BTreeSet::new(), + reservation_nonce: ReservationNonce::default(), + system_reservation: None, + }; + + let incoming_dispatch = IncomingDispatch::new( + DispatchKind::Handle, + Default::default(), + Some(context_store), + ); + + let ctx = MessageContext::new( + incoming_dispatch, + Default::default(), + ContextSettings::with_outgoing_limits(1024, 1), + ); + + // Creating a message context must return None, + // because of the outgoing messages bytes limit exceeded. + assert!(ctx.is_none(), "Expect None, got {:?}", ctx); + } + #[test] fn outgoing_limit_exceeded() { // Check that we can always send exactly outgoing_limit messages. @@ -626,10 +790,11 @@ mod tests { for n in 0..=max_n { // for outgoing_limit n checking that LimitExceeded will be after n's message. - let settings = ContextSettings::new(0, 0, 0, 0, 0, n); + let settings = ContextSettings::with_outgoing_limits(n, u32::MAX); let mut message_context = - MessageContext::new(Default::default(), Default::default(), settings); + MessageContext::new(Default::default(), Default::default(), settings) + .expect("Outgoing messages bytes limit exceeded"); // send n messages for _ in 0..n { let handle = message_context.send_init().expect("unreachable"); @@ -661,8 +826,9 @@ mod tests { let mut message_context = MessageContext::new( Default::default(), Default::default(), - ContextSettings::new(0, 0, 0, 0, 0, 1024), - ); + ContextSettings::with_outgoing_limits(1024, u32::MAX), + ) + .expect("Outgoing messages bytes limit exceeded"); // Use invalid handle 0. let out_of_bounds = message_context.send_commit(0, Default::default(), 0, None); @@ -687,8 +853,9 @@ mod tests { let mut message_context = MessageContext::new( Default::default(), Default::default(), - ContextSettings::new(0, 0, 0, 0, 0, 1024), - ); + ContextSettings::with_outgoing_limits(1024, u32::MAX), + ) + .expect("Outgoing messages bytes limit exceeded"); // First reply. assert_ok!(message_context.reply_commit(Default::default(), None)); @@ -700,10 +867,6 @@ mod tests { ); } - // Set of constants for clarity of a part of the test - const INCOMING_MESSAGE_ID: u64 = 3; - const INCOMING_MESSAGE_SOURCE: u64 = 4; - #[test] /// Test that covers full api of `MessageContext` fn message_context_api() { @@ -723,8 +886,9 @@ mod tests { let mut context = MessageContext::new( incoming_dispatch, Default::default(), - ContextSettings::new(0, 0, 0, 0, 0, 1024), - ); + ContextSettings::with_outgoing_limits(1024, u32::MAX), + ) + .expect("Outgoing messages bytes limit exceeded"); // Checking that the initial parameters of the context match the passed constants assert_eq!(context.current().id(), MessageId::from(INCOMING_MESSAGE_ID)); @@ -865,8 +1029,9 @@ mod tests { let mut context = MessageContext::new( incoming_dispatch, Default::default(), - ContextSettings::new(0, 0, 0, 0, 0, 1024), - ); + ContextSettings::with_outgoing_limits(1024, u32::MAX), + ) + .expect("Outgoing messages bytes limit exceeded"); context.wake(MessageId::default(), 10).unwrap(); @@ -892,8 +1057,9 @@ mod tests { let mut message_context = MessageContext::new( incoming_dispatch, Default::default(), - ContextSettings::new(0, 0, 0, 0, 0, 1024), - ); + ContextSettings::with_outgoing_limits(1024, u32::MAX), + ) + .expect("Outgoing messages bytes limit exceeded"); let handle = message_context.send_init().expect("unreachable"); message_context @@ -926,8 +1092,9 @@ mod tests { let mut message_context = MessageContext::new( incoming_dispatch, Default::default(), - ContextSettings::new(0, 0, 0, 0, 0, 1024), - ); + ContextSettings::with_outgoing_limits(1024, u32::MAX), + ) + .expect("Outgoing messages bytes limit exceeded"); let message_id = message_context .reply_commit(ReplyPacket::default(), None) diff --git a/core/src/message/handle.rs b/core/src/message/handle.rs index 8b8a94d27a7..cb2527a43fb 100644 --- a/core/src/message/handle.rs +++ b/core/src/message/handle.rs @@ -182,6 +182,10 @@ impl Packet for HandlePacket { self.payload.inner() } + fn payload_len(&self) -> u32 { + self.payload.len_u32() + } + fn gas_limit(&self) -> Option { self.gas_limit } diff --git a/core/src/message/init.rs b/core/src/message/init.rs index 01b92406cc3..0ef4e8f1364 100644 --- a/core/src/message/init.rs +++ b/core/src/message/init.rs @@ -189,6 +189,10 @@ impl Packet for InitPacket { self.payload.inner() } + fn payload_len(&self) -> u32 { + self.payload.len_u32() + } + fn gas_limit(&self) -> Option { self.gas_limit } diff --git a/core/src/message/mod.rs b/core/src/message/mod.rs index 2ffeb831ebb..7f89cbbf7c2 100644 --- a/core/src/message/mod.rs +++ b/core/src/message/mod.rs @@ -53,8 +53,7 @@ use gear_wasm_instrument::syscalls::SyscallName; /// Max payload size which one message can have (8 MiB). pub const MAX_PAYLOAD_SIZE: usize = 8 * 1024 * 1024; -// **WARNING**: do not remove this check until be sure that -// all `MAX_PAYLOAD_SIZE` conversions are safe! +// **WARNING**: do not remove this check const _: () = assert!(MAX_PAYLOAD_SIZE <= u32::MAX as usize); /// Payload size exceed error @@ -78,6 +77,14 @@ impl Display for PayloadSizeError { /// Payload type for message. pub type Payload = LimitedVec; +impl Payload { + /// Get payload length as u32. + pub fn len_u32(&self) -> u32 { + // Safe, cause it's guarantied: `MAX_PAYLOAD_SIZE` <= u32::MAX + self.inner().len() as u32 + } +} + /// Gas limit type for message. pub type GasLimit = u64; @@ -214,6 +221,9 @@ pub trait Packet { /// Packet payload bytes. fn payload_bytes(&self) -> &[u8]; + /// Payload len + fn payload_len(&self) -> u32; + /// Packet optional gas limit. fn gas_limit(&self) -> Option; diff --git a/core/src/message/reply.rs b/core/src/message/reply.rs index d58e5a0246b..e7c61c1fa6b 100644 --- a/core/src/message/reply.rs +++ b/core/src/message/reply.rs @@ -237,6 +237,10 @@ impl Packet for ReplyPacket { self.payload.inner() } + fn payload_len(&self) -> u32 { + self.payload.len_u32() + } + fn gas_limit(&self) -> Option { self.gas_limit } diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 2e9957b6b3b..bd65cc2738f 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -53,6 +53,7 @@ use std::{ }; const OUTGOING_LIMIT: u32 = 1024; +const OUTGOING_BYTES_LIMIT: u32 = 64 * 1024 * 1024; pub(crate) type Balance = u128; @@ -871,6 +872,7 @@ impl ExtManager { page_costs: PageCosts::new_for_tests(), existential_deposit: EXISTENTIAL_DEPOSIT, outgoing_limit: OUTGOING_LIMIT, + outgoing_bytes_limit: OUTGOING_BYTES_LIMIT, host_fn_weights: Default::default(), forbidden_funcs: Default::default(), mailbox_threshold: MAILBOX_THRESHOLD, diff --git a/node/service/src/lib.rs b/node/service/src/lib.rs index e9d8defb4b9..e50e42373be 100644 --- a/node/service/src/lib.rs +++ b/node/service/src/lib.rs @@ -192,6 +192,8 @@ where }) .transpose()?; + // TODO: consider to set ours `default_heap_pages` here, + // instead of using substrate's default #3741. let heap_pages = config .default_heap_pages .map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| HeapAllocStrategy::Static { diff --git a/pallets/gear-builtin/src/lib.rs b/pallets/gear-builtin/src/lib.rs index 1bcf0bf28bf..f47e4768b8d 100644 --- a/pallets/gear-builtin/src/lib.rs +++ b/pallets/gear-builtin/src/lib.rs @@ -279,7 +279,14 @@ impl BuiltinDispatcher for BuiltinRegistry { // Create an artificial `MessageContext` object that will help us to generate // a reply from the builtin actor. let mut message_context = - MessageContext::new(dispatch, actor_id, Default::default()); + MessageContext::new(dispatch, actor_id, Default::default()).unwrap_or_else( + || { + unreachable!( + "Builtin actor can't have context stored, + so must be always possible to create a new message context" + ) + }, + ); let packet = ReplyPacket::new(response_payload, 0); // Mark reply as sent diff --git a/pallets/gear-builtin/src/mock.rs b/pallets/gear-builtin/src/mock.rs index 0ebc8fa31e4..c8d14fa6822 100644 --- a/pallets/gear-builtin/src/mock.rs +++ b/pallets/gear-builtin/src/mock.rs @@ -98,6 +98,7 @@ common::impl_pallet_timestamp!(Test); parameter_types! { pub const BlockGasLimit: u64 = 100_000_000_000; pub const OutgoingLimit: u32 = 1024; + pub const OutgoingBytesLimit: u32 = 64 * 1024 * 1024; pub ReserveThreshold: BlockNumber = 1; pub GearSchedule: pallet_gear::Schedule = >::default(); pub RentFreePeriod: BlockNumber = 12_000; diff --git a/pallets/gear-builtin/src/tests/bad_builtin_ids.rs b/pallets/gear-builtin/src/tests/bad_builtin_ids.rs index 6a902eb9868..0973f6a9445 100644 --- a/pallets/gear-builtin/src/tests/bad_builtin_ids.rs +++ b/pallets/gear-builtin/src/tests/bad_builtin_ids.rs @@ -70,6 +70,7 @@ common::impl_pallet_timestamp!(Test); parameter_types! { pub const BlockGasLimit: u64 = 100_000_000_000; pub const OutgoingLimit: u32 = 1024; + pub const OutgoingBytesLimit: u32 = 64 * 1024 * 1024; pub ReserveThreshold: BlockNumber = 1; pub GearSchedule: pallet_gear::Schedule = >::default(); pub RentFreePeriod: BlockNumber = 12_000; diff --git a/pallets/gear-debug/src/mock.rs b/pallets/gear-debug/src/mock.rs index 02c8d1a8789..499b67a965f 100644 --- a/pallets/gear-debug/src/mock.rs +++ b/pallets/gear-debug/src/mock.rs @@ -58,6 +58,7 @@ impl pallet_gear_debug::Config for Test { parameter_types! { pub const OutgoingLimit: u32 = 1024; + pub const OutgoingBytesLimit: u32 = 64 * 1024 * 1024; pub const BlockGasLimit: u64 = 100_000_000_000; pub const PerformanceMultiplier: u32 = 100; } diff --git a/pallets/gear-scheduler/src/mock.rs b/pallets/gear-scheduler/src/mock.rs index f73dd0bb61f..02a209ffeb7 100644 --- a/pallets/gear-scheduler/src/mock.rs +++ b/pallets/gear-scheduler/src/mock.rs @@ -85,6 +85,7 @@ parameter_types! { parameter_types! { pub const BlockGasLimit: u64 = 100_000_000_000; pub const OutgoingLimit: u32 = 1024; + pub const OutgoingBytesLimit: u32 = 64 * 1024 * 1024; pub const PerformanceMultiplier: u32 = 100; pub GearSchedule: pallet_gear::Schedule = >::default(); pub RentFreePeriod: BlockNumber = 1_000; diff --git a/pallets/gear/src/benchmarking/mod.rs b/pallets/gear/src/benchmarking/mod.rs index 2fdc5d95f83..9893b721737 100644 --- a/pallets/gear/src/benchmarking/mod.rs +++ b/pallets/gear/src/benchmarking/mod.rs @@ -85,7 +85,7 @@ use gear_core::{ gas::{GasAllowanceCounter, GasCounter, ValueCounter}, ids::{CodeId, MessageId, ProgramId}, memory::{AllocationsContext, Memory, PageBuf}, - message::{ContextSettings, DispatchKind, IncomingDispatch, MessageContext}, + message::{DispatchKind, IncomingDispatch, MessageContext}, pages::{GearPage, PageU32Size, WasmPage}, reservation::GasReserver, }; @@ -179,8 +179,9 @@ fn default_processor_context() -> ProcessorContext { message_context: MessageContext::new( Default::default(), Default::default(), - ContextSettings::new(0, 0, 0, 0, 0, 0), - ), + Default::default(), + ) + .unwrap(), block_info: Default::default(), performance_multiplier: gsys::Percent::new(100), max_pages: TESTS_MAX_PAGES_NUMBER.into(), diff --git a/pallets/gear/src/benchmarking/utils.rs b/pallets/gear/src/benchmarking/utils.rs index eed5af90c54..9e8d44b1685 100644 --- a/pallets/gear/src/benchmarking/utils.rs +++ b/pallets/gear/src/benchmarking/utils.rs @@ -68,6 +68,7 @@ where page_costs: T::Schedule::get().memory_weights.into(), existential_deposit, outgoing_limit: 2048, + outgoing_bytes_limit: u32::MAX, host_fn_weights: Default::default(), forbidden_funcs: Default::default(), mailbox_threshold, diff --git a/pallets/gear/src/lib.rs b/pallets/gear/src/lib.rs index aedb713415c..0f79e303dfc 100644 --- a/pallets/gear/src/lib.rs +++ b/pallets/gear/src/lib.rs @@ -167,10 +167,14 @@ pub mod pallet { #[pallet::constant] type Schedule: Get>; - /// The maximum amount of messages that can be produced in single run. + /// The maximum amount of messages that can be produced in during all message executions. #[pallet::constant] type OutgoingLimit: Get; + /// The maximum amount of bytes in outgoing messages during message execution. + #[pallet::constant] + type OutgoingBytesLimit: Get; + /// Performance multiplier. #[pallet::constant] type PerformanceMultiplier: Get; @@ -1028,6 +1032,7 @@ pub mod pallet { page_costs: schedule.memory_weights.clone().into(), existential_deposit, outgoing_limit: T::OutgoingLimit::get(), + outgoing_bytes_limit: T::OutgoingBytesLimit::get(), host_fn_weights: schedule.host_fn_weights.into_core(), forbidden_funcs: Default::default(), mailbox_threshold: T::MailboxThreshold::get(), diff --git a/pallets/gear/src/mock.rs b/pallets/gear/src/mock.rs index d1def051846..cb18066b7a2 100644 --- a/pallets/gear/src/mock.rs +++ b/pallets/gear/src/mock.rs @@ -105,6 +105,7 @@ parameter_types! { // Match the default `max_block` set in frame_system::limits::BlockWeights::with_sensible_defaults() pub const BlockGasLimit: u64 = MAX_BLOCK; pub const OutgoingLimit: u32 = 1024; + pub const OutgoingBytesLimit: u32 = 64 * 1024 * 1024; pub ReserveThreshold: BlockNumber = 1; pub RentFreePeriod: BlockNumber = 1_000; pub RentCostPerBlock: Balance = 11; diff --git a/pallets/gear/src/pallet_tests.rs b/pallets/gear/src/pallet_tests.rs index 7664622d791..f4161a3eb7b 100644 --- a/pallets/gear/src/pallet_tests.rs +++ b/pallets/gear/src/pallet_tests.rs @@ -51,6 +51,7 @@ macro_rules! impl_config_inner { type WeightInfo = pallet_gear::weights::SubstrateWeight; type Schedule = GearConfigSchedule; type OutgoingLimit = OutgoingLimit; + type OutgoingBytesLimit = OutgoingBytesLimit; type PerformanceMultiplier = PerformanceMultiplier; type DebugInfo = GearConfigDebugInfo; type CodeStorage = GearProgram; diff --git a/pallets/gear/src/queue.rs b/pallets/gear/src/queue.rs index ed73b56b38f..81c2c8429f0 100644 --- a/pallets/gear/src/queue.rs +++ b/pallets/gear/src/queue.rs @@ -182,7 +182,7 @@ where (context, code, balance).into(), (random.encode(), bn.unique_saturated_into()), ) - .unwrap_or_else(|e| unreachable!("core-processor logic invalidated: {e}")) + .unwrap_or_else(|e| unreachable!("{e}")) } /// Message Queue processing. diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 05b1b0f5eb5..4c408b9eab2 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -47,7 +47,10 @@ use frame_system::pallet_prelude::BlockNumberFor; use gear_core::{ code::{self, Code, CodeAndId, CodeError, ExportError, InstrumentedCodeAndId}, ids::{CodeId, MessageId, ProgramId}, - message::UserStoredMessage, + message::{ + ContextSettings, DispatchKind, IncomingDispatch, IncomingMessage, MessageContext, Payload, + StoredDispatch, UserStoredMessage, + }, pages::{PageNumber, PageU32Size, WasmPage}, }; use gear_core_backend::error::{ @@ -14721,6 +14724,132 @@ fn program_with_large_indexes() { }); } +#[test] +fn outgoing_messages_bytes_limit_exceeded() { + let error_code = MessageError::OutgoingMessagesBytesLimitExceeded as u32; + + let wat = format!( + r#" + (module + (import "env" "memory" (memory 0x100)) + (import "env" "gr_send" (func $gr_send (param i32 i32 i32 i32 i32))) + (export "init" (func $init)) + (func $init + (loop $loop + i32.const 0 ;; destination and value ptr + i32.const 0 ;; payload ptr + i32.const 0x4c0000 ;; payload length + i32.const 0 ;; delay + i32.const 0x4d0000 ;; result ptr + call $gr_send + + ;; if it's not an error, then continue the loop + (if (i32.eqz (i32.load (i32.const 0x4d0000))) (then (br $loop))) + + ;; if it's sought-for error, then finish successfully + ;; if it's unknown error, then panic + (if (i32.eq (i32.const {error_code}) (i32.load (i32.const 0x4d0000))) + (then) + (else unreachable) + ) + ) + ) + (export "__gear_stack_end" (global 0)) + (global i32 (i32.const 0x1000000)) ;; all memory + )"# + ); + + init_logger(); + new_test_ext().execute_with(|| { + let code = ProgramCodeKind::Custom(wat.as_str()).to_bytes(); + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + code, + DEFAULT_SALT.to_vec(), + vec![], + 100_000_000_000, + 0, + false, + )); + + let mid = get_last_message_id(); + + run_to_next_block(None); + + assert_succeed(mid); + }); +} + +// TODO: this test must be moved to `core-processor` crate, +// but it's not possible currently, because mock for `core-processor` does not exist #3742 +#[test] +fn incorrect_store_context() { + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + ProgramCodeKind::Default.to_bytes(), + DEFAULT_SALT.to_vec(), + vec![], + 100_000_000_000, + 0, + false, + )); + + let pid = get_last_program_id(); + let mid = get_last_message_id(); + + run_to_next_block(None); + + assert_succeed(mid); + + let gas_limit = 10_000_000_000; + Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + vec![], + gas_limit, + 0, + true, + ) + .unwrap(); + let mid = get_last_message_id(); + + // Dequeue dispatch in order to queue corrupted dispatch with same id later + QueueOf::::dequeue().unwrap().unwrap(); + + // Start creating dispatch with outgoing messages total bytes limit exceeded + let payload = Vec::new().try_into().unwrap(); + let message = IncomingMessage::new(mid, USER_1.cast(), payload, gas_limit, 0, None); + + // Get overloaded `StoreContext` using `MessageContext` + let limit = ::OutgoingBytesLimit::get(); + let dispatch = IncomingDispatch::new(DispatchKind::Handle, message.clone(), None); + let settings = ContextSettings::with_outgoing_limits(1024, limit + 1); + let mut message_context = MessageContext::new(dispatch, pid, settings).unwrap(); + let mut counter = 0; + // Fill until the limit is reached + while counter < limit + 1 { + let handle = message_context.send_init().unwrap(); + let len = (Payload::max_len() as u32).min(limit + 1 - counter); + message_context + .send_push(handle, &vec![1; len as usize]) + .unwrap(); + counter += len; + } + let (_, context_store) = message_context.drain(); + + // Enqueue dispatch with corrupted context + let message = message.into_stored(pid); + let dispatch = StoredDispatch::new(DispatchKind::Handle, message, Some(context_store)); + QueueOf::::queue(dispatch).unwrap(); + + run_to_next_block(None); + + assert_failed(mid, ActorExecutionErrorReplyReason::UnsupportedMessage); + }); +} + mod utils { #![allow(unused)] diff --git a/pallets/payment/src/mock.rs b/pallets/payment/src/mock.rs index 1c3f4962d1c..b49d8c47d14 100644 --- a/pallets/payment/src/mock.rs +++ b/pallets/payment/src/mock.rs @@ -109,6 +109,7 @@ impl pallet_transaction_payment::Config for Test { parameter_types! { pub const BlockGasLimit: u64 = 500_000; pub const OutgoingLimit: u32 = 1024; + pub const OutgoingBytesLimit: u32 = 64 * 1024 * 1024; pub const PerformanceMultiplier: u32 = 100; pub GearSchedule: pallet_gear::Schedule = >::default(); pub RentFreePeriod: BlockNumber = 1_000; diff --git a/runtime/vara/src/lib.rs b/runtime/vara/src/lib.rs index 00f17059631..7408d7f54db 100644 --- a/runtime/vara/src/lib.rs +++ b/runtime/vara/src/lib.rs @@ -973,6 +973,9 @@ parameter_types! { pub const DispatchHoldCost: u64 = 100; pub const OutgoingLimit: u32 = 1024; + // 64 MB, must be less than max runtime heap memory. + // NOTE: currently runtime heap memory is 1 GB (see https://shorturl.at/DET45) + pub const OutgoingBytesLimit: u32 = 64 * 1024 * 1024; pub const MailboxThreshold: u64 = 3000; pub const PerformanceMultiplier: u32 = 100; @@ -996,6 +999,7 @@ impl pallet_gear::Config for Runtime { type WeightInfo = weights::pallet_gear::SubstrateWeight; type Schedule = Schedule; type OutgoingLimit = OutgoingLimit; + type OutgoingBytesLimit = OutgoingBytesLimit; type PerformanceMultiplier = PerformanceMultiplier; type DebugInfo = DebugInfo; type CodeStorage = GearProgram; diff --git a/utils/wasm-gen/src/tests.rs b/utils/wasm-gen/src/tests.rs index 42e04d4e397..495adc551fb 100644 --- a/utils/wasm-gen/src/tests.rs +++ b/utils/wasm-gen/src/tests.rs @@ -594,8 +594,9 @@ fn execute_wasm_with_custom_configs( let mut message_context = MessageContext::new( IncomingDispatch::new(DispatchKind::Init, incoming_message, None), program_id, - ContextSettings::new(0, 0, 0, 0, 0, outgoing_limit), - ); + ContextSettings::with_outgoing_limits(outgoing_limit, u32::MAX), + ) + .unwrap(); if imitate_reply { let _ = message_context.reply_commit(ReplyPacket::auto(), None);