From a60dc13f17342871cfe8c64b1378c5160b85f6ef Mon Sep 17 00:00:00 2001 From: Sabaun Taraki Date: Thu, 1 Aug 2024 23:17:04 +0300 Subject: [PATCH 01/29] State todos --- gtest/src/program.rs | 3 +++ gtest/src/system.rs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/gtest/src/program.rs b/gtest/src/program.rs index 9fb62e1cc30..d4d9a133c0b 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -551,6 +551,8 @@ impl<'a> Program<'a> { self.send_bytes_with_gas_and_value(from, payload, gas_limit, value) } + // todo [sab] this will return message id and adds message to the end of the queue + // need to be sure that you can obtain info about message being executed fn send_bytes_with_gas_and_value( &self, from: ID, @@ -595,6 +597,7 @@ impl<'a> Program<'a> { } /// Send signal to the program. + // todo [sab] remove that? #[track_caller] pub fn send_signal>(&self, from: ID, code: SignalCode) -> RunResult { let mut system = self.manager.borrow_mut(); diff --git a/gtest/src/system.rs b/gtest/src/system.rs index 9b81f2c2a5d..c07bc8f77d6 100644 --- a/gtest/src/system.rs +++ b/gtest/src/system.rs @@ -175,10 +175,12 @@ impl System { } /// Send raw message dispatch. + // todo [sab] remove that - it's a hack over the gtest and gear protocol. pub fn send_dispatch(&self, dispatch: Dispatch) -> RunResult { self.0.borrow_mut().validate_and_run_dispatch(dispatch) } + // todo [sab] this must be spending blocks only on task pool /// Spend blocks and return all results. pub fn spend_blocks(&self, amount: u32) -> Vec { let mut manager = self.0.borrow_mut(); From 5b040b18d877ac2eb4a7677a5103e34a78e49836 Mon Sep 17 00:00:00 2001 From: Sabaun Taraki Date: Wed, 7 Aug 2024 14:08:53 +0300 Subject: [PATCH 02/29] Change block execution model --- gtest/src/lib.rs | 29 +++- gtest/src/log.rs | 97 +++++------ gtest/src/mailbox/actor.rs | 78 +++------ gtest/src/manager.rs | 321 +++++++++++++++++++------------------ gtest/src/program.rs | 165 +++++++++---------- gtest/src/system.rs | 140 ++++++++++++++-- 6 files changed, 452 insertions(+), 378 deletions(-) diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index f0a1f3799f5..8777898fcb2 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -332,18 +332,31 @@ //! assert_eq!(y, y_from); //! ``` //! -//! ## Spending blocks +//! ## Blocks execution model //! -//! You may control time in the system by spending blocks. +//! Block execution has 2 main step: +//! - tasks processing +//! - messages processing //! -//! It adds the amount of blocks passed as arguments to the current block of the -//! system. Same for the timestamp. Note, that for now 1 block in Gear-based -//! network is 3 sec duration. +//! Tasks processing is a step, when all scheduled for the current block number +//! tasks are tried to be processed. This includes processing delayed +//! dispatches, waking waited messages and etc. +//! +//! Messages processing is a step, when messages from the queue are processed +//! until either the queue is empty or the block gas allowance is not enough for +//! the execution. +//! +//! Blocks can't be "spent" without their execution except for use the +//! [`System::run_scheduled_tasks`] method, which doesn't process the message +//! queue, but only processes scheduled tasks triggering blocks info +//! adjustments, which can be used to "spend" blocks. +//! +//! Note, that for now 1 block in Gear-based network is 3 sec duration. //! //! ```no_run //! # let sys = gtest::System::new(); -//! // Spend 150 blocks (7.5 mins for 3 sec block). -//! sys.spend_blocks(150); +//! // Spend 150 blocks by running only the task pool (7.5 mins for 3 sec block). +//! sys.run_scheduled_tasks(150); //! ``` //! //! Note that processing messages (e.g. by using @@ -429,7 +442,7 @@ mod manager; mod program; mod system; -pub use crate::log::{CoreLog, Log, RunResult}; +pub use crate::log::{BlockRunResult, CoreLog, Log}; pub use codec; pub use error::{Result, TestError}; pub use mailbox::ActorMailbox; diff --git a/gtest/src/log.rs b/gtest/src/log.rs index 2e45c0981f7..a2ce78d8f1e 100644 --- a/gtest/src/log.rs +++ b/gtest/src/log.rs @@ -18,12 +18,17 @@ use crate::program::{Gas, ProgramIdWrapper}; use codec::{Codec, Encode}; +use core_processor::configs::BlockInfo; use gear_core::{ ids::{MessageId, ProgramId}, message::{Payload, StoredMessage, UserStoredMessage}, }; use gear_core_errors::{ErrorReplyReason, ReplyCode, SimpleExecutionError, SuccessReplyReason}; -use std::{collections::BTreeMap, convert::TryInto, fmt::Debug}; +use std::{ + collections::{BTreeMap, BTreeSet}, + convert::TryInto, + fmt::Debug, +}; /// A log that emitted by a program, for user defined logs, /// see [`Log`]. @@ -372,20 +377,35 @@ impl PartialEq for CoreLog { } } -/// The result of a message run. -#[derive(Debug, Clone)] -pub struct RunResult { - pub(crate) log: Vec, - pub(crate) main_failed: bool, - pub(crate) others_failed: bool, - pub(crate) message_id: MessageId, - pub(crate) total_processed: u32, - pub(crate) main_gas_burned: Gas, - pub(crate) others_gas_burned: BTreeMap, +/// Result of running the block. +#[derive(Debug, Default)] +pub struct BlockRunResult { + /// Executed block info. + pub block_info: BlockInfo, + /// Gas allowance spent during the execution. + pub gas_allowance_spent: Gas, + /// Set of successfully executed messages + /// during the current block execution. + pub succeed: BTreeSet, + /// Set of failed messages during the current + /// block execution. + pub failed: BTreeSet, + /// Set of not executed messages + /// during the current block execution. + pub not_executed: BTreeSet, + /// Total messages processed during the current + /// execution. + pub total_processed: u32, + // todo [sab] change concept of the log + /// Logs created during the current execution. + pub log: Vec, + /// Mapping gas burned for each message during + /// the current block execution. + pub gas_burned: BTreeMap, } -impl RunResult { - /// If the result contains a specific log. +impl BlockRunResult { + /// Check, if the result contains a specific log. pub fn contains + Clone>(&self, log: &T) -> bool { let log = log.clone().into(); @@ -397,36 +417,6 @@ impl RunResult { &self.log } - /// If main message failed. - pub fn main_failed(&self) -> bool { - self.main_failed - } - - /// If any other messages failed. - pub fn others_failed(&self) -> bool { - self.others_failed - } - - /// Get the message id. - pub fn sent_message_id(&self) -> MessageId { - self.message_id - } - - /// Get the total number of processed messages. - pub fn total_processed(&self) -> u32 { - self.total_processed - } - - /// Get the total gas burned by the main message. - pub fn main_gas_burned(&self) -> Gas { - self.main_gas_burned - } - - /// Get the total gas burned by the other messages. - pub fn others_gas_burned(&self) -> &BTreeMap { - &self.others_gas_burned - } - /// Returns decoded logs. pub fn decoded_log(&self) -> Vec> { self.log @@ -436,16 +426,11 @@ impl RunResult { .collect() } - /// If the main message panicked. - pub fn main_panicked(&self) -> bool { - self.main_panic_log().is_some() - } - - /// Asserts that the main message panicked and that the panic contained a + /// Asserts that the message panicked and that the panic contained a /// given message. #[track_caller] - pub fn assert_panicked_with(&self, msg: impl Into) { - let panic_log = self.main_panic_log(); + pub fn assert_panicked_with(&self, message_id: MessageId, msg: impl Into) { + let panic_log = self.message_panic_log(message_id); assert!(panic_log.is_some(), "Program did not panic"); let msg = msg.into(); let payload = String::from_utf8( @@ -463,18 +448,18 @@ impl RunResult { } /// Trying to get the panic log. - fn main_panic_log(&self) -> Option<&CoreLog> { - let main_log = self + fn message_panic_log(&self, message_id: MessageId) -> Option<&CoreLog> { + let msg_log = self .log .iter() - .find(|log| log.reply_to == Some(self.message_id))?; + .find(|log| log.reply_to == Some(message_id))?; let is_panic = matches!( - main_log.reply_code(), + msg_log.reply_code(), Some(ReplyCode::Error(ErrorReplyReason::Execution( SimpleExecutionError::UserspacePanic ))) ); - is_panic.then_some(main_log) + is_panic.then_some(msg_log) } } diff --git a/gtest/src/mailbox/actor.rs b/gtest/src/mailbox/actor.rs index 940b8c42e66..7daa1f8eb4a 100644 --- a/gtest/src/mailbox/actor.rs +++ b/gtest/src/mailbox/actor.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{manager::ExtManager, Log, RunResult, GAS_ALLOWANCE}; +use crate::{manager::ExtManager, Log, GAS_ALLOWANCE}; use codec::Encode; use gear_common::{auxiliary::mailbox::*, storage::Interval}; use gear_core::{ @@ -54,7 +54,7 @@ impl<'a> ActorMailbox<'a> { log: Log, payload: impl Encode, value: u128, - ) -> Result { + ) -> Result { self.reply_bytes(log, payload.encode(), value) } @@ -65,7 +65,7 @@ impl<'a> ActorMailbox<'a> { log: Log, raw_payload: impl AsRef<[u8]>, value: u128, - ) -> Result { + ) -> Result { let mailboxed_msg = self .find_message_by_log(&log) .ok_or(MailboxErrorImpl::ElementNotFound)?; @@ -93,7 +93,7 @@ impl<'a> ActorMailbox<'a> { Ok(self .manager .borrow_mut() - .validate_and_run_dispatch(dispatch)) + .validate_and_route_dispatch(dispatch)) } /// Claims value from a message in mailbox. @@ -124,17 +124,10 @@ impl<'a> ActorMailbox<'a> { #[cfg(test)] mod tests { - use crate::{ - program::ProgramIdWrapper, Log, Program, System, EXISTENTIAL_DEPOSIT, GAS_ALLOWANCE, - }; + use crate::{Log, Program, System, EXISTENTIAL_DEPOSIT}; use codec::Encode; use demo_constructor::{Call, Calls, Scheme, WASM_BINARY}; - use gear_common::auxiliary::mailbox::MailboxErrorImpl; - use gear_core::{ - ids::{prelude::MessageIdExt, MessageId, ProgramId}, - message::{Dispatch, DispatchKind, HandleMessage, HandlePacket, Payload}, - }; - use std::convert::TryInto; + use gear_core::ids::ProgramId; fn prepare_program(system: &System) -> (Program<'_>, ([u8; 32], Vec, Log)) { let program = Program::from_binary_with_id(system, 121, WASM_BINARY); @@ -143,44 +136,13 @@ mod tests { let payload = b"sup!".to_vec(); let log = Log::builder().dest(sender).payload_bytes(payload.clone()); - let res = program.send(sender, Scheme::empty()); - assert!(!res.main_failed()); + let msg_id = program.send(sender, Scheme::empty()); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); (program, (sender, payload, log)) } - #[test] - fn user2user_doesnt_reach_mailbox() { - let system = System::new(); - - let source = ProgramIdWrapper::from(100).0; - let message_id: MessageId = MessageId::generate_from_user(0, source, 0); - let destination = ProgramIdWrapper::from(200).0; - let payload: Payload = vec![1, 2, 3].try_into().expect("len exceed"); - let log = Log::builder() - .dest(destination) - .payload_bytes(payload.inner()); - - let dispatch = Dispatch::new( - DispatchKind::Handle, - HandleMessage::from_packet( - message_id, - HandlePacket::new_with_gas(destination, payload, GAS_ALLOWANCE, 0), - ) - .into_message(source), - ); - - // Log exists - let res = system.send_dispatch(dispatch); - assert!(res.contains(&log)); - - // But message doesn't exist in mailbox - let mailbox = system.get_mailbox(destination); - let res = mailbox.reply(log, b"", 0); - assert!(res.is_err()); - assert_eq!(res.unwrap_err(), MailboxErrorImpl::ElementNotFound); - } - #[test] fn claim_value_from_mailbox() { let system = System::new(); @@ -191,8 +153,9 @@ mod tests { let value_send = 2 * EXISTENTIAL_DEPOSIT; let handle = Calls::builder().send_value(sender, payload, value_send); - let res = program.send_bytes_with_value(sender, handle.encode(), value_send); - assert!(!res.main_failed()); + let msg_id = program.send_bytes_with_value(sender, handle.encode(), value_send); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); assert!(res.contains(&log)); assert_eq!(system.balance_of(sender), original_balance - value_send); @@ -208,16 +171,18 @@ mod tests { let (program, (sender, payload, log)) = prepare_program(&system); let handle = Calls::builder().send(sender, payload); - let res = program.send(sender, handle); - assert!(!res.main_failed()); + let msg_id = program.send(sender, handle); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); assert!(res.contains(&log)); let mailbox = system.get_mailbox(sender); assert!(mailbox.contains(&log)); - let res = mailbox + let msg_id = mailbox .reply(log, Calls::default(), 0) .expect("sending reply failed: didn't find message in mailbox"); - assert!(!res.main_failed()); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); } #[test] @@ -233,10 +198,11 @@ mod tests { 0.into(), delay.into(), )); - let res = program.send(sender, handle); - assert!(!res.main_failed()); + let msg_id = program.send(sender, handle); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); - let results = system.spend_blocks(delay); + let results = system.run_scheduled_tasks(delay); let delayed_dispatch_res = results.last().expect("internal error: no blocks spent"); assert!(delayed_dispatch_res.contains(&log)); diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 608c30be759..d68a7cb3585 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -19,7 +19,7 @@ use crate::{ blocks::BlocksManager, gas_tree::GasTreeManager, - log::{CoreLog, RunResult}, + log::{BlockRunResult, CoreLog}, mailbox::MailboxManager, program::{Gas, WasmProgram}, Result, TestError, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, EXISTENTIAL_DEPOSIT, @@ -60,8 +60,9 @@ use gear_wasm_instrument::gas_metering::Schedule; use rand::{rngs::StdRng, RngCore, SeedableRng}; use std::{ cell::{Ref, RefCell, RefMut}, - collections::{BTreeMap, HashMap, VecDeque}, + collections::{BTreeMap, BTreeSet, HashMap, VecDeque}, convert::TryInto, + mem, rc::Rc, }; @@ -243,15 +244,14 @@ pub(crate) struct ExtManager { pub(crate) gas_tree: GasTreeManager, pub(crate) gas_allowance: Gas, pub(crate) delayed_dispatches: HashMap>, + pub(crate) messages_processing_enabled: bool, - // Last run info - pub(crate) origin: ProgramId, - pub(crate) msg_id: MessageId, + // Last block execution info + pub(crate) succeed: BTreeSet, + pub(crate) failed: BTreeSet, + pub(crate) not_executed: BTreeSet, + pub(crate) gas_burned: BTreeMap, pub(crate) log: Vec, - pub(crate) main_failed: bool, - pub(crate) others_failed: bool, - pub(crate) main_gas_burned: Gas, - pub(crate) others_gas_burned: BTreeMap, } impl ExtManager { @@ -261,6 +261,7 @@ impl ExtManager { msg_nonce: 1, id_nonce: 1, blocks_manager: BlocksManager::new(), + messages_processing_enabled: true, random_data: ( { let mut rng = StdRng::seed_from_u64(INITIAL_RANDOM_SEED); @@ -312,7 +313,7 @@ impl ExtManager { } /// Insert message into the delayed queue. - pub(crate) fn send_delayed_dispatch(&mut self, dispatch: Dispatch, bn: u32) { + fn send_delayed_dispatch(&mut self, dispatch: Dispatch, bn: u32) { self.delayed_dispatches .entry(bn) .or_default() @@ -320,46 +321,43 @@ impl ExtManager { } /// Process all delayed dispatches. - pub(crate) fn process_delayed_dispatches(&mut self, bn: u32) -> Vec { - self.delayed_dispatches - .remove(&bn) - .map(|dispatches| { - dispatches - .into_iter() - .map(|dispatch| self.run_dispatch(dispatch, true)) - .collect() - }) - .unwrap_or_default() + pub(crate) fn process_delayed_dispatches(&mut self, bn: u32) { + let Some(dispatches) = self.delayed_dispatches.remove(&bn) else { + return; + }; + + for dispatch in dispatches { + self.route_dispatch_from_task_pool(dispatch); + } } /// Process scheduled wait list. - pub(crate) fn process_scheduled_wait_list(&mut self, bn: u32) -> Vec { - self.wait_list_schedules - .remove(&bn) - .map(|ids| { - ids.into_iter() - .filter_map(|key| { - self.wait_list.remove(&key).map(|dispatch| { - let (kind, message, ..) = dispatch.into_parts(); - let message = Message::new( - message.id(), - message.source(), - message.destination(), - message - .payload_bytes() - .to_vec() - .try_into() - .unwrap_or_default(), - self.gas_tree.get_limit(message.id()).ok(), - message.value(), - message.details(), - ); - self.run_dispatch(Dispatch::new(kind, message), true) - }) - }) - .collect() - }) - .unwrap_or_default() + pub(crate) fn process_scheduled_wait_list(&mut self, bn: u32) { + let Some(wl_schedules) = self.wait_list_schedules.remove(&bn) else { + return; + }; + + for wl_schedule in wl_schedules { + let Some(dispatch) = self.wait_list.remove(&wl_schedule) else { + continue; + }; + + let (kind, message, ..) = dispatch.into_parts(); + let message = Message::new( + message.id(), + message.source(), + message.destination(), + message + .payload_bytes() + .to_vec() + .try_into() + .unwrap_or_default(), + self.gas_tree.get_limit(message.id()).ok(), + message.value(), + message.details(), + ); + self.route_dispatch_from_task_pool(Dispatch::new(kind, message)); + } } /// Check if the current block number should trigger new epoch and reset @@ -398,9 +396,99 @@ impl ExtManager { } } - pub(crate) fn validate_and_run_dispatch(&mut self, dispatch: Dispatch) -> RunResult { + pub(crate) fn validate_and_route_dispatch(&mut self, dispatch: Dispatch) -> MessageId { self.validate_dispatch(&dispatch); - self.run_dispatch(dispatch, false) + let gas_limit = dispatch + .gas_limit() + .unwrap_or_else(|| unreachable!("message from program API always has gas")); + self.gas_tree + .create(dispatch.source(), dispatch.id(), gas_limit) + .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); + self.route_dispatch(dispatch) + } + + pub(crate) fn route_dispatch(&mut self, dispatch: Dispatch) -> MessageId { + let stored_dispatch = dispatch.into_stored(); + if self.is_user(&stored_dispatch.destination()) { + panic!("Program API only sends message to programs.") + } + + let message_id = stored_dispatch.id(); + self.dispatches.push_back(stored_dispatch); + + message_id + } + + // todo [sab] include task pool gas charges and stop processing if gas allowance + // is not enough (test that) todo [sab] bring queue processing from common + // todo [sab] check programs states and their executions in accordance to + // `process_dispatch` and etc. basically refactor the + // process_dormant/process_normal and etc. for example test + // `test_handle_messages_to_failing_program` must deny sending to dormant actor + #[track_caller] + pub(crate) fn run_new_block(&mut self, allowance: Gas) -> BlockRunResult { + self.gas_allowance = allowance; + self.blocks_manager.next_block(); + let new_block_bn = self.blocks_manager.get().height; + + self.process_tasks(new_block_bn); + let total_processed = self.process_messages(); + + BlockRunResult { + block_info: self.blocks_manager.get(), + gas_allowance_spent: Gas(GAS_ALLOWANCE) - self.gas_allowance, + succeed: mem::take(&mut self.succeed), + failed: mem::take(&mut self.failed), + not_executed: mem::take(&mut self.not_executed), + total_processed, + log: mem::take(&mut self.log) + .into_iter() + .map(CoreLog::from) + .collect(), + gas_burned: mem::take(&mut self.gas_burned), + } + } + + #[track_caller] + pub(crate) fn process_tasks(&mut self, bn: u32) { + self.process_delayed_dispatches(bn); + self.process_scheduled_wait_list(bn); + } + + #[track_caller] + fn process_messages(&mut self) -> u32 { + self.messages_processing_enabled = true; + + let mut total_processed = 0; + while self.messages_processing_enabled { + let dispatch = match self.dispatches.pop_front() { + Some(dispatch) => dispatch, + None => break, + }; + + let mut actors = self.actors.borrow_mut(); + let (actor, balance) = actors + .get_mut(&dispatch.destination()) + .expect("Somehow message queue contains message for user"); + let balance = *balance; + + if actor.is_dormant() { + drop(actors); + self.process_dormant(balance, dispatch); + } else if let Some((data, code)) = actor.get_executable_actor_data() { + drop(actors); + self.process_normal(balance, data, code, dispatch); + } else if let Some(mock) = actor.take_mock() { + drop(actors); + self.process_mock(mock, dispatch); + } else { + unreachable!(); + } + + total_processed += 1; + } + + total_processed } #[track_caller] @@ -431,26 +519,8 @@ impl ExtManager { } #[track_caller] - pub(crate) fn run_dispatch(&mut self, dispatch: Dispatch, from_task_pool: bool) -> RunResult { - self.prepare_for(&dispatch, !from_task_pool); - + pub(crate) fn route_dispatch_from_task_pool(&mut self, dispatch: Dispatch) { if self.is_program(&dispatch.destination()) { - if !from_task_pool { - let gas_limit = matches!(dispatch.kind(), DispatchKind::Signal) - .then(|| { - assert!( - dispatch.gas_limit().is_none(), - "signals must be sent with `None` gas limit" - ); - GAS_ALLOWANCE - }) - .or_else(|| dispatch.gas_limit()) - .unwrap_or_else(|| unreachable!("message from program API has always gas")); - self.gas_tree - .create(dispatch.source(), dispatch.id(), gas_limit) - .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); - } - self.dispatches.push_back(dispatch.into_stored()); } else { let message = dispatch.into_parts().1.into_stored(); @@ -465,44 +535,6 @@ impl ExtManager { self.log.push(message) } - - let mut total_processed = 0; - while let Some(dispatch) = self.dispatches.pop_front() { - let dest = dispatch.destination(); - - let mut actors = self.actors.borrow_mut(); - let (actor, balance) = actors - .get_mut(&dest) - .expect("Somehow message queue contains message for user"); - let balance = *balance; - - if actor.is_dormant() { - drop(actors); - self.process_dormant(balance, dispatch); - } else if let Some((data, code)) = actor.get_executable_actor_data() { - drop(actors); - self.process_normal(balance, data, code, dispatch); - } else if let Some(mock) = actor.take_mock() { - drop(actors); - self.process_mock(mock, dispatch); - } else { - unreachable!(); - } - - total_processed += 1; - } - - let log = self.log.clone(); - - RunResult { - main_failed: self.main_failed, - others_failed: self.others_failed, - log: log.into_iter().map(CoreLog::from).collect(), - message_id: self.msg_id, - total_processed, - main_gas_burned: self.main_gas_burned, - others_gas_burned: self.others_gas_burned.clone(), - } } /// Call non-void meta function from actor stored in manager. @@ -670,35 +702,6 @@ impl ExtManager { } } - #[track_caller] - fn prepare_for(&mut self, dispatch: &Dispatch, update_block: bool) { - self.msg_id = dispatch.id(); - self.origin = dispatch.source(); - self.log.clear(); - self.main_failed = false; - self.others_failed = false; - self.main_gas_burned = Gas::zero(); - self.others_gas_burned = { - let mut m = BTreeMap::new(); - let block_height = self.blocks_manager.get().height; - m.insert(block_height, Gas::zero()); - - m - }; - self.gas_allowance = Gas(GAS_ALLOWANCE); - if update_block { - let _ = self.blocks_manager.next_block(); - } - } - - fn mark_failed(&mut self, msg_id: MessageId) { - if self.msg_id == msg_id { - self.main_failed = true; - } else { - self.others_failed = true; - } - } - #[track_caller] fn init_success(&mut self, program_id: ProgramId) { let mut actors = self.actors.borrow_mut(); @@ -707,21 +710,16 @@ impl ExtManager { .expect("Can't find existing program"); actor.set_initialized(); - - drop(actors); } #[track_caller] - fn init_failure(&mut self, message_id: MessageId, program_id: ProgramId) { + fn init_failure(&mut self, program_id: ProgramId) { let mut actors = self.actors.borrow_mut(); let (actor, _) = actors .get_mut(&program_id) .expect("Can't find existing program"); *actor = TestActor::Dormant; - - drop(actors); - self.mark_failed(message_id); } fn process_mock(&mut self, mut mock: Box, dispatch: StoredDispatch) { @@ -997,14 +995,23 @@ impl JournalHandler for ExtManager { outcome: DispatchOutcome, ) { match outcome { - DispatchOutcome::MessageTrap { .. } => self.mark_failed(message_id), - DispatchOutcome::Success - | DispatchOutcome::NoExecution - | DispatchOutcome::Exit { .. } => {} + DispatchOutcome::MessageTrap { .. } => { + self.failed.insert(message_id); + } + DispatchOutcome::NoExecution => { + self.not_executed.insert(message_id); + } + DispatchOutcome::Success | DispatchOutcome::Exit { .. } => { + self.succeed.insert(message_id); + } DispatchOutcome::InitFailure { program_id, .. } => { - self.init_failure(message_id, program_id) + self.init_failure(program_id); + self.failed.insert(message_id); + } + DispatchOutcome::InitSuccess { program_id, .. } => { + self.init_success(program_id); + self.succeed.insert(message_id); } - DispatchOutcome::InitSuccess { program_id, .. } => self.init_success(program_id), } } @@ -1014,15 +1021,12 @@ impl JournalHandler for ExtManager { .spend(message_id, amount) .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); - if self.msg_id == message_id { - self.main_gas_burned = self.main_gas_burned.saturating_add(Gas(amount)); - } else { - self.others_gas_burned - .entry(self.blocks_manager.get().height) - .and_modify(|others_gas_burned| { - *others_gas_burned = others_gas_burned.saturating_add(Gas(amount)) - }); - } + self.gas_burned + .entry(message_id) + .and_modify(|gas| { + *gas += Gas(amount); + }) + .or_insert(Gas(amount)); } fn exit_dispatch(&mut self, id_exited: ProgramId, value_destination: ProgramId) { @@ -1255,11 +1259,8 @@ impl JournalHandler for ExtManager { gas_burned, ); - // Update gas allowance and start a new block with the `dispatch` being first in - // the queue. - self.gas_allowance = Gas(GAS_ALLOWANCE); + self.messages_processing_enabled = false; self.dispatches.push_front(dispatch); - self.blocks_manager.next_block(); } fn reserve_gas( diff --git a/gtest/src/program.rs b/gtest/src/program.rs index d4d9a133c0b..20d9d83936d 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -17,7 +17,6 @@ // along with this program. If not, see . use crate::{ - log::RunResult, manager::{Balance, ExtManager, GenuineProgram, MintMode, Program as InnerProgram, TestActor}, system::System, Result, GAS_ALLOWANCE, @@ -26,9 +25,8 @@ use codec::{Codec, Decode, Encode}; use gear_core::{ code::{Code, CodeAndId, InstrumentedCodeAndId}, ids::{prelude::*, CodeId, MessageId, ProgramId}, - message::{Dispatch, DispatchKind, Message, SignalMessage}, + message::{Dispatch, DispatchKind, Message}, }; -use gear_core_errors::SignalCode; use gear_utils::{MemoryPageDump, ProgramMemoryDump}; use gear_wasm_instrument::gas_metering::Schedule; use path_clean::PathClean; @@ -485,7 +483,7 @@ impl<'a> Program<'a> { } /// Send message to the program. - pub fn send(&self, from: ID, payload: C) -> RunResult + pub fn send(&self, from: ID, payload: C) -> MessageId where ID: Into, C: Codec, @@ -494,7 +492,7 @@ impl<'a> Program<'a> { } /// Send message to the program with value. - pub fn send_with_value(&self, from: ID, payload: C, value: u128) -> RunResult + pub fn send_with_value(&self, from: ID, payload: C, value: u128) -> MessageId where ID: Into, C: Codec, @@ -509,7 +507,7 @@ impl<'a> Program<'a> { payload: P, gas_limit: u64, value: u128, - ) -> RunResult + ) -> MessageId where ID: Into, P: Encode, @@ -518,7 +516,7 @@ impl<'a> Program<'a> { } /// Send message to the program with bytes payload. - pub fn send_bytes(&self, from: ID, payload: T) -> RunResult + pub fn send_bytes(&self, from: ID, payload: T) -> MessageId where ID: Into, T: Into>, @@ -528,7 +526,7 @@ impl<'a> Program<'a> { /// Send the message to the program with bytes payload and value. #[track_caller] - pub fn send_bytes_with_value(&self, from: ID, payload: T, value: u128) -> RunResult + pub fn send_bytes_with_value(&self, from: ID, payload: T, value: u128) -> MessageId where ID: Into, T: Into>, @@ -543,7 +541,7 @@ impl<'a> Program<'a> { payload: T, gas_limit: u64, value: u128, - ) -> RunResult + ) -> MessageId where ID: Into, T: Into>, @@ -551,15 +549,13 @@ impl<'a> Program<'a> { self.send_bytes_with_gas_and_value(from, payload, gas_limit, value) } - // todo [sab] this will return message id and adds message to the end of the queue - // need to be sure that you can obtain info about message being executed fn send_bytes_with_gas_and_value( &self, from: ID, payload: T, gas_limit: u64, value: u128, - ) -> RunResult + ) -> MessageId where ID: Into, T: Into>, @@ -593,34 +589,7 @@ impl<'a> Program<'a> { }; drop(actors); - system.validate_and_run_dispatch(Dispatch::new(kind, message)) - } - - /// Send signal to the program. - // todo [sab] remove that? - #[track_caller] - pub fn send_signal>(&self, from: ID, code: SignalCode) -> RunResult { - let mut system = self.manager.borrow_mut(); - - let source = from.into().0; - - let origin_msg_id = MessageId::generate_from_user( - system.blocks_manager.get().height, - source, - system.fetch_inc_message_nonce() as u128, - ); - let message = SignalMessage::new(origin_msg_id, code); - - let mut actors = system.actors.borrow_mut(); - let (actor, _) = actors.get_mut(&self.id).expect("Can't fail"); - - if let TestActor::Uninitialized(id @ None, _) = actor { - *id = Some(message.id()); - }; - - drop(actors); - let dispatch = message.into_dispatch(origin_msg_id, self.id); - system.validate_and_run_dispatch(dispatch) + system.validate_and_route_dispatch(Dispatch::new(kind, message)) } /// Get program id. @@ -903,18 +872,22 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 137, demo_futures_unordered::WASM_BINARY); let init_msg_payload = String::from("InvalidInput"); - let run_result = prog.send(user_id, init_msg_payload); + let msg_id = prog.send(user_id, init_msg_payload); - run_result.assert_panicked_with("Failed to load destination: Decode(Error)"); + let res = sys.run_next_block(); - let run_result = prog.send(user_id, String::from("should_be_skipped")); + res.assert_panicked_with(msg_id, "Failed to load destination: Decode(Error)"); + + let msg_id = prog.send(user_id, String::from("should_be_skipped")); + + let res = sys.run_next_block(); let expected_log = Log::error_builder(ErrorReplyReason::InactiveActor) .source(prog.id()) .dest(user_id); - assert!(!run_result.main_failed()); - assert!(run_result.contains(&expected_log)); + assert!(res.not_executed.contains(&msg_id)); + assert!(res.contains(&expected_log)); } #[test] @@ -932,10 +905,12 @@ mod tests { assert_eq!(prog.balance(), 2 * crate::EXISTENTIAL_DEPOSIT); prog.send_with_value(user_id, "init".to_string(), crate::EXISTENTIAL_DEPOSIT); + sys.run_next_block(); assert_eq!(prog.balance(), 3 * crate::EXISTENTIAL_DEPOSIT); assert_eq!(sys.balance_of(user_id), 9 * crate::EXISTENTIAL_DEPOSIT); prog.send_with_value(user_id, "PING".to_string(), 2 * crate::EXISTENTIAL_DEPOSIT); + sys.run_next_block(); assert_eq!(prog.balance(), 5 * crate::EXISTENTIAL_DEPOSIT); assert_eq!(sys.balance_of(user_id), 7 * crate::EXISTENTIAL_DEPOSIT); } @@ -958,21 +933,26 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 137, demo_piggy_bank::WASM_BINARY); prog.send_bytes(receiver, b"init"); + sys.run_next_block(); assert_eq!(prog.balance(), 0); // Send values to the program prog.send_bytes_with_value(sender0, b"insert", 2 * crate::EXISTENTIAL_DEPOSIT); + sys.run_next_block(); assert_eq!(sys.balance_of(sender0), 18 * crate::EXISTENTIAL_DEPOSIT); prog.send_bytes_with_value(sender1, b"insert", 4 * crate::EXISTENTIAL_DEPOSIT); + sys.run_next_block(); assert_eq!(sys.balance_of(sender1), 16 * crate::EXISTENTIAL_DEPOSIT); prog.send_bytes_with_value(sender2, b"insert", 6 * crate::EXISTENTIAL_DEPOSIT); + sys.run_next_block(); assert_eq!(sys.balance_of(sender2), 14 * crate::EXISTENTIAL_DEPOSIT); // Check program's balance assert_eq!(prog.balance(), (2 + 4 + 6) * crate::EXISTENTIAL_DEPOSIT); // Request to smash the piggy bank and send the value to the receiver address - let res = prog.send_bytes(receiver, b"smash"); + prog.send_bytes(receiver, b"smash"); + let res = sys.run_next_block(); let reply_to_id = { let log = res.log(); // 1 auto reply and 1 message from program @@ -1024,6 +1004,7 @@ mod tests { assert_eq!(sys.balance_of(user), crate::EXISTENTIAL_DEPOSIT); prog.send_bytes_with_value(user, b"init", crate::EXISTENTIAL_DEPOSIT + 1); + sys.run_next_block(); } #[test] @@ -1039,9 +1020,11 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 137, demo_piggy_bank::WASM_BINARY); prog.send_bytes(receiver, b"init"); + sys.run_next_block(); // Get zero value to the receiver's mailbox prog.send_bytes(receiver, b"smash"); + sys.run_next_block(); let receiver_mailbox = sys.get_mailbox(receiver); assert!(receiver_mailbox @@ -1052,6 +1035,7 @@ mod tests { // Get the value > ED to the receiver's mailbox prog.send_bytes_with_value(sender, b"insert", 2 * crate::EXISTENTIAL_DEPOSIT); prog.send_bytes(receiver, b"smash"); + sys.run_next_block(); // Check receiver's balance assert!(receiver_mailbox @@ -1084,14 +1068,16 @@ mod tests { // Init capacitor with limit = 15 prog.send(signer, InitMessage::Capacitor("15".to_string())); + sys.run_next_block(); // Charge capacitor with charge = 10 - let response = dbg!(prog.send_bytes(signer, b"10")); + dbg!(prog.send_bytes(signer, b"10")); + let res = sys.run_next_block(); let log = Log::builder() .source(prog.id()) .dest(signer) .payload_bytes([]); - assert!(response.contains(&log)); + assert!(res.contains(&log)); let cleanup = CleanupFolderOnDrop { path: "./296c6962726".to_string(), @@ -1099,25 +1085,27 @@ mod tests { prog.save_memory_dump("./296c6962726/demo_custom.dump"); // Charge capacitor with charge = 10 - let response = prog.send_bytes(signer, b"10"); + prog.send_bytes(signer, b"10"); + let res = sys.run_next_block(); let log = Log::builder() .source(prog.id()) .dest(signer) .payload_bytes("Discharged: 20"); // dbg!(log.clone()); - assert!(response.contains(&log)); + assert!(res.contains(&log)); assert!(signer_mailbox.claim_value(log).is_ok()); prog.load_memory_dump("./296c6962726/demo_custom.dump"); drop(cleanup); // Charge capacitor with charge = 10 - let response = prog.send_bytes(signer, b"10"); + prog.send_bytes(signer, b"10"); + let res = sys.run_next_block(); let log = Log::builder() .source(prog.id()) .dest(signer) .payload_bytes("Discharged: 20"); - assert!(response.contains(&log)); + assert!(res.contains(&log)); assert!(signer_mailbox.claim_value(log).is_ok()); } @@ -1133,22 +1121,24 @@ mod tests { // Init simple waiter prog.send(signer, InitMessage::SimpleWaiter); + sys.run_next_block(); // Invoke `exec::wait_for` when running for the first time - let result = prog.send_bytes(signer, b"doesn't matter"); + prog.send_bytes(signer, b"doesn't matter"); + let result = sys.run_next_block(); // No log entries as the program is waiting assert!(result.log().is_empty()); - // Spend 20 blocks and make the waiter to wake up - let results = sys.spend_blocks(20); + // Run task pool to make the waiter to wake up + let _ = sys.run_scheduled_tasks(20); + let res = sys.run_next_block(); let log = Log::builder() .source(prog.id()) .dest(signer) .payload_bytes("hello"); - - assert!(results.iter().any(|result| result.contains(&log))); + assert!(res.contains(&log)); } // Test for issue#3699 @@ -1163,15 +1153,18 @@ mod tests { // Init reserver prog.send(signer, InitMessage::Reserver); + sys.run_next_block(); for _ in 0..258 { // Reserve - let result = prog.send_bytes(signer, b"reserve"); - assert!(!result.main_failed()); + let msg_id = prog.send_bytes(signer, b"reserve"); + let result = sys.run_next_block(); + assert!(result.succeed.contains(&msg_id)); // Spend - let result = prog.send_bytes(signer, b"send from reservation"); - assert!(!result.main_failed()); + let msg_id = prog.send_bytes(signer, b"send from reservation"); + let result = sys.run_next_block(); + assert!(result.succeed.contains(&msg_id)); } } @@ -1185,11 +1178,13 @@ mod tests { let user_id = [42; 32]; let prog = Program::from_binary_with_id(&sys, 137, WASM_BINARY); - let run_result = prog.send(user_id, demo_exit_handle::scheme()); - assert!(!run_result.main_failed()); + let msg_id = prog.send(user_id, demo_exit_handle::scheme()); + let result = sys.run_next_block(); + assert!(result.succeed.contains(&msg_id)); - let run_result = prog.send_bytes(user_id, []); - assert!(!run_result.main_failed()); + let msg_id = prog.send_bytes(user_id, []); + let result = sys.run_next_block(); + assert!(result.succeed.contains(&msg_id)); } #[test] @@ -1202,7 +1197,8 @@ mod tests { let user_id = ActorId::zero(); // set insufficient gas for execution - let res = prog.send_with_gas(user_id, "init".to_string(), 1, 0); + let msg_id = prog.send_with_gas(user_id, "init".to_string(), 1, 0); + let res = sys.run_next_block(); let expected_log = Log::builder() @@ -1213,7 +1209,7 @@ mod tests { ))); assert!(res.contains(&expected_log)); - assert!(res.main_failed()); + assert!(res.failed.contains(&msg_id)); } #[test] @@ -1227,13 +1223,15 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 4242, WASM_BINARY); // Initialize program - let res = prog.send(user_id, Scheme::empty()); - assert!(!res.main_failed()); + let msg_id = prog.send(user_id, Scheme::empty()); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); // Reserve gas handle let handle = Calls::builder().reserve_gas(1_000_000, 10); - let res = prog.send(user_id, handle); - assert!(!res.main_failed()); + let msg_id = prog.send(user_id, handle); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); // Get reservation id from program let reservation_id = sys @@ -1255,8 +1253,9 @@ mod tests { // Unreserve gas handle let handle = Calls::builder().unreserve_gas(reservation_id.into_bytes()); - let res = prog.send(user_id, handle); - assert!(!res.main_failed()); + let msg_id = prog.send(user_id, handle); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); // Check reservation is removed from the tree assert!(!sys.0.borrow().gas_tree.exists(reservation_id)); @@ -1274,8 +1273,9 @@ mod tests { let prog = Program::from_binary_with_id(&sys, prog_id, WASM_BINARY); // Initialize program - let res = prog.send(user_id, Scheme::empty()); - assert!(!res.main_failed()); + let msg_id = prog.send(user_id, Scheme::empty()); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); // Send user message from reservation let payload = b"to_user".to_vec(); @@ -1283,8 +1283,9 @@ mod tests { .reserve_gas(10_000_000_000, 5) .store("reservation") .reservation_send_value("reservation", user_id.into_origin().0, payload.clone(), 0); - let res = prog.send(user_id, handle); - assert!(!res.main_failed()); + let msg_id = prog.send(user_id, handle); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); // Check user message in mailbox let mailbox = sys.get_mailbox(user_id); @@ -1301,16 +1302,18 @@ mod tests { Calls::builder().noop(), Calls::builder().noop(), ); - let res = new_program.send(user_id, scheme); - assert!(!res.main_failed()); + let msg_id = new_program.send(user_id, scheme); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); // Send program message from reservation let handle = Calls::builder() .reserve_gas(10_000_000_000, 5) .store("reservation") .reservation_send_value("reservation", new_prog_id.into_origin().0, [], 0); - let res = prog.send(user_id, handle); - assert!(!res.main_failed()); + let msg_id = prog.send(user_id, handle); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); assert!(mailbox.contains(&Log::builder().payload_bytes(payload).source(new_prog_id))); } } diff --git a/gtest/src/system.rs b/gtest/src/system.rs index c07bc8f77d6..eb3b7c679a0 100644 --- a/gtest/src/system.rs +++ b/gtest/src/system.rs @@ -17,23 +17,23 @@ // along with this program. If not, see . use crate::{ - log::RunResult, + log::{BlockRunResult, CoreLog}, mailbox::ActorMailbox, manager::{Actors, Balance, ExtManager, MintMode}, program::{Program, ProgramIdWrapper}, + Gas, GAS_ALLOWANCE, }; use codec::{Decode, DecodeAll}; use colored::Colorize; use env_logger::{Builder, Env}; use gear_core::{ ids::{CodeId, ProgramId}, - message::Dispatch, pages::GearPage, }; use gear_lazy_pages::{LazyPagesStorage, LazyPagesVersion}; use gear_lazy_pages_common::LazyPagesInitContext; use path_clean::PathClean; -use std::{borrow::Cow, cell::RefCell, env, fs, io::Write, path::Path, thread}; +use std::{borrow::Cow, cell::RefCell, env, fs, io::Write, mem, path::Path, thread}; thread_local! { /// `System` is a singleton with a one instance and no copies returned. @@ -174,15 +174,69 @@ impl System { .try_init(); } - /// Send raw message dispatch. - // todo [sab] remove that - it's a hack over the gtest and gear protocol. - pub fn send_dispatch(&self, dispatch: Dispatch) -> RunResult { - self.0.borrow_mut().validate_and_run_dispatch(dispatch) + /// Run next block. + /// + /// Block execution model is the following: + /// - increase the block number, update the timestamp + /// - process tasks from the task pool + /// - process messages in the queue. + /// + /// The system is always initialized with a 0 block number. Current block + /// number in the system is the number of the already executed block, + /// therefore block execution starts with a block info update (block + /// number increase, timestamp update). For example, if current block + /// number is 2, it means that messages and tasks on 2 were executed, so + /// the method goes to block number 3 and executes tasks and messages for + /// the updated block number. + /// + /// Task processing basically tries to execute the scheduled to the specific + /// block tasks: + /// - delayed sending + /// - waking message + /// - removing from the mailbox + /// - removing reservations + /// - removing stalled wait message. + /// + /// Messages processing executes messages until either queue becomes empty + /// or block gas allowance is fully consumed. + pub fn run_next_block(&self) -> BlockRunResult { + self.run_next_block_with_allowance(Gas(GAS_ALLOWANCE)) + } + + /// Runs blocks same as [`Self::run_to_next_block`], but with limited + /// allowance. + pub fn run_next_block_with_allowance(&self, allowance: Gas) -> BlockRunResult { + if allowance > Gas(GAS_ALLOWANCE) { + panic!("Provided allowance more than allowed limit of {GAS_ALLOWANCE}."); + } + + self.0.borrow_mut().run_new_block(allowance) + } + + /// Runs blocks same as [`Self::run_to_next_block`], but executes blocks to + /// block number `bn` including it. + pub fn run_to_block(&self, bn: u32) -> Vec { + let mut manager = self.0.borrow_mut(); + + let mut current_block = manager.blocks_manager.get().height; + if current_block > bn { + panic!("Can't run blocks until bn {bn}, as current bn is {current_block}"); + } + + let mut ret = Vec::with_capacity((bn - current_block) as usize); + while current_block != bn { + let res = manager.run_new_block(Gas(GAS_ALLOWANCE)); + ret.push(res); + + current_block = manager.blocks_manager.get().height; + } + + ret } - // todo [sab] this must be spending blocks only on task pool - /// Spend blocks and return all results. - pub fn spend_blocks(&self, amount: u32) -> Vec { + /// Runs `amount` of blocks only with processing task pool, without + /// processing the message queue. + pub fn run_scheduled_tasks(&self, amount: u32) -> Vec { let mut manager = self.0.borrow_mut(); let block_height = manager.blocks_manager.get().height; @@ -192,12 +246,20 @@ impl System { let block_info = manager.blocks_manager.next_block(); let next_block_number = block_info.height; - let mut results = manager.process_delayed_dispatches(next_block_number); - results.extend(manager.process_scheduled_wait_list(next_block_number)); - results + manager.process_tasks(next_block_number); + + let log = mem::take(&mut manager.log) + .into_iter() + .map(CoreLog::from) + .collect(); + BlockRunResult { + block_info, + gas_allowance_spent: Gas(GAS_ALLOWANCE) - manager.gas_allowance, + log, + ..Default::default() + } }) - .collect::>>() - .concat() + .collect() } /// Return the current block height of the testing environment. @@ -352,17 +414,61 @@ mod tests { #[test] fn test_multithread_copy_singleton() { let first_instance = System::new(); - first_instance.spend_blocks(5); + first_instance.run_scheduled_tasks(5); assert_eq!(first_instance.block_height(), 5); let h = std::thread::spawn(|| { let second_instance = System::new(); - second_instance.spend_blocks(10); + second_instance.run_scheduled_tasks(10); assert_eq!(second_instance.block_height(), 10); }); h.join().expect("internal error failed joining thread"); } + + #[test] + fn test_bn_adjustments() { + let sys = System::new(); + assert_eq!(sys.block_height(), 0); + + // ### Check block info after run to next block ### + let res = sys.run_next_block(); + let block_info = res.block_info; + assert_eq!(block_info.height, sys.block_height()); + assert_eq!(block_info.height, 1); + + // ### Check block info after run to block ### + let current_height = block_info.height; + let until_height = 5; + let results = sys.run_to_block(until_height); + assert_eq!(results.len(), (until_height - current_height) as usize); + + // Check first block executed is always the next block + let first_run = results.first().expect("checked above"); + assert_eq!(first_run.block_info.height, current_height + 1); + + // Check the last block executed number + let last_run = results.last().expect("checked above"); + assert_eq!(last_run.block_info.height, until_height); + assert_eq!(last_run.block_info.height, sys.block_height()); + + // ### Check block info after running the task pool ### + let current_height = last_run.block_info.height; + let amount_of_blocks = 10; + let results = sys.run_scheduled_tasks(amount_of_blocks); + assert_eq!(results.len(), amount_of_blocks as usize); + + let first_run = results.first().expect("checked above"); + assert_eq!(first_run.block_info.height, current_height + 1); + + let last_run = results.last().expect("checked above"); + assert_eq!( + last_run.block_info.height, + current_height + amount_of_blocks + ); + + assert_eq!(last_run.block_info.height, 15); + } } From 32ced45de218b82a832b945753548811b37decc1 Mon Sep 17 00:00:00 2001 From: Sabaun Taraki Date: Wed, 7 Aug 2024 15:00:07 +0300 Subject: [PATCH 03/29] State proper todos --- gtest/src/log.rs | 2 +- gtest/src/manager.rs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/gtest/src/log.rs b/gtest/src/log.rs index a2ce78d8f1e..29824cfb8e0 100644 --- a/gtest/src/log.rs +++ b/gtest/src/log.rs @@ -396,7 +396,7 @@ pub struct BlockRunResult { /// Total messages processed during the current /// execution. pub total_processed: u32, - // todo [sab] change concept of the log + // TODO #4122 /// Logs created during the current execution. pub log: Vec, /// Mapping gas burned for each message during diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index d68a7cb3585..e3cb8f43226 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -419,12 +419,8 @@ impl ExtManager { message_id } - // todo [sab] include task pool gas charges and stop processing if gas allowance - // is not enough (test that) todo [sab] bring queue processing from common - // todo [sab] check programs states and their executions in accordance to - // `process_dispatch` and etc. basically refactor the - // process_dormant/process_normal and etc. for example test - // `test_handle_messages_to_failing_program` must deny sending to dormant actor + // TODO #4120 Charge for task pool processing the gas from gas allowance + // TODO #4121 #[track_caller] pub(crate) fn run_new_block(&mut self, allowance: Gas) -> BlockRunResult { self.gas_allowance = allowance; From b23c60ae88d3fa031d0b1151d8515f69f4182517 Mon Sep 17 00:00:00 2001 From: Sabaun Taraki Date: Thu, 8 Aug 2024 21:56:27 +0300 Subject: [PATCH 04/29] Fix most of workspace tests --- examples/autoreply/src/lib.rs | 14 ++++--- examples/custom/src/btree.rs | 10 +++-- examples/distributor/src/lib.rs | 40 +++++++++++------- examples/gas-burned/src/lib.rs | 18 ++++++-- examples/new-meta/tests/read_state.rs | 1 + examples/node/src/lib.rs | 39 +++++++++++------- examples/program-factory/src/lib.rs | 48 +++++++++++----------- examples/reserve-gas/src/lib.rs | 5 ++- examples/signal-entry/src/lib.rs | 27 +++++++----- examples/stack-allocations/src/lib.rs | 9 ++-- examples/syscall-error/src/lib.rs | 6 +-- examples/wait_wake/src/lib.rs | 43 +++++++++++-------- examples/waiter/tests/mx_lock_access.rs | 11 ++--- examples/waiter/tests/rw_lock_access.rs | 10 +++-- utils/cargo-gbuild/test-program/src/lib.rs | 10 +++-- utils/cargo-gbuild/tests/smoke.rs | 10 +++-- utils/wasm-builder/test-program/src/lib.rs | 6 ++- 17 files changed, 183 insertions(+), 124 deletions(-) diff --git a/examples/autoreply/src/lib.rs b/examples/autoreply/src/lib.rs index 677f4c08cb1..f36016841ca 100644 --- a/examples/autoreply/src/lib.rs +++ b/examples/autoreply/src/lib.rs @@ -48,17 +48,19 @@ mod tests { let from = 42; // Init Program-1 - let res = prog1.send(from, ActorId::zero()); - assert!(!res.main_failed()); + let init_msg1 = prog1.send(from, ActorId::zero()); // Init Program-2 with Program-1 as destination let prog2 = Program::current(&system); - let res = prog2.send(from, prog1_id); - assert!(!res.main_failed()); + let init_msg2 = prog2.send(from, prog1_id); // Send a message from Program-2 to Program-1 - let res = prog2.send_bytes(from, b"Let's go!"); - assert!(!res.main_failed()); + let msg3 = prog2.send_bytes(from, b"Let's go!"); + + let res = system.run_next_block(); + for msg in [init_msg1, init_msg2, msg3] { + assert!(res.succeed.contains(&msg)); + } // Check whether the auto-reply was received let reply_received: bool = prog2 diff --git a/examples/custom/src/btree.rs b/examples/custom/src/btree.rs index a19699b0ae1..2d005b8f960 100644 --- a/examples/custom/src/btree.rs +++ b/examples/custom/src/btree.rs @@ -105,7 +105,8 @@ mod tests { let from = 42; - let res = program.send(from, InitMessage::BTree); + program.send(from, InitMessage::BTree); + let res = system.run_next_block(); let log = Log::builder().source(program.id()).dest(from); assert!(res.contains(&log)); } @@ -119,7 +120,7 @@ mod tests { let from = 42; - let _res = program.send(from, InitMessage::BTree); + program.send(from, InitMessage::BTree); IntoIterator::into_iter([ Request::Insert(0, 1), @@ -131,7 +132,10 @@ mod tests { Request::Clear, Request::List, ]) - .map(|r| program.send(from, r)) + .map(|r| { + program.send(from, r); + system.run_next_block() + }) .zip(IntoIterator::into_iter([ Reply::Value(None), Reply::Value(Some(1)), diff --git a/examples/distributor/src/lib.rs b/examples/distributor/src/lib.rs index 34d7856df48..4a9965ad7d4 100644 --- a/examples/distributor/src/lib.rs +++ b/examples/distributor/src/lib.rs @@ -68,7 +68,8 @@ mod tests { let from = 42; - let res = program.send_bytes(from, b"init"); + program.send_bytes(from, b"init"); + let res = system.run_next_block(); let log = Log::builder().source(program.id()).dest(from); assert!(res.contains(&log)); } @@ -82,16 +83,19 @@ mod tests { let from = 42; - let _res = program.send_bytes(from, b"init"); + // Init + program.send_bytes(from, b"init"); - let res = program.send(from, Request::Receive(10)); + program.send(from, Request::Receive(10)); + let res = system.run_next_block(); let log = Log::builder() .source(program.id()) .dest(from) .payload(Reply::Success); assert!(res.contains(&log)); - let res = program.send(from, Request::Report); + program.send(from, Request::Report); + let res = system.run_next_block(); let log = Log::builder() .source(program.id()) .dest(from) @@ -110,22 +114,24 @@ mod tests { let from = 42; let program_1 = Program::current_with_id(system, program_1_id); - let _res = program_1.send_bytes(from, b"init"); + program_1.send_bytes(from, b"init"); let program_2 = Program::current_with_id(system, program_2_id); - let _res = program_2.send_bytes(from, b"init"); + program_2.send_bytes(from, b"init"); let program_3 = Program::current_with_id(system, program_3_id); - let _res = program_3.send_bytes(from, b"init"); + program_3.send_bytes(from, b"init"); - let res = program_1.send(from, Request::Join(program_2_id.into())); + program_1.send(from, Request::Join(program_2_id.into())); + let res = system.run_next_block(); let log = Log::builder() .source(program_1.id()) .dest(from) .payload(Reply::Success); assert!(res.contains(&log)); - let res = program_1.send(from, Request::Join(program_3_id.into())); + program_1.send(from, Request::Join(program_3_id.into())); + let res = system.run_next_block(); let log = Log::builder() .source(program_1.id()) .dest(from) @@ -142,21 +148,24 @@ mod tests { let from = 42; - let res = program_1.send(from, Request::Receive(11)); + program_1.send(from, Request::Receive(11)); + let res = system.run_next_block(); let log = Log::builder() .source(program_1.id()) .dest(from) .payload(Reply::Success); assert!(res.contains(&log)); - let res = program_2.send(from, Request::Report); + program_2.send(from, Request::Report); + let res = system.run_next_block(); let log = Log::builder() .source(program_2.id()) .dest(from) .payload(Reply::Amount(5)); assert!(res.contains(&log)); - let res = program_1.send(from, Request::Report); + program_1.send(from, Request::Report); + let res = system.run_next_block(); let log = Log::builder() .source(program_1.id()) .dest(from) @@ -174,10 +183,13 @@ mod tests { let from = 42; let program_4 = Program::current_with_id(&system, program_4_id); - let _res = program_4.send_bytes(from, b"init"); + program_4.send_bytes(from, b"init"); IntoIterator::into_iter([Request::Receive(11), Request::Join(program_4_id.into())]) - .map(|request| program_1.send(from, request)) + .map(|request| { + program_1.send(from, request); + system.run_next_block() + }) .zip(IntoIterator::into_iter([Reply::Success, Reply::Success])) .for_each(|(result, reply)| { let log = Log::builder() diff --git a/examples/gas-burned/src/lib.rs b/examples/gas-burned/src/lib.rs index 45e628fea74..5b6f056cc6b 100644 --- a/examples/gas-burned/src/lib.rs +++ b/examples/gas-burned/src/lib.rs @@ -23,13 +23,23 @@ mod tests { let from = 42; let program = Program::current(&system); - let res = program.send_bytes(from, "init"); - let init_gas_burned = res.main_gas_burned(); + let init_msg_id = program.send_bytes(from, "init"); + let res = system.run_next_block(); + let init_gas_burned = res + .gas_burned + .get(&init_msg_id) + .copied() + .expect("internal error: init message isn't sent"); log::debug!("Init gas burned: {init_gas_burned}"); assert!(init_gas_burned > Gas::zero()); - let res = program.send_bytes(from, "handle"); - let handle_gas_burned = res.main_gas_burned(); + let handle_msg_id = program.send_bytes(from, "handle"); + let res = system.run_next_block(); + let handle_gas_burned = res + .gas_burned + .get(&handle_msg_id) + .copied() + .expect("internal error: init message isn't sent"); log::debug!("Handle gas burned: {handle_gas_burned}"); assert!(handle_gas_burned > init_gas_burned); } diff --git a/examples/new-meta/tests/read_state.rs b/examples/new-meta/tests/read_state.rs index 9c7fffdc380..2caf1b64948 100644 --- a/examples/new-meta/tests/read_state.rs +++ b/examples/new-meta/tests/read_state.rs @@ -192,5 +192,6 @@ fn initialize_current_program(system: &System) -> Program { currency: "USD".into(), }, ); + system.run_next_block(); program } diff --git a/examples/node/src/lib.rs b/examples/node/src/lib.rs index 87cd998e099..3718cbd44fe 100644 --- a/examples/node/src/lib.rs +++ b/examples/node/src/lib.rs @@ -71,8 +71,9 @@ mod tests { let from = 42; let program = Program::current(&system); - let res = program.send(from, Request::IsReady); - assert!(res.main_failed()); + let msg_id = program.send(from, Request::IsReady); + let res = system.run_next_block(); + assert!(res.failed.contains(&msg_id)); } #[test] @@ -83,9 +84,10 @@ mod tests { let from = 42; let program = Program::current(&system); - let res = program.send(from, Initialization { status: 5 }); + let msg_id = program.send(from, Initialization { status: 5 }); let log = Log::builder().source(program.id()).dest(from); - assert!(!res.main_failed()); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); assert!(res.contains(&log)); } @@ -97,23 +99,26 @@ mod tests { let from = 42; let program = Program::current(&system); - let _res = program.send(from, Initialization { status: 5 }); + program.send(from, Initialization { status: 5 }); - let res = program.send(from, Request::IsReady); + program.send(from, Request::IsReady); let log = Log::builder() .source(program.id()) .dest(from) .payload(Reply::Yes); + let res = system.run_next_block(); assert!(res.contains(&log)); - let res = program.send(from, Request::Begin(Operation { to_status: 7 })); + program.send(from, Request::Begin(Operation { to_status: 7 })); + let res = system.run_next_block(); let log = Log::builder() .source(program.id()) .dest(from) .payload(Reply::Success); assert!(res.contains(&log)); - let res = program.send(from, Request::Commit); + program.send(from, Request::Commit); + let res = system.run_next_block(); let log = Log::builder() .source(program.id()) .dest(from) @@ -133,36 +138,40 @@ mod tests { let program_3_id = 3; let program_1 = Program::current_with_id(&system, program_1_id); - let _res = program_1.send(from, Initialization { status: 5 }); + program_1.send(from, Initialization { status: 5 }); let program_2 = Program::current_with_id(&system, program_2_id); - let _res = program_2.send(from, Initialization { status: 5 }); + program_2.send(from, Initialization { status: 5 }); let program_3 = Program::current_with_id(&system, program_3_id); - let _res = program_3.send(from, Initialization { status: 9 }); + program_3.send(from, Initialization { status: 9 }); - let res = program_1.send(from, Request::Add(program_2_id.into())); + program_1.send(from, Request::Add(program_2_id.into())); + let res = system.run_next_block(); let log = Log::builder() .source(program_1.id()) .dest(from) .payload(Reply::Success); assert!(res.contains(&log)); - let res = program_1.send(from, Request::Add(program_3_id.into())); + program_1.send(from, Request::Add(program_3_id.into())); + let res = system.run_next_block(); let log = Log::builder() .source(program_1.id()) .dest(from) .payload(Reply::Success); assert!(res.contains(&log)); - let res = program_1.send(from, Request::Begin(Operation { to_status: 7 })); + program_1.send(from, Request::Begin(Operation { to_status: 7 })); + let res = system.run_next_block(); let log = Log::builder() .source(program_1.id()) .dest(from) .payload(Reply::Success); assert!(res.contains(&log)); - let res = program_1.send(from, Request::Commit); + program_1.send(from, Request::Commit); + let res = system.run_next_block(); let log = Log::builder() .source(program_1.id()) .dest(from) diff --git a/examples/program-factory/src/lib.rs b/examples/program-factory/src/lib.rs index e0a169a5b29..64b0f2ce91a 100644 --- a/examples/program-factory/src/lib.rs +++ b/examples/program-factory/src/lib.rs @@ -71,8 +71,9 @@ mod tests { sys.mint_to(user_id, 100 * UNITS); // Send `init` msg to factory - let res = factory.send_bytes_with_value(user_id, "EMPTY", 10 * UNITS); - assert!(!res.main_failed()); + let msg_id = factory.send_bytes_with_value(user_id, "EMPTY", 10 * UNITS); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); assert!(sys.is_active_program(100)); factory @@ -97,13 +98,11 @@ mod tests { let factory = prepare_factory(&sys); // Send `handle` msg to factory to create a new child - let res = factory.send_bytes(10001, CreateProgram::Default.encode()); - let child_id_expected = calculate_program_id( - CHILD_CODE_HASH.into(), - &0i32.to_le_bytes(), - Some(res.sent_message_id()), - ); - assert!(!res.main_failed()); + let msg_id = factory.send_bytes(10001, CreateProgram::Default.encode()); + let res = sys.run_next_block(); + let child_id_expected = + calculate_program_id(CHILD_CODE_HASH.into(), &0i32.to_le_bytes(), Some(msg_id)); + assert!(res.succeed.contains(&msg_id)); assert!(sys.is_active_program(child_id_expected)); } @@ -117,24 +116,24 @@ mod tests { let payload = CreateProgram::Custom(vec![(CHILD_CODE_HASH, salt.to_vec(), 100_000_000)]); // Send `handle` msg to factory to create a new child - let res = factory.send_bytes(10001, payload.encode()); + let msg_id = factory.send_bytes(10001, payload.encode()); + let res = sys.run_next_block(); - let child_id_expected = - calculate_program_id(CHILD_CODE_HASH.into(), &salt, Some(res.sent_message_id())); + let child_id_expected = calculate_program_id(CHILD_CODE_HASH.into(), &salt, Some(msg_id)); - assert!(!res.main_failed()); + assert!(res.succeed.contains(&msg_id)); assert!(sys.is_active_program(child_id_expected)); // Send `handle` msg to create a duplicate - let res = factory.send_bytes(10001, payload.encode()); + let msg_id = factory.send_bytes(10001, payload.encode()); + let res = sys.run_next_block(); - let child_id_expected = - calculate_program_id(CHILD_CODE_HASH.into(), &salt, Some(res.sent_message_id())); + let child_id_expected = calculate_program_id(CHILD_CODE_HASH.into(), &salt, Some(msg_id)); - assert!(!res.main_failed()); + assert!(res.succeed.contains(&msg_id)); assert!(sys.is_active_program(child_id_expected)); - assert_eq!(res.total_processed(), 3 + 1 + 1); // +1 for the original message, initiated by user +1 for auto generated replies + assert_eq!(res.total_processed, 3 + 1 + 1); // +1 for the original message, initiated by user +1 for auto generated replies } #[test] @@ -147,13 +146,11 @@ mod tests { let non_existing_code_hash = [10u8; 32]; let salt = b"some_salt"; let payload = CreateProgram::Custom(vec![(non_existing_code_hash, salt.to_vec(), 100_000)]); - let res = factory.send_bytes(10001, payload.encode()); - let fictional_program_id = calculate_program_id( - non_existing_code_hash.into(), - salt, - Some(res.sent_message_id()), - ); - assert!(!res.main_failed()); + let msg_id = factory.send_bytes(10001, payload.encode()); + let res = sys.run_next_block(); + let fictional_program_id = + calculate_program_id(non_existing_code_hash.into(), salt, Some(msg_id)); + assert!(res.succeed.contains(&msg_id)); // No new program with fictional id assert!(!sys.is_active_program(fictional_program_id)); } @@ -175,5 +172,6 @@ mod tests { 100_000, )]); factory.send_bytes(10001, payload.encode()); + let _ = sys.run_next_block(); } } diff --git a/examples/reserve-gas/src/lib.rs b/examples/reserve-gas/src/lib.rs index 85c616371bf..ee335804869 100644 --- a/examples/reserve-gas/src/lib.rs +++ b/examples/reserve-gas/src/lib.rs @@ -78,7 +78,7 @@ mod tests { let program = Program::current(&system); - let res = program.send( + let msg_id = program.send( 0, InitAction::Normal(vec![ // orphan reservation; will be removed automatically @@ -87,6 +87,7 @@ mod tests { (25_000, 5), ]), ); - assert!(!res.main_failed()); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); } } diff --git a/examples/signal-entry/src/lib.rs b/examples/signal-entry/src/lib.rs index 53abc5ee02c..f15b4b655f7 100644 --- a/examples/signal-entry/src/lib.rs +++ b/examples/signal-entry/src/lib.rs @@ -68,15 +68,20 @@ mod tests { use gstd::errors::{SignalCode, SimpleExecutionError}; use gtest::{Program, System}; - #[test] - fn signal_can_be_sent() { - let system = System::new(); - system.init_logger(); - - let program = Program::current(&system); - - let signal_code: SignalCode = SimpleExecutionError::UserspacePanic.into(); - let res = program.send_signal(0, signal_code); - assert!(!res.main_failed()); - } +// #[test] +// fn signal_can_be_sent() { +// let system = System::new(); +// system.init_logger(); +// +// let program = Program::current(&system); +// +// todo!(); +// let signal_code: SignalCode = SimpleExecutionError::UserspacePanic.into(); +// program.send_signal(0, signal_code); +// let res = system.run_next_block(); +// +// // Checking signal executed successfully by checking if there are failed messages. +// assert!(res.failed.is_empty()); +// assert!(res.not_executed.is_empty()); +// } } diff --git a/examples/stack-allocations/src/lib.rs b/examples/stack-allocations/src/lib.rs index b23726856f4..5f9ab3a1ee6 100644 --- a/examples/stack-allocations/src/lib.rs +++ b/examples/stack-allocations/src/lib.rs @@ -101,20 +101,21 @@ mod tests { } // Init program - if program.send(from, InitConfig { actions }).main_failed() { - panic!("Init failed"); - } + let msg_id = program.send(from, InitConfig { actions }); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); let number: u8 = rng.gen_range(0..=MAX_NUMBER); let expected_check_sum = actions_amount * number as usize * HANDLE_DATA_SIZE; // Send data to handle - let res = program.send( + program.send( from, HandleData { data: [number; HANDLE_DATA_SIZE], }, ); + let res = system.run_next_block(); assert_eq!( expected_check_sum as u32, diff --git a/examples/syscall-error/src/lib.rs b/examples/syscall-error/src/lib.rs index 83edf6ed522..2f2e3f5080e 100644 --- a/examples/syscall-error/src/lib.rs +++ b/examples/syscall-error/src/lib.rs @@ -41,8 +41,8 @@ mod tests { system.init_logger(); let program = Program::current(&system); - - let res = program.send_bytes(0, b"dummy"); - assert!(!res.main_failed()); + let msg_id = program.send_bytes(0, b"dummy"); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); } } diff --git a/examples/wait_wake/src/lib.rs b/examples/wait_wake/src/lib.rs index d450e946f0a..33673502844 100644 --- a/examples/wait_wake/src/lib.rs +++ b/examples/wait_wake/src/lib.rs @@ -53,7 +53,8 @@ mod tests { let from = 42; - let res = program.send_bytes(from, b"init"); + let msg_id = program.send_bytes(from, b"init"); + let res = system.run_next_block(); let log = Log::builder().source(program.id()).dest(from); assert!(res.contains(&log)); } @@ -66,26 +67,28 @@ mod tests { let from = 42; let program = Program::current(&system); - let _res = program.send_bytes(from, b"init"); + program.send_bytes(from, b"init"); let msg_1_echo_wait = 100; - let res = program.send(from, Request::EchoWait(msg_1_echo_wait)); - let msg_id_1 = res.sent_message_id(); + let msg_id_1 = program.send(from, Request::EchoWait(msg_1_echo_wait)); + let res = system.run_next_block(); assert!(res.log().is_empty()); let msg_2_echo_wait = 200; - let res = program.send(from, Request::EchoWait(msg_2_echo_wait)); - let msg_id_2 = res.sent_message_id(); + let msg_id_2 = program.send(from, Request::EchoWait(msg_2_echo_wait)); + let res = system.run_next_block(); assert!(res.log().is_empty()); - let res = program.send(from, Request::Wake(msg_id_1.into())); + program.send(from, Request::Wake(msg_id_1.into())); + let res = system.run_next_block(); let log = Log::builder() .source(program.id()) .dest(from) .payload(msg_1_echo_wait); assert!(res.contains(&log)); - let res = program.send(from, Request::Wake(msg_id_2.into())); + program.send(from, Request::Wake(msg_id_2.into())); + let res = system.run_next_block(); let log = Log::builder() .source(program.id()) .dest(from) @@ -101,30 +104,32 @@ mod tests { let from = 42; let program_1 = Program::current(&system); - let _res = program_1.send_bytes(from, b"init"); + program_1.send_bytes(from, b"init"); let program_2 = Program::current(&system); - let _res = program_2.send_bytes(from, b"init"); + program_2.send_bytes(from, b"init"); let msg_1_echo_wait = 100; - let res = program_1.send(from, Request::EchoWait(msg_1_echo_wait)); - let msg_id_1 = res.sent_message_id(); + let msg_id_1 = program_1.send(from, Request::EchoWait(msg_1_echo_wait)); + let res = system.run_next_block(); assert!(res.log().is_empty()); let msg_2_echo_wait = 200; - let res = program_2.send(from, Request::EchoWait(msg_2_echo_wait)); - let msg_id_2 = res.sent_message_id(); + let msg_id_2 = program_2.send(from, Request::EchoWait(msg_2_echo_wait)); + let res = system.run_next_block(); assert!(res.log().is_empty()); // try to wake other messages - let res = program_2.send(from, Request::Wake(msg_id_1.into())); + program_2.send(from, Request::Wake(msg_id_1.into())); + let res = system.run_next_block(); let log = Log::builder() .source(program_2.id()) .dest(from) .payload_bytes([]); assert!(res.contains(&log)); - let res = program_1.send(from, Request::Wake(msg_id_2.into())); + program_1.send(from, Request::Wake(msg_id_2.into())); + let res = system.run_next_block(); let log = Log::builder() .source(program_1.id()) .dest(from) @@ -132,14 +137,16 @@ mod tests { assert!(res.contains(&log)); // wake msg_1 for program_1 and msg_2 for program_2 - let res = program_1.send(from, Request::Wake(msg_id_1.into())); + program_1.send(from, Request::Wake(msg_id_1.into())); + let res = system.run_next_block(); let log = Log::builder() .source(program_1.id()) .dest(from) .payload(msg_1_echo_wait); assert!(res.contains(&log)); - let res = program_2.send(from, Request::Wake(msg_id_2.into())); + program_2.send(from, Request::Wake(msg_id_2.into())); + let res = system.run_next_block(); let log = Log::builder() .source(program_2.id()) .dest(from) diff --git a/examples/waiter/tests/mx_lock_access.rs b/examples/waiter/tests/mx_lock_access.rs index ed431cd3b85..b8d632c758e 100644 --- a/examples/waiter/tests/mx_lock_access.rs +++ b/examples/waiter/tests/mx_lock_access.rs @@ -35,12 +35,12 @@ fn access_mx_lock_guard_from_different_msg_fails( let system = System::new(); let (program, lock_msg_id) = init_fixture(&system); - let lock_access_result = + let lock_access_msg_id = program.send(USER_ID, Command::MxLockStaticAccess(lock_access_subcommand)); + let lock_access_result = system.run_next_block(); lock_access_result.assert_panicked_with(format!( - "Mutex guard held by message {lock_msg_id} is being accessed by message {}", - lock_access_result.sent_message_id(), + "Mutex guard held by message {lock_msg_id} is being accessed by message {lock_access_msg_id}", )); } @@ -48,12 +48,13 @@ fn init_fixture(system: &System) -> (Program<'_>, MessageId) { system.init_logger_with_default_filter(""); let program = Program::current(system); program.send_bytes(USER_ID, []); - let lock_result = program.send( + let msg_id = program.send( USER_ID, Command::MxLock( None, MxLockContinuation::General(LockContinuation::MoveToStatic), ), ); - (program, lock_result.sent_message_id()) + system.run_next_block(); + (program, msg_id) } diff --git a/examples/waiter/tests/rw_lock_access.rs b/examples/waiter/tests/rw_lock_access.rs index c3603deea32..5a21b3315d1 100644 --- a/examples/waiter/tests/rw_lock_access.rs +++ b/examples/waiter/tests/rw_lock_access.rs @@ -77,13 +77,14 @@ fn access_rw_lock_guard_from_different_msg_fails( let system = System::new(); let (program, lock_msg_id) = init_fixture(&system, lock_type); - let lock_access_result = program.send( + let lock_access_msg_id = program.send( USER_ID, Command::RwLockStaticAccess(lock_type, lock_access_subcommand), ); + let lock_access_result = system.run_next_block(); lock_access_result.assert_panicked_with(format!( - "{lock_type:?} lock guard held by message {lock_msg_id} is being accessed by message {}", + "{lock_type:?} lock guard held by message {lock_msg_id} is being accessed by message {lock_access_msg_id}", lock_access_result.sent_message_id(), )); } @@ -92,12 +93,13 @@ fn init_fixture(system: &System, lock_type: RwLockType) -> (Program<'_>, Message system.init_logger_with_default_filter(""); let program = Program::current(system); program.send_bytes(USER_ID, []); - let lock_result = program.send( + let msg_id = program.send( USER_ID, Command::RwLock( lock_type, RwLockContinuation::General(LockContinuation::MoveToStatic), ), ); - (program, lock_result.sent_message_id()) + system.run_next_block(); + (program, msg_id) } diff --git a/utils/cargo-gbuild/test-program/src/lib.rs b/utils/cargo-gbuild/test-program/src/lib.rs index 659dc8eff36..ac5e4e501c3 100644 --- a/utils/cargo-gbuild/test-program/src/lib.rs +++ b/utils/cargo-gbuild/test-program/src/lib.rs @@ -55,13 +55,15 @@ mod tests { let program = Program::current(&system); // Init program - let res = program.send_bytes(user, b"PING"); - assert!(!res.main_failed()); + let msg_id = program.send_bytes(user, b"PING"); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); assert!(res.contains(&(user, b"INIT_PONG"))); // Handle program - let res = program.send_bytes(user, b"PING"); - assert!(!res.main_failed()); + let msg_id = program.send_bytes(user, b"PING"); + let res = system.run_next_block(); + assert!(res.succeed.contains(&msg_id)); assert!(res.contains(&(user, b"HANDLE_PONG"))); } } diff --git a/utils/cargo-gbuild/tests/smoke.rs b/utils/cargo-gbuild/tests/smoke.rs index 679cb89ea11..988c63a3901 100644 --- a/utils/cargo-gbuild/tests/smoke.rs +++ b/utils/cargo-gbuild/tests/smoke.rs @@ -27,13 +27,15 @@ fn ping(sys: &System, prog: PathBuf) -> Program<'_> { let program = Program::from_file(sys, prog); // Init program - let res = program.send_bytes(user, b"PING"); - assert!(!res.main_failed()); + let msg_id = program.send_bytes(user, b"PING"); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); assert!(res.contains(&(user, b"INIT_PONG"))); // Handle program - let res = program.send_bytes(user, b"PING"); - assert!(!res.main_failed()); + let msg_id = program.send_bytes(user, b"PING"); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); assert!(res.contains(&(user, b"HANDLE_PONG"))); program diff --git a/utils/wasm-builder/test-program/src/lib.rs b/utils/wasm-builder/test-program/src/lib.rs index 844cb9d143b..b5d2b50cec5 100644 --- a/utils/wasm-builder/test-program/src/lib.rs +++ b/utils/wasm-builder/test-program/src/lib.rs @@ -36,10 +36,12 @@ mod gtest_tests { let this_program = Program::current(&system); - let res = this_program.send_bytes(123, "INIT"); + this_program.send_bytes(123, "INIT"); + let res = system.run_next_block(); assert!(res.contains(&Log::builder().source(1).dest(123).payload_bytes([]))); - let res = this_program.send_bytes(123, "Hi"); + this_program.send_bytes(123, "Hi"); + let res = system.run_next_block(); assert!(res.contains( &Log::builder() .source(1) From 7d8ac30af5756de89b897c7015cce33f6785948f Mon Sep 17 00:00:00 2001 From: Sabaun Taraki Date: Fri, 9 Aug 2024 12:34:26 +0300 Subject: [PATCH 05/29] Adjust signals test --- examples/signal-entry/src/lib.rs | 49 +++++++++++++++++++------------ examples/signal-entry/src/wasm.rs | 1 + 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/examples/signal-entry/src/lib.rs b/examples/signal-entry/src/lib.rs index f15b4b655f7..23521365640 100644 --- a/examples/signal-entry/src/lib.rs +++ b/examples/signal-entry/src/lib.rs @@ -65,23 +65,34 @@ mod wasm; #[cfg(test)] mod tests { - use gstd::errors::{SignalCode, SimpleExecutionError}; - use gtest::{Program, System}; - -// #[test] -// fn signal_can_be_sent() { -// let system = System::new(); -// system.init_logger(); -// -// let program = Program::current(&system); -// -// todo!(); -// let signal_code: SignalCode = SimpleExecutionError::UserspacePanic.into(); -// program.send_signal(0, signal_code); -// let res = system.run_next_block(); -// -// // Checking signal executed successfully by checking if there are failed messages. -// assert!(res.failed.is_empty()); -// assert!(res.not_executed.is_empty()); -// } + use crate::HandleAction; + use gtest::{Log, Program, System}; + + #[test] + fn signal_can_be_sent() { + let system = System::new(); + system.init_logger(); + + let user_id = 42; + let program = Program::current(&system); + + // Initialize program + program.send_bytes(user_id, b"init_program"); + system.run_next_block(); + + // Make program panic + let msg_id = program.send(user_id, HandleAction::Panic); + let res = system.run_next_block(); + + // Checking signal executed successfully by checking if there are failed messages. + assert_eq!(res.failed.len(), 1); + assert!(res.failed.contains(&msg_id)); + assert!(res.not_executed.is_empty()); + + // Signal sends user message + let log = Log::builder().dest(user_id); + assert!(res.contains(&log)); + let mailbox = system.get_mailbox(user_id); + assert!(mailbox.contains(&log)); + } } diff --git a/examples/signal-entry/src/wasm.rs b/examples/signal-entry/src/wasm.rs index 24b8e0c4051..caa61cb5609 100644 --- a/examples/signal-entry/src/wasm.rs +++ b/examples/signal-entry/src/wasm.rs @@ -247,6 +247,7 @@ extern "C" fn handle_signal() { ) )); + debug!("HERE!"); if let Some(handle_msg) = unsafe { HANDLE_MSG } { assert_eq!(msg::signal_from(), Ok(handle_msg)); } From 17bcfa214cdb6ebed2d5d10043cbdeb04d553f65 Mon Sep 17 00:00:00 2001 From: Sabaun Taraki Date: Fri, 9 Aug 2024 17:55:57 +0300 Subject: [PATCH 06/29] Fix workspace tests --- examples/signal-entry/src/lib.rs | 2 +- examples/signal-entry/src/wasm.rs | 1 - examples/wait_wake/src/lib.rs | 5 ++++- examples/waiter/tests/mx_lock_access.rs | 4 ++-- examples/waiter/tests/rw_lock_access.rs | 5 ++--- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/signal-entry/src/lib.rs b/examples/signal-entry/src/lib.rs index 23521365640..67dfbeb6573 100644 --- a/examples/signal-entry/src/lib.rs +++ b/examples/signal-entry/src/lib.rs @@ -90,7 +90,7 @@ mod tests { assert!(res.not_executed.is_empty()); // Signal sends user message - let log = Log::builder().dest(user_id); + let log = Log::builder().dest(user_id).payload(b"handle_signal"); assert!(res.contains(&log)); let mailbox = system.get_mailbox(user_id); assert!(mailbox.contains(&log)); diff --git a/examples/signal-entry/src/wasm.rs b/examples/signal-entry/src/wasm.rs index caa61cb5609..24b8e0c4051 100644 --- a/examples/signal-entry/src/wasm.rs +++ b/examples/signal-entry/src/wasm.rs @@ -247,7 +247,6 @@ extern "C" fn handle_signal() { ) )); - debug!("HERE!"); if let Some(handle_msg) = unsafe { HANDLE_MSG } { assert_eq!(msg::signal_from(), Ok(handle_msg)); } diff --git a/examples/wait_wake/src/lib.rs b/examples/wait_wake/src/lib.rs index 33673502844..25daabcfb6f 100644 --- a/examples/wait_wake/src/lib.rs +++ b/examples/wait_wake/src/lib.rs @@ -53,7 +53,7 @@ mod tests { let from = 42; - let msg_id = program.send_bytes(from, b"init"); + program.send_bytes(from, b"init"); let res = system.run_next_block(); let log = Log::builder().source(program.id()).dest(from); assert!(res.contains(&log)); @@ -68,6 +68,7 @@ mod tests { let program = Program::current(&system); program.send_bytes(from, b"init"); + system.run_next_block(); let msg_1_echo_wait = 100; let msg_id_1 = program.send(from, Request::EchoWait(msg_1_echo_wait)); @@ -105,9 +106,11 @@ mod tests { let program_1 = Program::current(&system); program_1.send_bytes(from, b"init"); + system.run_next_block(); let program_2 = Program::current(&system); program_2.send_bytes(from, b"init"); + system.run_next_block(); let msg_1_echo_wait = 100; let msg_id_1 = program_1.send(from, Request::EchoWait(msg_1_echo_wait)); diff --git a/examples/waiter/tests/mx_lock_access.rs b/examples/waiter/tests/mx_lock_access.rs index b8d632c758e..f00269db7fe 100644 --- a/examples/waiter/tests/mx_lock_access.rs +++ b/examples/waiter/tests/mx_lock_access.rs @@ -39,8 +39,8 @@ fn access_mx_lock_guard_from_different_msg_fails( program.send(USER_ID, Command::MxLockStaticAccess(lock_access_subcommand)); let lock_access_result = system.run_next_block(); - lock_access_result.assert_panicked_with(format!( - "Mutex guard held by message {lock_msg_id} is being accessed by message {lock_access_msg_id}", + lock_access_result.assert_panicked_with(lock_access_msg_id, format!( + "Mutex guard held by message {lock_msg_id} is being accessed by message {lock_access_msg_id}" )); } diff --git a/examples/waiter/tests/rw_lock_access.rs b/examples/waiter/tests/rw_lock_access.rs index 5a21b3315d1..a17d2f8fd30 100644 --- a/examples/waiter/tests/rw_lock_access.rs +++ b/examples/waiter/tests/rw_lock_access.rs @@ -83,9 +83,8 @@ fn access_rw_lock_guard_from_different_msg_fails( ); let lock_access_result = system.run_next_block(); - lock_access_result.assert_panicked_with(format!( - "{lock_type:?} lock guard held by message {lock_msg_id} is being accessed by message {lock_access_msg_id}", - lock_access_result.sent_message_id(), + lock_access_result.assert_panicked_with(lock_access_msg_id, format!( + "{lock_type:?} lock guard held by message {lock_msg_id} is being accessed by message {lock_access_msg_id}" )); } From aeabdb23c534e43bccbbd79321fdd5ac8d4561fb Mon Sep 17 00:00:00 2001 From: Sabaun Taraki Date: Fri, 9 Aug 2024 18:56:39 +0300 Subject: [PATCH 07/29] Fix test --- gtest/src/program.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/gtest/src/program.rs b/gtest/src/program.rs index e9662367587..5a346971e47 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -888,11 +888,14 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 137, WASM_BINARY); - let run_result = prog.send(user_id, scheme); - assert!(!run_result.main_failed()); - let run_result = prog.send(user_id, *b"Hello"); + let msg_id = prog.send(user_id, scheme); + let res = sys.run_next_block(); + assert!(res.succeed.contains(&msg_id)); + + let msg_id = prog.send(user_id, *b"Hello"); + let res = sys.run_next_block(); - run_result.assert_panicked_with(panic_message); + res.assert_panicked_with(msg_id, panic_message); let log = Log::builder().payload_bytes(message); let value = sys.get_mailbox(user_id).claim_value(log); assert!(value.is_ok()); From 977913ea2f08a36a3f844f75ecd27a11ae51b600 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 5 Aug 2024 02:59:05 +0400 Subject: [PATCH 08/29] feat(gtest): Decrease value after message sent --- gtest/src/gas_tree.rs | 16 +++++- gtest/src/lib.rs | 24 +++++++++ gtest/src/log.rs | 16 +++++- gtest/src/manager.rs | 48 +++++++++++++++-- gtest/src/program.rs | 119 ++++++++++++++++++++++++++++++++---------- 5 files changed, 188 insertions(+), 35 deletions(-) diff --git a/gtest/src/gas_tree.rs b/gtest/src/gas_tree.rs index 8e10d5d7cba..ed117087ed3 100644 --- a/gtest/src/gas_tree.rs +++ b/gtest/src/gas_tree.rs @@ -22,12 +22,17 @@ use crate::GAS_MULTIPLIER; use gear_common::{ auxiliary::gas_provider::{AuxiliaryGasProvider, GasTreeError, PlainNodeId}, gas_provider::{ConsumeResultOf, GasNodeId, Provider, ReservableTree, Tree}, - Gas, Origin, + Gas, GasMultiplier, Origin, }; use gear_core::ids::{MessageId, ProgramId, ReservationId}; pub(crate) type PositiveImbalance = ::PositiveImbalance; pub(crate) type NegativeImbalance = ::NegativeImbalance; +pub type OriginNodeDataOf = ( + ::ExternalOrigin, + GasMultiplier<::Funds, ::Balance>, + ::NodeId, +); type GasTree = ::GasTree; /// Gas tree manager which operates under the hood over @@ -179,4 +184,13 @@ impl GasTreeManager { pub(crate) fn system_reserve(&self, key: MessageId, amount: Gas) -> Result<(), GasTreeError> { GasTree::system_reserve(GasNodeId::from(key.cast::()), amount) } + + /// The id of node, external origin and funds multiplier for a key. + /// + /// Error occurs if the tree is invalidated (has "orphan" nodes), and the + /// node identified by the `key` belongs to a subtree originating at + /// such "orphan" node, or in case of inexistent key. + pub(crate) fn get_origin_node(&self, key: MessageId) -> Result { + GasTree::get_origin_node(GasNodeId::from(key.cast::())) + } } diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index e8b397a761b..06a0e3113ad 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -548,4 +548,28 @@ pub mod constants { pub const MODULE_INSTRUMENTATION_BYTE_COST: Gas = 13; /// Initial random seed for testing environment. pub const INITIAL_RANDOM_SEED: u64 = 42; + + /* Default users constants with initial balance */ + + /// Default user id for Alice. + pub const DEFAULT_USER_ALICE: u64 = u64::MAX - 1; + /// Default user id for Bob. + pub const DEFAULT_USER_BOB: u64 = u64::MAX - 2; + /// Default user id for Charlie. + pub const DEFAULT_USER_CHARLIE: u64 = u64::MAX - 3; + /// Default user id for Eve. + pub const DEFAULT_USER_EVE: u64 = u64::MAX - 4; + + /// Default list of users. + pub const fn default_users_list() -> &'static [u64] { + &[ + DEFAULT_USER_ALICE, + DEFAULT_USER_BOB, + DEFAULT_USER_CHARLIE, + DEFAULT_USER_EVE, + ] + } + + /// Default initial balance for users. + pub const DEFAULT_USERS_INITIAL_BALANCE: Value = EXISTENTIAL_DEPOSIT * 100_000; } diff --git a/gtest/src/log.rs b/gtest/src/log.rs index 29824cfb8e0..cbaafc3b697 100644 --- a/gtest/src/log.rs +++ b/gtest/src/log.rs @@ -16,7 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::program::{Gas, ProgramIdWrapper}; +use crate::{ + manager::Balance, + program::{Gas, ProgramIdWrapper}, + GAS_MULTIPLIER, +}; use codec::{Codec, Encode}; use core_processor::configs::BlockInfo; use gear_core::{ @@ -447,6 +451,16 @@ impl BlockRunResult { ); } + /// Calculate the total spent value. + pub fn spent_value(&self) -> Balance { + let spent_gas = self + .gas_burned + .values() + .fold(Gas::zero(), |acc, &x| acc.saturating_add(x)); + + GAS_MULTIPLIER.gas_to_value(spent_gas.0) + } + /// Trying to get the panic log. fn message_panic_log(&self, message_id: MessageId) -> Option<&CoreLog> { let msg_log = self diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 5288c152a17..e799d874284 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -18,13 +18,14 @@ use crate::{ blocks::BlocksManager, + default_users_list, gas_tree::GasTreeManager, log::{BlockRunResult, CoreLog}, mailbox::MailboxManager, program::{Gas, WasmProgram}, - Result, TestError, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, EXISTENTIAL_DEPOSIT, - GAS_ALLOWANCE, INITIAL_RANDOM_SEED, LOAD_ALLOCATIONS_PER_INTERVAL, MAILBOX_THRESHOLD, - MAX_RESERVATIONS, MODULE_CODE_SECTION_INSTANTIATION_BYTE_COST, + Result, TestError, DEFAULT_USERS_INITIAL_BALANCE, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, + EXISTENTIAL_DEPOSIT, GAS_ALLOWANCE, INITIAL_RANDOM_SEED, LOAD_ALLOCATIONS_PER_INTERVAL, + MAILBOX_THRESHOLD, MAX_RESERVATIONS, MODULE_CODE_SECTION_INSTANTIATION_BYTE_COST, MODULE_DATA_SECTION_INSTANTIATION_BYTE_COST, MODULE_ELEMENT_SECTION_INSTANTIATION_BYTE_COST, MODULE_GLOBAL_SECTION_INSTANTIATION_BYTE_COST, MODULE_INSTRUMENTATION_BYTE_COST, MODULE_INSTRUMENTATION_COST, MODULE_TABLE_SECTION_INSTANTIATION_BYTE_COST, @@ -257,7 +258,7 @@ pub(crate) struct ExtManager { impl ExtManager { #[track_caller] pub(crate) fn new() -> Self { - Self { + let mut manager = Self { msg_nonce: 1, id_nonce: 1, blocks_manager: BlocksManager::new(), @@ -273,6 +274,19 @@ impl ExtManager { 0, ), ..Default::default() + }; + + manager.init_default_users(); + manager + } + + // Should be called once inside `new` method. + fn init_default_users(&mut self) { + for &default_user_id in default_users_list() { + self.actors.insert( + default_user_id.into(), + (TestActor::User, DEFAULT_USERS_INITIAL_BALANCE), + ); } } @@ -638,6 +652,24 @@ impl ExtManager { *balance = balance.saturating_add(value); } + pub(crate) fn burn_from(&mut self, id: &ProgramId, value: Balance) { + let mut actors = self.actors.borrow_mut(); + let (_, balance) = actors.get_mut(id).expect("Can't find existing program"); + + if *balance < value { + panic!( + "Insufficient balance: user ({}) tries to burn \ + ({}) value, while his balance ({})", + id, value, balance + ); + } else { + *balance -= value; + if *balance < crate::EXISTENTIAL_DEPOSIT { + *balance = 0; + } + } + } + pub(crate) fn balance_of(&self, id: &ProgramId) -> Balance { self.actors .borrow() @@ -1023,6 +1055,14 @@ impl JournalHandler for ExtManager { *gas += Gas(amount); }) .or_insert(Gas(amount)); + + let (external, multiplier, _) = self + .gas_tree + .get_origin_node(message_id) + .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); + + let id: ProgramId = external.into_origin().into(); + self.burn_from(&id, multiplier.gas_to_value(amount)); } fn exit_dispatch(&mut self, id_exited: ProgramId, value_destination: ProgramId) { diff --git a/gtest/src/program.rs b/gtest/src/program.rs index 5a346971e47..4041c35e773 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use crate::{ + default_users_list, manager::{Balance, ExtManager, GenuineProgram, MintMode, Program as InnerProgram, TestActor}, system::System, Result, GAS_ALLOWANCE, @@ -398,6 +399,10 @@ impl<'a> Program<'a> { ) -> Self { let program_id = id.clone().into().0; + if default_users_list().contains(&(program_id.into_bytes()[0] as u64)) { + panic!("Can't create program with id {id:?}, because it's reserved for default users") + } + if system .0 .borrow_mut() @@ -857,7 +862,10 @@ pub mod gbuild { mod tests { use super::Program; - use crate::{Log, ProgramIdWrapper, System}; + use crate::{ + manager::Balance, Log, ProgramIdWrapper, System, DEFAULT_USERS_INITIAL_BALANCE, + DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT, + }; use demo_constructor::{Arg, Scheme}; use gear_common::Origin; @@ -870,7 +878,7 @@ mod tests { let sys = System::new(); sys.init_logger(); - let user_id = 42; + let user_id = DEFAULT_USER_ALICE; let message = "Signal handle"; let panic_message = "Gotcha!"; @@ -906,7 +914,7 @@ mod tests { let sys = System::new(); sys.init_logger(); - let user_id = 100; + let user_id = DEFAULT_USER_ALICE; let prog = Program::from_binary_with_id(&sys, 137, demo_futures_unordered::WASM_BINARY); @@ -935,6 +943,7 @@ mod tests { sys.init_logger(); let user_id = 42; + let mut user_spent_balance = 0; sys.mint_to(user_id, 10 * crate::EXISTENTIAL_DEPOSIT); assert_eq!(sys.balance_of(user_id), 10 * crate::EXISTENTIAL_DEPOSIT); @@ -944,14 +953,21 @@ mod tests { assert_eq!(prog.balance(), 2 * crate::EXISTENTIAL_DEPOSIT); prog.send_with_value(user_id, "init".to_string(), crate::EXISTENTIAL_DEPOSIT); - sys.run_next_block(); + user_spent_balance += sys.run_next_block().spent_value(); assert_eq!(prog.balance(), 3 * crate::EXISTENTIAL_DEPOSIT); - assert_eq!(sys.balance_of(user_id), 9 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!( + sys.balance_of(user_id), + 9 * crate::EXISTENTIAL_DEPOSIT - user_spent_balance + ); prog.send_with_value(user_id, "PING".to_string(), 2 * crate::EXISTENTIAL_DEPOSIT); - sys.run_next_block(); + user_spent_balance += sys.run_next_block().spent_value(); + assert_eq!(prog.balance(), 5 * crate::EXISTENTIAL_DEPOSIT); - assert_eq!(sys.balance_of(user_id), 7 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!( + sys.balance_of(user_id), + 7 * crate::EXISTENTIAL_DEPOSIT - user_spent_balance + ); } #[test] @@ -969,22 +985,35 @@ mod tests { sys.mint_to(sender1, 20 * crate::EXISTENTIAL_DEPOSIT); sys.mint_to(sender2, 20 * crate::EXISTENTIAL_DEPOSIT); + // Top-up receiver balance + let mut receiver_expected_balance = 2 * crate::EXISTENTIAL_DEPOSIT; + sys.mint_to(receiver, receiver_expected_balance); + let prog = Program::from_binary_with_id(&sys, 137, demo_piggy_bank::WASM_BINARY); prog.send_bytes(receiver, b"init"); - sys.run_next_block(); + receiver_expected_balance -= sys.run_next_block().spent_value(); assert_eq!(prog.balance(), 0); // Send values to the program prog.send_bytes_with_value(sender0, b"insert", 2 * crate::EXISTENTIAL_DEPOSIT); - sys.run_next_block(); - assert_eq!(sys.balance_of(sender0), 18 * crate::EXISTENTIAL_DEPOSIT); + let sender0_spent_value = sys.run_next_block().spent_value(); + assert_eq!( + sys.balance_of(sender0), + 18 * crate::EXISTENTIAL_DEPOSIT - sender0_spent_value + ); prog.send_bytes_with_value(sender1, b"insert", 4 * crate::EXISTENTIAL_DEPOSIT); - sys.run_next_block(); - assert_eq!(sys.balance_of(sender1), 16 * crate::EXISTENTIAL_DEPOSIT); + let sender1_spent_value = sys.run_next_block().spent_value(); + assert_eq!( + sys.balance_of(sender1), + 16 * crate::EXISTENTIAL_DEPOSIT - sender1_spent_value + ); prog.send_bytes_with_value(sender2, b"insert", 6 * crate::EXISTENTIAL_DEPOSIT); - sys.run_next_block(); - assert_eq!(sys.balance_of(sender2), 14 * crate::EXISTENTIAL_DEPOSIT); + let sender2_spent_value = sys.run_next_block().spent_value(); + assert_eq!( + sys.balance_of(sender2), + 14 * crate::EXISTENTIAL_DEPOSIT - sender2_spent_value + ); // Check program's balance assert_eq!(prog.balance(), (2 + 4 + 6) * crate::EXISTENTIAL_DEPOSIT); @@ -992,6 +1021,7 @@ mod tests { // Request to smash the piggy bank and send the value to the receiver address prog.send_bytes(receiver, b"smash"); let res = sys.run_next_block(); + receiver_expected_balance -= res.spent_value(); let reply_to_id = { let log = res.log(); // 1 auto reply and 1 message from program @@ -1013,7 +1043,7 @@ mod tests { .is_ok()); assert_eq!( sys.balance_of(receiver), - (2 + 4 + 6) * crate::EXISTENTIAL_DEPOSIT + (2 + 4 + 6) * crate::EXISTENTIAL_DEPOSIT + receiver_expected_balance ); // Check program's balance is empty @@ -1030,13 +1060,13 @@ mod tests { #[test] #[should_panic(expected = "Insufficient value: user \ - (0x0100000000000000000000000000000000000000000000000000000000000000) tries \ + (0x0500000000000000000000000000000000000000000000000000000000000000) tries \ to send (1000000000001) value, while his balance (1000000000000)")] fn fails_on_insufficient_balance() { let sys = System::new(); - let user = 1; - let prog = Program::from_binary_with_id(&sys, 2, demo_piggy_bank::WASM_BINARY); + let user = 5; + let prog = Program::from_binary_with_id(&sys, 6, demo_piggy_bank::WASM_BINARY); assert_eq!(sys.balance_of(user), 0); sys.mint_to(user, crate::EXISTENTIAL_DEPOSIT); @@ -1051,36 +1081,44 @@ mod tests { let sys = System::new(); sys.init_logger(); + const RECEIVER_INITIAL_BALANCE: Balance = 2 * crate::EXISTENTIAL_DEPOSIT; + let sender = 42; let receiver = 84; + let mut receiver_expected_balance = RECEIVER_INITIAL_BALANCE; sys.mint_to(sender, 20 * crate::EXISTENTIAL_DEPOSIT); + sys.mint_to(receiver, RECEIVER_INITIAL_BALANCE); let prog = Program::from_binary_with_id(&sys, 137, demo_piggy_bank::WASM_BINARY); prog.send_bytes(receiver, b"init"); - sys.run_next_block(); + receiver_expected_balance -= sys.run_next_block().spent_value(); // Get zero value to the receiver's mailbox prog.send_bytes(receiver, b"smash"); - sys.run_next_block(); + receiver_expected_balance -= sys.run_next_block().spent_value(); let receiver_mailbox = sys.get_mailbox(receiver); assert!(receiver_mailbox .claim_value(Log::builder().dest(receiver).payload_bytes(b"send")) .is_ok()); - assert_eq!(sys.balance_of(receiver), 0); + assert_eq!(sys.balance_of(receiver), receiver_expected_balance); // Get the value > ED to the receiver's mailbox prog.send_bytes_with_value(sender, b"insert", 2 * crate::EXISTENTIAL_DEPOSIT); - prog.send_bytes(receiver, b"smash"); sys.run_next_block(); + prog.send_bytes(receiver, b"smash"); + receiver_expected_balance -= sys.run_next_block().spent_value(); // Check receiver's balance assert!(receiver_mailbox .claim_value(Log::builder().dest(receiver).payload_bytes(b"send")) .is_ok()); - assert_eq!(sys.balance_of(receiver), 2 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!( + sys.balance_of(receiver), + 2 * crate::EXISTENTIAL_DEPOSIT + receiver_expected_balance + ); } struct CleanupFolderOnDrop { @@ -1102,7 +1140,7 @@ mod tests { let mut prog = Program::from_binary_with_id(&sys, 420, WASM_BINARY); - let signer = 42; + let signer = DEFAULT_USER_ALICE; let signer_mailbox = sys.get_mailbox(signer); // Init capacitor with limit = 15 @@ -1156,7 +1194,7 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 420, WASM_BINARY); - let signer = 42; + let signer = DEFAULT_USER_ALICE; // Init simple waiter prog.send(signer, InitMessage::SimpleWaiter); @@ -1188,7 +1226,7 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 420, WASM_BINARY); - let signer = 42; + let signer = DEFAULT_USER_ALICE; // Init reserver prog.send(signer, InitMessage::Reserver); @@ -1214,16 +1252,21 @@ mod tests { let sys = System::new(); sys.init_logger(); - let user_id = [42; 32]; + let user_id = 42; + sys.mint_to(user_id, EXISTENTIAL_DEPOSIT); + let prog = Program::from_binary_with_id(&sys, 137, WASM_BINARY); let msg_id = prog.send(user_id, demo_exit_handle::scheme()); let result = sys.run_next_block(); assert!(result.succeed.contains(&msg_id)); + assert_eq!(sys.balance_of(user_id), 0); + sys.mint_to(user_id, EXISTENTIAL_DEPOSIT); let msg_id = prog.send_bytes(user_id, []); let result = sys.run_next_block(); assert!(result.succeed.contains(&msg_id)); + assert_eq!(sys.balance_of(user_id), 0); } #[test] @@ -1258,7 +1301,7 @@ mod tests { let sys = System::new(); sys.init_logger(); - let user_id = 42; + let user_id = DEFAULT_USER_ALICE; let prog = Program::from_binary_with_id(&sys, 4242, WASM_BINARY); // Initialize program @@ -1307,7 +1350,7 @@ mod tests { let sys = System::new(); sys.init_logger(); - let user_id = 42; + let user_id = DEFAULT_USER_ALICE; let prog_id = 4242; let prog = Program::from_binary_with_id(&sys, prog_id, WASM_BINARY); @@ -1355,4 +1398,22 @@ mod tests { assert!(res.succeed.contains(&msg_id)); assert!(mailbox.contains(&Log::builder().payload_bytes(payload).source(new_prog_id))); } + + #[test] + fn test_send_message_decreases_user_balance() { + use demo_constructor::WASM_BINARY; + + let sys = System::new(); + sys.init_logger(); + + let user_id = DEFAULT_USER_ALICE; + + let prog = Program::from_binary_with_id(&sys, 1337, WASM_BINARY); + + prog.send(user_id, Scheme::empty()); + let block_result = sys.run_next_block(); + let expected_value = DEFAULT_USERS_INITIAL_BALANCE - block_result.spent_value(); + + assert_eq!(sys.balance_of(user_id), expected_value); + } } From d42557b76dc39ac859b7d8b2e7316cff603955cc Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Sun, 11 Aug 2024 07:34:45 +0400 Subject: [PATCH 09/29] WIP --- gtest/src/balance.rs | 162 +++++++++++++++++++++++++++++++++++++ gtest/src/lib.rs | 3 +- gtest/src/log.rs | 3 +- gtest/src/manager.rs | 185 +++++++++++++++++++++++++++++-------------- gtest/src/program.rs | 10 +-- gtest/src/system.rs | 6 +- 6 files changed, 300 insertions(+), 69 deletions(-) create mode 100644 gtest/src/balance.rs diff --git a/gtest/src/balance.rs b/gtest/src/balance.rs new file mode 100644 index 00000000000..390287e8cf6 --- /dev/null +++ b/gtest/src/balance.rs @@ -0,0 +1,162 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Balance management. + +use std::collections::HashMap; + +use crate::{manager::Actors, GAS_MULTIPLIER}; +use gear_common::{Gas, GasMultiplier, ProgramId}; +use gear_core::message::Value; + +#[derive(Debug, Clone, Default)] +pub struct Balance { + total: u128, + // Primary used for ED locking + locked: u128, +} + +impl Balance { + pub fn new(total: u128) -> Self { + Self { total, locked: 0 } + } + + #[track_caller] + pub fn set_lock(&mut self, value: Value) { + self.locked = value; + } + + pub fn empty() -> Self { + Self { + total: 0, + locked: 0, + } + } + + pub fn available(&self) -> u128 { + self.total - self.locked + } + + pub fn total(&self) -> u128 { + self.total + } + + #[track_caller] + pub fn decrease(&mut self, value: u128, _keep_alive: bool) { + //if self.total < value { + // unreachable!("Actor {:?} balance is less then sent value", from); + //} + + self.total -= value; + } + + pub fn increase(&mut self, value: u128) { + self.total += value; + } + + pub(crate) fn transfer( + actors: &mut Actors, + from: ProgramId, + to: ProgramId, + value: u128, + keep_alive: bool, + ) { + let mut actors = actors.borrow_mut(); + let (_, from) = actors.get_mut(&from).expect("Actor should exist"); + from.decrease(value, keep_alive); + let (_, to) = actors.get_mut(&to).expect("Actor should exist"); + to.increase(value); + } +} + +impl PartialEq for Balance { + fn eq(&self, other: &u128) -> bool { + self.total == *other + } +} + +#[derive(Default, Debug)] +struct AccountBalance { + gas: Value, + value: Value, +} + +#[derive(Default, Debug)] +pub struct Bank { + accounts: HashMap, +} + +impl Bank { + #[track_caller] + pub fn deposit_value( + &mut self, + from: &mut Balance, + to: ProgramId, + value: Value, + keep_alive: bool, + ) { + from.decrease(value, keep_alive); + self.accounts + .entry(to) + .or_insert(AccountBalance { gas: 0, value: 0 }) + .value += value; + } + + #[track_caller] + pub fn deposit_gas(&mut self, from: &mut Balance, to: ProgramId, gas: Gas, keep_alive: bool) { + let gas_value = GAS_MULTIPLIER.gas_to_value(gas); + from.decrease(gas_value, keep_alive); + self.accounts + .entry(to) + .or_insert(AccountBalance { gas: 0, value: 0 }) + .gas += gas_value; + } + + pub fn spend_gas(&mut self, from: ProgramId, gas: Gas, multiplier: GasMultiplier) { + let gas_value = multiplier.gas_to_value(gas); + self.accounts.get_mut(&from).expect("must exist").gas -= gas_value; + } + + pub fn spend_gas_to( + &mut self, + from: ProgramId, + to: &mut Balance, + gas: Gas, + multiplier: GasMultiplier, + ) { + self.withdraw_gas(from, to, gas, multiplier) + } + + pub fn withdraw_gas( + &mut self, + from: ProgramId, + to: &mut Balance, + gas_left: Gas, + multiplier: GasMultiplier, + ) { + let gas_left_value = multiplier.gas_to_value(gas_left); + self.accounts.get_mut(&from).expect("must exist").gas -= gas_left_value; + let value = multiplier.gas_to_value(gas_left); + to.increase(value); + } + + pub fn transfer_value(&mut self, from: ProgramId, to: &mut Balance, value: Value) { + self.accounts.get_mut(&from).expect("must exist").value -= value; + to.increase(value); + } +} diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index 06a0e3113ad..111d079d8b7 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -429,10 +429,11 @@ //! // without any arguments. //! ``` //! --> -#![deny(missing_docs)] +//#![deny(missing_docs)] #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] #![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] +pub mod balance; mod blocks; mod error; mod gas_tree; diff --git a/gtest/src/log.rs b/gtest/src/log.rs index cbaafc3b697..65e9310ab53 100644 --- a/gtest/src/log.rs +++ b/gtest/src/log.rs @@ -17,7 +17,6 @@ // along with this program. If not, see . use crate::{ - manager::Balance, program::{Gas, ProgramIdWrapper}, GAS_MULTIPLIER, }; @@ -452,7 +451,7 @@ impl BlockRunResult { } /// Calculate the total spent value. - pub fn spent_value(&self) -> Balance { + pub fn spent_value(&self) -> u128 { let spent_gas = self .gas_burned .values() diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index e799d874284..2762f2b9e21 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use crate::{ + balance::Bank, blocks::BlocksManager, default_users_list, gas_tree::GasTreeManager, @@ -39,7 +40,7 @@ use core_processor::{ }, ContextChargedForCode, ContextChargedForInstrumentation, Ext, }; -use gear_common::{auxiliary::mailbox::MailboxErrorImpl, Origin}; +use gear_common::{auxiliary::mailbox::MailboxErrorImpl, gas_provider::Imbalance as _, Origin}; use gear_core::{ code::{Code, CodeAndId, InstrumentedCode, InstrumentedCodeAndId, TryNewCodeConfig}, ids::{prelude::*, CodeId, MessageId, ProgramId, ReservationId}, @@ -67,10 +68,12 @@ use std::{ rc::Rc, }; +pub use crate::balance::Balance; + const OUTGOING_LIMIT: u32 = 1024; const OUTGOING_BYTES_LIMIT: u32 = 64 * 1024 * 1024; -pub(crate) type Balance = u128; +pub(crate) type Value = u128; #[derive(Debug)] pub(crate) enum TestActor { @@ -236,6 +239,7 @@ pub(crate) struct ExtManager { // State pub(crate) actors: Actors, + pub(crate) bank: Bank, pub(crate) opt_binaries: BTreeMap>, pub(crate) meta_binaries: BTreeMap>, pub(crate) dispatches: VecDeque, @@ -285,7 +289,7 @@ impl ExtManager { for &default_user_id in default_users_list() { self.actors.insert( default_user_id.into(), - (TestActor::User, DEFAULT_USERS_INITIAL_BALANCE), + (TestActor::User, Balance::new(DEFAULT_USERS_INITIAL_BALANCE)), ); } } @@ -299,8 +303,10 @@ impl ExtManager { if let Program::Genuine(GenuineProgram { code, .. }) = &program { self.store_new_code(code.code().to_vec()); } - self.actors - .insert(program_id, (TestActor::new(init_message_id, program), 0)) + self.actors.insert( + program_id, + (TestActor::new(init_message_id, program), Balance::default()), + ) } pub(crate) fn store_new_code(&mut self, code: Vec) -> CodeId { @@ -480,7 +486,7 @@ impl ExtManager { let (actor, balance) = actors .get_mut(&dispatch.destination()) .expect("Somehow message queue contains message for user"); - let balance = *balance; + let balance = balance.available(); if actor.is_dormant() { drop(actors); @@ -510,22 +516,39 @@ impl ExtManager { let mut actors = self.actors.borrow_mut(); let (_, balance) = actors .entry(dispatch.source()) - .or_insert((TestActor::User, 0)); + .or_insert((TestActor::User, Balance::default())); + let keep_alive_sender = false; - if *balance < dispatch.value() { + // TODO: move panic into bank + if balance.available() < dispatch.value() { panic!( "Insufficient value: user ({}) tries to send \ - ({}) value, while his balance ({})", + ({}) value, while his balance ({:?})", dispatch.source(), dispatch.value(), balance ); } else { - *balance -= dispatch.value(); - if *balance < crate::EXISTENTIAL_DEPOSIT { - *balance = 0; - } + // Deposit message value + self.bank.deposit_value( + balance, + dispatch.destination(), + dispatch.value(), + keep_alive_sender, + ); } + + let gas_limit = dispatch + .gas_limit() + .unwrap_or_else(|| unreachable!("message from program API always has gas")); + + // Deposit gas + self.bank.deposit_gas( + balance, + dispatch.destination(), + gas_limit, + keep_alive_sender, + ); } #[track_caller] @@ -638,7 +661,7 @@ impl ExtManager { ) } - pub(crate) fn mint_to(&mut self, id: &ProgramId, value: Balance, mint_mode: MintMode) { + pub(crate) fn mint_to(&mut self, id: &ProgramId, value: Value, mint_mode: MintMode) { if mint_mode == MintMode::KeepAlive && value < crate::EXISTENTIAL_DEPOSIT { panic!( "An attempt to mint value ({}) less than existential deposit ({})", @@ -648,34 +671,34 @@ impl ExtManager { } let mut actors = self.actors.borrow_mut(); - let (_, balance) = actors.entry(*id).or_insert((TestActor::User, 0)); - *balance = balance.saturating_add(value); + let (_, balance) = actors + .entry(*id) + .or_insert((TestActor::User, Balance::default())); + balance.increase(value); } - pub(crate) fn burn_from(&mut self, id: &ProgramId, value: Balance) { + pub(crate) fn burn_from(&mut self, id: &ProgramId, value: Value) { let mut actors = self.actors.borrow_mut(); let (_, balance) = actors.get_mut(id).expect("Can't find existing program"); - if *balance < value { + if balance.total() < value { panic!( "Insufficient balance: user ({}) tries to burn \ - ({}) value, while his balance ({})", + ({}) value, while his balance ({:?})", id, value, balance ); } else { - *balance -= value; - if *balance < crate::EXISTENTIAL_DEPOSIT { - *balance = 0; - } + balance.decrease(value, false); } } - pub(crate) fn balance_of(&self, id: &ProgramId) -> Balance { + pub(crate) fn balance_of(&self, id: &ProgramId) -> Value { self.actors .borrow() .get(id) - .map(|(_, balance)| *balance) + .map(|(_, balance)| balance.clone()) .unwrap_or_default() + .total() } pub(crate) fn claim_value_from_mailbox( @@ -696,7 +719,7 @@ impl ExtManager { } #[track_caller] - pub(crate) fn override_balance(&mut self, id: &ProgramId, balance: Balance) { + pub(crate) fn override_balance(&mut self, id: &ProgramId, balance: Value) { if self.is_user(id) && balance < crate::EXISTENTIAL_DEPOSIT { panic!( "An attempt to override balance with value ({}) less than existential deposit ({})", @@ -706,8 +729,10 @@ impl ExtManager { } let mut actors = self.actors.borrow_mut(); - let (_, actor_balance) = actors.entry(*id).or_insert((TestActor::User, 0)); - *actor_balance = balance; + let (_, actor_balance) = actors + .entry(*id) + .or_insert((TestActor::User, Balance::default())); + *actor_balance = Balance::new(balance); } #[track_caller] @@ -741,12 +766,17 @@ impl ExtManager { } #[track_caller] - fn init_failure(&mut self, program_id: ProgramId) { + fn init_failure(&mut self, program_id: ProgramId, origin: ProgramId) { + let total_value = { + let actors = self.actors.borrow_mut(); + actors.get(&program_id).expect("Can't fail").1.total() + }; + Balance::transfer(&mut self.actors, program_id, origin, total_value, false); + let mut actors = self.actors.borrow_mut(); let (actor, _) = actors .get_mut(&program_id) .expect("Can't find existing program"); - *actor = TestActor::Dormant; } @@ -1032,8 +1062,10 @@ impl JournalHandler for ExtManager { DispatchOutcome::Success | DispatchOutcome::Exit { .. } => { self.succeed.insert(message_id); } - DispatchOutcome::InitFailure { program_id, .. } => { - self.init_failure(program_id); + DispatchOutcome::InitFailure { + program_id, origin, .. + } => { + self.init_failure(program_id, origin); self.failed.insert(message_id); } DispatchOutcome::InitSuccess { program_id, .. } => { @@ -1062,19 +1094,48 @@ impl JournalHandler for ExtManager { .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); let id: ProgramId = external.into_origin().into(); - self.burn_from(&id, multiplier.gas_to_value(amount)); + self.bank.spend_gas(id, amount, multiplier); } fn exit_dispatch(&mut self, id_exited: ProgramId, value_destination: ProgramId) { - if let Some((_, balance)) = self.actors.remove(&id_exited) { - self.mint_to(&value_destination, balance, MintMode::AllowDeath); - } + let total_value = self + .actors + .borrow_mut() + .get(&id_exited) + .expect("Can't fail") + .1 + .total(); + Balance::transfer( + &mut self.actors, + id_exited, + value_destination, + total_value, + false, + ); + + self.actors.remove(&id_exited); } fn message_consumed(&mut self, message_id: MessageId) { - self.gas_tree + let outcome = self + .gas_tree .consume(message_id) .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); + + // Retrieve gas + if let Some((imbalance, multiplier, external)) = outcome { + // Peeking numeric value from negative imbalance. + let gas_left = imbalance.peek(); + + let id: ProgramId = external.into_origin().into(); + let mut actors = self.actors.borrow_mut(); + let (_, to) = actors.get_mut(&id).expect("Can't fail"); + + // Unreserving funds, if left non-zero amount of gas. + if gas_left != 0 { + self.bank.withdraw_gas(id, to, gas_left, multiplier); + } + } } fn send_dispatch( @@ -1095,6 +1156,14 @@ impl JournalHandler for ExtManager { let source = dispatch.source(); + if dispatch.value() != 0 { + let mut actors = self.actors.borrow_mut(); + let (_, balance) = actors.get_mut(&source).expect("Can't fail"); + + self.bank + .deposit_value(balance, source, dispatch.value(), false); + } + if self.is_program(&dispatch.destination()) { match (dispatch.gas_limit(), reservation) { (Some(gas_limit), None) => self @@ -1208,31 +1277,17 @@ impl JournalHandler for ExtManager { } #[track_caller] - fn send_value(&mut self, from: ProgramId, to: Option, value: Balance) { + fn send_value(&mut self, from: ProgramId, to: Option, value: Value) { if value == 0 { // Nothing to do return; } - if let Some(ref to) = to { - if self.is_program(&from) { - let mut actors = self.actors.borrow_mut(); - let (_, balance) = actors.get_mut(&from).expect("Can't fail"); - if *balance < value { - unreachable!("Actor {:?} balance is less then sent value", from); - } - - *balance -= value; - - if *balance < crate::EXISTENTIAL_DEPOSIT { - *balance = 0; - } - } + let to = to.unwrap_or(from); + let mut actors = self.actors.borrow_mut(); + let (_, to) = actors.get_mut(&to).expect("Can't fail"); - self.mint_to(to, value, MintMode::KeepAlive); - } else { - self.mint_to(&from, value, MintMode::KeepAlive); - } + self.bank.transfer_value(from, to, value); } #[track_caller] @@ -1272,7 +1327,21 @@ impl JournalHandler for ExtManager { Some(init_message_id), ); // Transfer the ED from the program-creator to the new program - self.send_value(program_id, Some(candidate_id), crate::EXISTENTIAL_DEPOSIT); + Balance::transfer( + &mut self.actors, + program_id, + candidate_id, + crate::EXISTENTIAL_DEPOSIT, + false, + ); + + // Set ED lock + let mut actors = self.actors.borrow_mut(); + actors + .get_mut(&candidate_id) + .expect("Can't fail") + .1 + .set_lock(EXISTENTIAL_DEPOSIT); } else { log::debug!("Program with id {candidate_id:?} already exists"); } @@ -1281,7 +1350,7 @@ impl JournalHandler for ExtManager { log::debug!("No referencing code with code hash {code_id:?} for candidate programs"); for (_, invalid_candidate_id) in candidates { self.actors - .insert(invalid_candidate_id, (TestActor::Dormant, 0)); + .insert(invalid_candidate_id, (TestActor::Dormant, Balance::empty())); } } } diff --git a/gtest/src/program.rs b/gtest/src/program.rs index 4041c35e773..eeca8b30947 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -18,7 +18,7 @@ use crate::{ default_users_list, - manager::{Balance, ExtManager, GenuineProgram, MintMode, Program as InnerProgram, TestActor}, + manager::{ExtManager, GenuineProgram, MintMode, Program as InnerProgram, TestActor, Value}, system::System, Result, GAS_ALLOWANCE, }; @@ -732,14 +732,14 @@ impl<'a> Program<'a> { } /// Mint balance to the account. - pub fn mint(&mut self, value: Balance) { + pub fn mint(&mut self, value: Value) { self.manager .borrow_mut() .mint_to(&self.id(), value, MintMode::KeepAlive) } /// Returns the balance of the account. - pub fn balance(&self) -> Balance { + pub fn balance(&self) -> Value { self.manager.borrow().balance_of(&self.id()) } @@ -863,7 +863,7 @@ mod tests { use super::Program; use crate::{ - manager::Balance, Log, ProgramIdWrapper, System, DEFAULT_USERS_INITIAL_BALANCE, + manager::Value, Log, ProgramIdWrapper, System, DEFAULT_USERS_INITIAL_BALANCE, DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT, }; use demo_constructor::{Arg, Scheme}; @@ -1081,7 +1081,7 @@ mod tests { let sys = System::new(); sys.init_logger(); - const RECEIVER_INITIAL_BALANCE: Balance = 2 * crate::EXISTENTIAL_DEPOSIT; + const RECEIVER_INITIAL_BALANCE: Value = 2 * crate::EXISTENTIAL_DEPOSIT; let sender = 42; let receiver = 84; diff --git a/gtest/src/system.rs b/gtest/src/system.rs index eb3b7c679a0..91bacda5070 100644 --- a/gtest/src/system.rs +++ b/gtest/src/system.rs @@ -19,7 +19,7 @@ use crate::{ log::{BlockRunResult, CoreLog}, mailbox::ActorMailbox, - manager::{Actors, Balance, ExtManager, MintMode}, + manager::{Actors, ExtManager, MintMode, Value}, program::{Program, ProgramIdWrapper}, Gas, GAS_ALLOWANCE, }; @@ -376,7 +376,7 @@ impl System { } /// Mint balance to user with given `id` and `value`. - pub fn mint_to>(&self, id: ID, value: Balance) { + pub fn mint_to>(&self, id: ID, value: Value) { let actor_id = id.into().0; self.0 .borrow_mut() @@ -384,7 +384,7 @@ impl System { } /// Returns balance of user with given `id`. - pub fn balance_of>(&self, id: ID) -> Balance { + pub fn balance_of>(&self, id: ID) -> Value { let actor_id = id.into().0; self.0.borrow().balance_of(&actor_id) } From aad49be6f7e0fc891673f3f972affb0eca5e3f7e Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 12 Aug 2024 01:01:12 +0400 Subject: [PATCH 10/29] Update tests --- gtest/src/balance.rs | 3 ++- gtest/src/manager.rs | 54 +++++++++++++++++++++----------------------- gtest/src/program.rs | 37 +++++++++++++++++++----------- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/gtest/src/balance.rs b/gtest/src/balance.rs index 390287e8cf6..a7f69049862 100644 --- a/gtest/src/balance.rs +++ b/gtest/src/balance.rs @@ -20,7 +20,7 @@ use std::collections::HashMap; -use crate::{manager::Actors, GAS_MULTIPLIER}; +use crate::{manager::Actors, DEFAULT_USER_ALICE, GAS_MULTIPLIER}; use gear_common::{Gas, GasMultiplier, ProgramId}; use gear_core::message::Value; @@ -127,6 +127,7 @@ impl Bank { .gas += gas_value; } + #[track_caller] pub fn spend_gas(&mut self, from: ProgramId, gas: Gas, multiplier: GasMultiplier) { let gas_value = multiplier.gas_to_value(gas); self.accounts.get_mut(&from).expect("must exist").gas -= gas_value; diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 2762f2b9e21..586a9533e97 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -25,13 +25,14 @@ use crate::{ mailbox::MailboxManager, program::{Gas, WasmProgram}, Result, TestError, DEFAULT_USERS_INITIAL_BALANCE, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, - EXISTENTIAL_DEPOSIT, GAS_ALLOWANCE, INITIAL_RANDOM_SEED, LOAD_ALLOCATIONS_PER_INTERVAL, - MAILBOX_THRESHOLD, MAX_RESERVATIONS, MODULE_CODE_SECTION_INSTANTIATION_BYTE_COST, - MODULE_DATA_SECTION_INSTANTIATION_BYTE_COST, MODULE_ELEMENT_SECTION_INSTANTIATION_BYTE_COST, - MODULE_GLOBAL_SECTION_INSTANTIATION_BYTE_COST, MODULE_INSTRUMENTATION_BYTE_COST, - MODULE_INSTRUMENTATION_COST, MODULE_TABLE_SECTION_INSTANTIATION_BYTE_COST, - MODULE_TYPE_SECTION_INSTANTIATION_BYTE_COST, READ_COST, READ_PER_BYTE_COST, RESERVATION_COST, - RESERVE_FOR, VALUE_PER_GAS, WAITLIST_COST, WRITE_COST, + EXISTENTIAL_DEPOSIT, GAS_ALLOWANCE, GAS_MULTIPLIER, INITIAL_RANDOM_SEED, + LOAD_ALLOCATIONS_PER_INTERVAL, MAILBOX_THRESHOLD, MAX_RESERVATIONS, + MODULE_CODE_SECTION_INSTANTIATION_BYTE_COST, MODULE_DATA_SECTION_INSTANTIATION_BYTE_COST, + MODULE_ELEMENT_SECTION_INSTANTIATION_BYTE_COST, MODULE_GLOBAL_SECTION_INSTANTIATION_BYTE_COST, + MODULE_INSTRUMENTATION_BYTE_COST, MODULE_INSTRUMENTATION_COST, + MODULE_TABLE_SECTION_INSTANTIATION_BYTE_COST, MODULE_TYPE_SECTION_INSTANTIATION_BYTE_COST, + READ_COST, READ_PER_BYTE_COST, RESERVATION_COST, RESERVE_FOR, VALUE_PER_GAS, WAITLIST_COST, + WRITE_COST, }; use core_processor::{ common::*, @@ -519,36 +520,33 @@ impl ExtManager { .or_insert((TestActor::User, Balance::default())); let keep_alive_sender = false; - // TODO: move panic into bank - if balance.available() < dispatch.value() { + let gas_limit = dispatch + .gas_limit() + .unwrap_or_else(|| unreachable!("message from program API always has gas")); + + // Check sender has enough balance to support dispatch + if balance.total() < { dispatch.value() + GAS_MULTIPLIER.gas_to_value(gas_limit) } { panic!( - "Insufficient value: user ({}) tries to send \ - ({}) value, while his balance ({:?})", + "Insufficient balance: user ({}) tries to send \ + ({}) value and ({}) gas, while his balance ({:?})", dispatch.source(), dispatch.value(), - balance - ); - } else { - // Deposit message value - self.bank.deposit_value( - balance, - dispatch.destination(), - dispatch.value(), - keep_alive_sender, + gas_limit, + balance.total(), ); } - let gas_limit = dispatch - .gas_limit() - .unwrap_or_else(|| unreachable!("message from program API always has gas")); - - // Deposit gas - self.bank.deposit_gas( + // Deposit message value + self.bank.deposit_value( balance, - dispatch.destination(), - gas_limit, + dispatch.source(), + dispatch.value(), keep_alive_sender, ); + + // Deposit gas + self.bank + .deposit_gas(balance, dispatch.source(), gas_limit, keep_alive_sender); } #[track_caller] diff --git a/gtest/src/program.rs b/gtest/src/program.rs index eeca8b30947..e05fac77d4f 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -991,7 +991,7 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 137, demo_piggy_bank::WASM_BINARY); - prog.send_bytes(receiver, b"init"); + prog.send_with_gas(receiver, b"init", 1_000_000, 0); receiver_expected_balance -= sys.run_next_block().spent_value(); assert_eq!(prog.balance(), 0); @@ -1019,7 +1019,7 @@ mod tests { assert_eq!(prog.balance(), (2 + 4 + 6) * crate::EXISTENTIAL_DEPOSIT); // Request to smash the piggy bank and send the value to the receiver address - prog.send_bytes(receiver, b"smash"); + prog.send_bytes_with_gas(receiver, b"smash", 100_000_000, 0); let res = sys.run_next_block(); receiver_expected_balance -= res.spent_value(); let reply_to_id = { @@ -1059,9 +1059,10 @@ mod tests { } #[test] - #[should_panic(expected = "Insufficient value: user \ - (0x0500000000000000000000000000000000000000000000000000000000000000) tries \ - to send (1000000000001) value, while his balance (1000000000000)")] + #[should_panic( + expected = "Insufficient balance: user (0x0500000000000000000000000000000000000000000000000000000000000000) \ + tries to send (1000000000001) value and (750000000000) gas, while his balance (1000000000000)" + )] fn fails_on_insufficient_balance() { let sys = System::new(); @@ -1081,7 +1082,7 @@ mod tests { let sys = System::new(); sys.init_logger(); - const RECEIVER_INITIAL_BALANCE: Value = 2 * crate::EXISTENTIAL_DEPOSIT; + const RECEIVER_INITIAL_BALANCE: Value = 10 * crate::EXISTENTIAL_DEPOSIT; let sender = 42; let receiver = 84; @@ -1253,20 +1254,28 @@ mod tests { sys.init_logger(); let user_id = 42; - sys.mint_to(user_id, EXISTENTIAL_DEPOSIT); + let mut user_balance = EXISTENTIAL_DEPOSIT; + sys.mint_to(user_id, user_balance); - let prog = Program::from_binary_with_id(&sys, 137, WASM_BINARY); + let prog_id = 137; + assert_eq!(sys.balance_of(prog_id), 0); + let prog = Program::from_binary_with_id(&sys, prog_id, WASM_BINARY); - let msg_id = prog.send(user_id, demo_exit_handle::scheme()); + let msg_id = prog.send_with_gas(user_id, demo_exit_handle::scheme(), 1_000_000_000, 0); let result = sys.run_next_block(); + user_balance -= result.spent_value(); + assert!(result.succeed.contains(&msg_id)); - assert_eq!(sys.balance_of(user_id), 0); + assert_eq!(sys.balance_of(prog_id), 0); + assert_eq!(sys.balance_of(user_id), user_balance); - sys.mint_to(user_id, EXISTENTIAL_DEPOSIT); - let msg_id = prog.send_bytes(user_id, []); + let msg_id = prog.send_bytes_with_gas(user_id, [], 1_000_000_000, 0); let result = sys.run_next_block(); + user_balance -= result.spent_value(); + assert!(result.succeed.contains(&msg_id)); - assert_eq!(sys.balance_of(user_id), 0); + assert_eq!(sys.balance_of(prog_id), 0); + assert_eq!(sys.balance_of(user_id), user_balance); } #[test] @@ -1277,6 +1286,7 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 137, demo_ping::WASM_BINARY); let user_id = ActorId::zero(); + sys.mint_to(user_id, EXISTENTIAL_DEPOSIT + 1); // set insufficient gas for execution let msg_id = prog.send_with_gas(user_id, "init".to_string(), 1, 0); @@ -1407,6 +1417,7 @@ mod tests { sys.init_logger(); let user_id = DEFAULT_USER_ALICE; + let _ = sys.balance_of(user_id); let prog = Program::from_binary_with_id(&sys, 1337, WASM_BINARY); From 2b529705d9439c415ab0248cf6cd3bbb4c5daff2 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 12 Aug 2024 04:06:22 +0400 Subject: [PATCH 11/29] Update test, fix ED charging --- gtest/src/balance.rs | 34 ++++++++++-- gtest/src/manager.rs | 103 ++++++++++++++++++++++++++++--------- gtest/src/program.rs | 120 +++++++++++++++++++++++++++---------------- 3 files changed, 183 insertions(+), 74 deletions(-) diff --git a/gtest/src/balance.rs b/gtest/src/balance.rs index a7f69049862..248e9488a89 100644 --- a/gtest/src/balance.rs +++ b/gtest/src/balance.rs @@ -20,7 +20,7 @@ use std::collections::HashMap; -use crate::{manager::Actors, DEFAULT_USER_ALICE, GAS_MULTIPLIER}; +use crate::{manager::Actors, GAS_MULTIPLIER}; use gear_common::{Gas, GasMultiplier, ProgramId}; use gear_core::message::Value; @@ -38,6 +38,13 @@ impl Balance { #[track_caller] pub fn set_lock(&mut self, value: Value) { + if self.available() < value { + unreachable!( + "Trying to lock more then available balance, total: {}, lock: {}", + self.available(), + value + ); + } self.locked = value; } @@ -48,6 +55,7 @@ impl Balance { } } + #[track_caller] pub fn available(&self) -> u128 { self.total - self.locked } @@ -57,12 +65,28 @@ impl Balance { } #[track_caller] - pub fn decrease(&mut self, value: u128, _keep_alive: bool) { - //if self.total < value { - // unreachable!("Actor {:?} balance is less then sent value", from); - //} + pub fn decrease(&mut self, value: u128, keep_alive: bool) { + if keep_alive { + if self.available() < value { + unreachable!( + "Not enough balance to decrease, available: {}, value: {}", + self.available(), + value + ); + } + } else { + if self.total < value { + unreachable!( + "Not enough balance to decrease, total: {}, value: {}", + self.total, value + ); + } + } self.total -= value; + if !keep_alive && self.total < self.locked { + self.locked = self.total; + } } pub fn increase(&mut self, value: u128) { diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 586a9533e97..98cc00014fe 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -214,6 +214,28 @@ impl Actors { self.0.borrow().contains_key(program_id) } + pub fn total_balance(&self, program_id: &ProgramId) -> Value { + self.balance(program_id, true) + } + + pub fn available_balance(&self, program_id: &ProgramId) -> Value { + self.balance(program_id, false) + } + + fn balance(&self, program_id: &ProgramId, total: bool) -> Value { + let actors = self.0.borrow(); + + let Some((_, balance)) = &actors.get(program_id) else { + return 0; + }; + + if total { + balance.total() + } else { + balance.available() + } + } + fn remove(&mut self, program_id: &ProgramId) -> Option<(TestActor, Balance)> { self.0.borrow_mut().remove(program_id) } @@ -510,43 +532,80 @@ impl ExtManager { #[track_caller] fn validate_dispatch(&mut self, dispatch: &Dispatch) { - if self.is_program(&dispatch.source()) { + let source = dispatch.source(); + let destination = dispatch.destination(); + + if self.is_program(&source) { panic!("Sending messages allowed only from users id"); } - let mut actors = self.actors.borrow_mut(); - let (_, balance) = actors - .entry(dispatch.source()) - .or_insert((TestActor::User, Balance::default())); + // Create user if it doesn't exist + if !self.actors.contains_key(&source) { + self.actors + .borrow_mut() + .insert(source, (TestActor::User, Balance::default())); + } + + let charge_ed = self + .actors + .borrow() + .get(&destination) + .expect("Can't fail") + .0 + .is_uninitialized() + && self.actors.total_balance(&destination) == 0; + let ed = if charge_ed { EXISTENTIAL_DEPOSIT } else { 0 }; + + let total_balance = self.actors.total_balance(&source); let keep_alive_sender = false; let gas_limit = dispatch .gas_limit() .unwrap_or_else(|| unreachable!("message from program API always has gas")); + let gas_value = GAS_MULTIPLIER.gas_to_value(gas_limit); // Check sender has enough balance to support dispatch - if balance.total() < { dispatch.value() + GAS_MULTIPLIER.gas_to_value(gas_limit) } { + if total_balance < { dispatch.value() + gas_value + ed } { panic!( "Insufficient balance: user ({}) tries to send \ - ({}) value and ({}) gas, while his balance ({:?})", - dispatch.source(), + ({}) value, ({}) gas and ED {}, while his balance ({:?})", + source, dispatch.value(), - gas_limit, - balance.total(), + gas_value, + EXISTENTIAL_DEPOSIT, + total_balance, ); } + // Charge for program ED upon creation + if charge_ed { + Balance::transfer( + &mut self.actors, + source, + destination, + EXISTENTIAL_DEPOSIT, + keep_alive_sender, + ); + + // Set ED lock + let mut actors = self.actors.borrow_mut(); + actors + .get_mut(&destination) + .expect("Can't fail") + .1 + .set_lock(EXISTENTIAL_DEPOSIT); + } + + let mut actors = self.actors.borrow_mut(); + let source_balance = &mut actors.get_mut(&source).expect("Can't fail").1; + // Deposit message value - self.bank.deposit_value( - balance, - dispatch.source(), - dispatch.value(), - keep_alive_sender, - ); + self.bank + .deposit_value(source_balance, source, dispatch.value(), keep_alive_sender); // Deposit gas self.bank - .deposit_gas(balance, dispatch.source(), gas_limit, keep_alive_sender); + .deposit_gas(source_balance, source, gas_limit, keep_alive_sender); } #[track_caller] @@ -1096,13 +1155,7 @@ impl JournalHandler for ExtManager { } fn exit_dispatch(&mut self, id_exited: ProgramId, value_destination: ProgramId) { - let total_value = self - .actors - .borrow_mut() - .get(&id_exited) - .expect("Can't fail") - .1 - .total(); + let total_value = self.actors.total_balance(&id_exited); Balance::transfer( &mut self.actors, id_exited, @@ -1330,7 +1383,7 @@ impl JournalHandler for ExtManager { program_id, candidate_id, crate::EXISTENTIAL_DEPOSIT, - false, + true, ); // Set ED lock diff --git a/gtest/src/program.rs b/gtest/src/program.rs index e05fac77d4f..2609105c3c5 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -864,7 +864,7 @@ mod tests { use crate::{ manager::Value, Log, ProgramIdWrapper, System, DEFAULT_USERS_INITIAL_BALANCE, - DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT, + DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT, GAS_MULTIPLIER, }; use demo_constructor::{Arg, Scheme}; use gear_common::Origin; @@ -944,29 +944,29 @@ mod tests { let user_id = 42; let mut user_spent_balance = 0; - sys.mint_to(user_id, 10 * crate::EXISTENTIAL_DEPOSIT); - assert_eq!(sys.balance_of(user_id), 10 * crate::EXISTENTIAL_DEPOSIT); + sys.mint_to(user_id, 10 * EXISTENTIAL_DEPOSIT); + assert_eq!(sys.balance_of(user_id), 10 * EXISTENTIAL_DEPOSIT); let mut prog = Program::from_binary_with_id(&sys, 137, demo_ping::WASM_BINARY); - prog.mint(2 * crate::EXISTENTIAL_DEPOSIT); - assert_eq!(prog.balance(), 2 * crate::EXISTENTIAL_DEPOSIT); + prog.mint(2 * EXISTENTIAL_DEPOSIT); + assert_eq!(prog.balance(), 2 * EXISTENTIAL_DEPOSIT); - prog.send_with_value(user_id, "init".to_string(), crate::EXISTENTIAL_DEPOSIT); + prog.send_with_value(user_id, "init".to_string(), EXISTENTIAL_DEPOSIT); user_spent_balance += sys.run_next_block().spent_value(); - assert_eq!(prog.balance(), 3 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!(prog.balance(), 3 * EXISTENTIAL_DEPOSIT); assert_eq!( sys.balance_of(user_id), - 9 * crate::EXISTENTIAL_DEPOSIT - user_spent_balance + 9 * EXISTENTIAL_DEPOSIT - user_spent_balance ); - prog.send_with_value(user_id, "PING".to_string(), 2 * crate::EXISTENTIAL_DEPOSIT); + prog.send_with_value(user_id, "PING".to_string(), 2 * EXISTENTIAL_DEPOSIT); user_spent_balance += sys.run_next_block().spent_value(); - assert_eq!(prog.balance(), 5 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!(prog.balance(), 5 * EXISTENTIAL_DEPOSIT); assert_eq!( sys.balance_of(user_id), - 7 * crate::EXISTENTIAL_DEPOSIT - user_spent_balance + 7 * EXISTENTIAL_DEPOSIT - user_spent_balance ); } @@ -981,42 +981,45 @@ mod tests { let sender2 = 45; // Top-up senders balances - sys.mint_to(sender0, 20 * crate::EXISTENTIAL_DEPOSIT); - sys.mint_to(sender1, 20 * crate::EXISTENTIAL_DEPOSIT); - sys.mint_to(sender2, 20 * crate::EXISTENTIAL_DEPOSIT); + sys.mint_to(sender0, 20 * EXISTENTIAL_DEPOSIT); + sys.mint_to(sender1, 20 * EXISTENTIAL_DEPOSIT); + sys.mint_to(sender2, 20 * EXISTENTIAL_DEPOSIT); // Top-up receiver balance - let mut receiver_expected_balance = 2 * crate::EXISTENTIAL_DEPOSIT; + let mut receiver_expected_balance = 3 * EXISTENTIAL_DEPOSIT; sys.mint_to(receiver, receiver_expected_balance); let prog = Program::from_binary_with_id(&sys, 137, demo_piggy_bank::WASM_BINARY); prog.send_with_gas(receiver, b"init", 1_000_000, 0); - receiver_expected_balance -= sys.run_next_block().spent_value(); - assert_eq!(prog.balance(), 0); + receiver_expected_balance -= sys.run_next_block().spent_value() + EXISTENTIAL_DEPOSIT; + assert_eq!(prog.balance(), EXISTENTIAL_DEPOSIT); // Send values to the program - prog.send_bytes_with_value(sender0, b"insert", 2 * crate::EXISTENTIAL_DEPOSIT); + prog.send_bytes_with_value(sender0, b"insert", 2 * EXISTENTIAL_DEPOSIT); let sender0_spent_value = sys.run_next_block().spent_value(); assert_eq!( sys.balance_of(sender0), - 18 * crate::EXISTENTIAL_DEPOSIT - sender0_spent_value + 18 * EXISTENTIAL_DEPOSIT - sender0_spent_value ); - prog.send_bytes_with_value(sender1, b"insert", 4 * crate::EXISTENTIAL_DEPOSIT); + prog.send_bytes_with_value(sender1, b"insert", 4 * EXISTENTIAL_DEPOSIT); let sender1_spent_value = sys.run_next_block().spent_value(); assert_eq!( sys.balance_of(sender1), - 16 * crate::EXISTENTIAL_DEPOSIT - sender1_spent_value + 16 * EXISTENTIAL_DEPOSIT - sender1_spent_value ); - prog.send_bytes_with_value(sender2, b"insert", 6 * crate::EXISTENTIAL_DEPOSIT); + prog.send_bytes_with_value(sender2, b"insert", 6 * EXISTENTIAL_DEPOSIT); let sender2_spent_value = sys.run_next_block().spent_value(); assert_eq!( sys.balance_of(sender2), - 14 * crate::EXISTENTIAL_DEPOSIT - sender2_spent_value + 14 * EXISTENTIAL_DEPOSIT - sender2_spent_value ); // Check program's balance - assert_eq!(prog.balance(), (2 + 4 + 6) * crate::EXISTENTIAL_DEPOSIT); + assert_eq!( + prog.balance(), + (2 + 4 + 6) * EXISTENTIAL_DEPOSIT + EXISTENTIAL_DEPOSIT + ); // Request to smash the piggy bank and send the value to the receiver address prog.send_bytes_with_gas(receiver, b"smash", 100_000_000, 0); @@ -1043,11 +1046,10 @@ mod tests { .is_ok()); assert_eq!( sys.balance_of(receiver), - (2 + 4 + 6) * crate::EXISTENTIAL_DEPOSIT + receiver_expected_balance + (2 + 4 + 6) * EXISTENTIAL_DEPOSIT + receiver_expected_balance ); - - // Check program's balance is empty - assert_eq!(prog.balance(), 0); + // Program is alive and holds the ED + assert_eq!(prog.balance(), EXISTENTIAL_DEPOSIT); } #[test] @@ -1061,7 +1063,7 @@ mod tests { #[test] #[should_panic( expected = "Insufficient balance: user (0x0500000000000000000000000000000000000000000000000000000000000000) \ - tries to send (1000000000001) value and (750000000000) gas, while his balance (1000000000000)" + tries to send (1000000000001) value, (4500000000000) gas and ED 1000000000000, while his balance (1000000000000)" )] fn fails_on_insufficient_balance() { let sys = System::new(); @@ -1070,10 +1072,10 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 6, demo_piggy_bank::WASM_BINARY); assert_eq!(sys.balance_of(user), 0); - sys.mint_to(user, crate::EXISTENTIAL_DEPOSIT); - assert_eq!(sys.balance_of(user), crate::EXISTENTIAL_DEPOSIT); + sys.mint_to(user, EXISTENTIAL_DEPOSIT); + assert_eq!(sys.balance_of(user), EXISTENTIAL_DEPOSIT); - prog.send_bytes_with_value(user, b"init", crate::EXISTENTIAL_DEPOSIT + 1); + prog.send_bytes_with_value(user, b"init", EXISTENTIAL_DEPOSIT + 1); sys.run_next_block(); } @@ -1082,19 +1084,19 @@ mod tests { let sys = System::new(); sys.init_logger(); - const RECEIVER_INITIAL_BALANCE: Value = 10 * crate::EXISTENTIAL_DEPOSIT; + const RECEIVER_INITIAL_BALANCE: Value = 10 * EXISTENTIAL_DEPOSIT; let sender = 42; let receiver = 84; let mut receiver_expected_balance = RECEIVER_INITIAL_BALANCE; - sys.mint_to(sender, 20 * crate::EXISTENTIAL_DEPOSIT); + sys.mint_to(sender, 20 * EXISTENTIAL_DEPOSIT); sys.mint_to(receiver, RECEIVER_INITIAL_BALANCE); let prog = Program::from_binary_with_id(&sys, 137, demo_piggy_bank::WASM_BINARY); prog.send_bytes(receiver, b"init"); - receiver_expected_balance -= sys.run_next_block().spent_value(); + receiver_expected_balance -= sys.run_next_block().spent_value() + EXISTENTIAL_DEPOSIT; // Get zero value to the receiver's mailbox prog.send_bytes(receiver, b"smash"); @@ -1107,7 +1109,7 @@ mod tests { assert_eq!(sys.balance_of(receiver), receiver_expected_balance); // Get the value > ED to the receiver's mailbox - prog.send_bytes_with_value(sender, b"insert", 2 * crate::EXISTENTIAL_DEPOSIT); + prog.send_bytes_with_value(sender, b"insert", 2 * EXISTENTIAL_DEPOSIT); sys.run_next_block(); prog.send_bytes(receiver, b"smash"); receiver_expected_balance -= sys.run_next_block().spent_value(); @@ -1118,8 +1120,10 @@ mod tests { .is_ok()); assert_eq!( sys.balance_of(receiver), - 2 * crate::EXISTENTIAL_DEPOSIT + receiver_expected_balance + 2 * EXISTENTIAL_DEPOSIT + receiver_expected_balance ); + // Program is alive and holds the ED + assert_eq!(prog.balance(), EXISTENTIAL_DEPOSIT); } struct CleanupFolderOnDrop { @@ -1254,7 +1258,7 @@ mod tests { sys.init_logger(); let user_id = 42; - let mut user_balance = EXISTENTIAL_DEPOSIT; + let mut user_balance = 2 * EXISTENTIAL_DEPOSIT; sys.mint_to(user_id, user_balance); let prog_id = 137; @@ -1263,16 +1267,18 @@ mod tests { let msg_id = prog.send_with_gas(user_id, demo_exit_handle::scheme(), 1_000_000_000, 0); let result = sys.run_next_block(); - user_balance -= result.spent_value(); + user_balance -= result.spent_value() + EXISTENTIAL_DEPOSIT; assert!(result.succeed.contains(&msg_id)); - assert_eq!(sys.balance_of(prog_id), 0); + assert_eq!(sys.balance_of(prog_id), EXISTENTIAL_DEPOSIT); assert_eq!(sys.balance_of(user_id), user_balance); let msg_id = prog.send_bytes_with_gas(user_id, [], 1_000_000_000, 0); let result = sys.run_next_block(); user_balance -= result.spent_value(); + // ED returned upon program exit + user_balance += EXISTENTIAL_DEPOSIT; assert!(result.succeed.contains(&msg_id)); assert_eq!(sys.balance_of(prog_id), 0); assert_eq!(sys.balance_of(user_id), user_balance); @@ -1286,7 +1292,10 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 137, demo_ping::WASM_BINARY); let user_id = ActorId::zero(); - sys.mint_to(user_id, EXISTENTIAL_DEPOSIT + 1); + sys.mint_to( + user_id, + EXISTENTIAL_DEPOSIT + GAS_MULTIPLIER.gas_to_value(1), + ); // set insufficient gas for execution let msg_id = prog.send_with_gas(user_id, "init".to_string(), 1, 0); @@ -1417,14 +1426,37 @@ mod tests { sys.init_logger(); let user_id = DEFAULT_USER_ALICE; - let _ = sys.balance_of(user_id); - let prog = Program::from_binary_with_id(&sys, 1337, WASM_BINARY); prog.send(user_id, Scheme::empty()); let block_result = sys.run_next_block(); - let expected_value = DEFAULT_USERS_INITIAL_BALANCE - block_result.spent_value(); + let expected_value = + DEFAULT_USERS_INITIAL_BALANCE - block_result.spent_value() - EXISTENTIAL_DEPOSIT; assert_eq!(sys.balance_of(user_id), expected_value); } + + #[test] + fn test_program_creation_charges_ed() { + use demo_constructor::WASM_BINARY; + + let sys = System::new(); + //sys.init_logger(); + sys.init_verbose_logger(); + + let user_id = DEFAULT_USER_ALICE; + let orig_user_balance = sys.balance_of(user_id); + + let prog_id = 1337; + let prog = Program::from_binary_with_id(&sys, prog_id, WASM_BINARY); + + prog.send(user_id, Scheme::empty()); + let block_result = sys.run_next_block(); + + assert_eq!(sys.balance_of(prog_id), EXISTENTIAL_DEPOSIT); + assert_eq!( + sys.balance_of(user_id), + orig_user_balance - EXISTENTIAL_DEPOSIT - block_result.spent_value() + ); + } } From 322bd575b6852d9ec926997ed2ff2688da64d2bb Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 12 Aug 2024 04:40:40 +0400 Subject: [PATCH 12/29] Clean up, return send_value on child program creation --- gtest/src/balance.rs | 15 ++++++----- gtest/src/manager.rs | 64 ++++++++++++-------------------------------- gtest/src/program.rs | 6 ++--- gtest/src/system.rs | 6 ++--- 4 files changed, 29 insertions(+), 62 deletions(-) diff --git a/gtest/src/balance.rs b/gtest/src/balance.rs index 248e9488a89..2fd2b967f6e 100644 --- a/gtest/src/balance.rs +++ b/gtest/src/balance.rs @@ -74,13 +74,11 @@ impl Balance { value ); } - } else { - if self.total < value { - unreachable!( - "Not enough balance to decrease, total: {}, value: {}", - self.total, value - ); - } + } else if self.total < value { + unreachable!( + "Not enough balance to decrease, total: {}, value: {}", + self.total, value + ); } self.total -= value; @@ -157,6 +155,7 @@ impl Bank { self.accounts.get_mut(&from).expect("must exist").gas -= gas_value; } + #[track_caller] pub fn spend_gas_to( &mut self, from: ProgramId, @@ -167,6 +166,7 @@ impl Bank { self.withdraw_gas(from, to, gas, multiplier) } + #[track_caller] pub fn withdraw_gas( &mut self, from: ProgramId, @@ -180,6 +180,7 @@ impl Bank { to.increase(value); } + #[track_caller] pub fn transfer_value(&mut self, from: ProgramId, to: &mut Balance, value: Value) { self.accounts.get_mut(&from).expect("must exist").value -= value; to.increase(value); diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 98cc00014fe..520275c0de9 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -218,8 +218,15 @@ impl Actors { self.balance(program_id, true) } - pub fn available_balance(&self, program_id: &ProgramId) -> Value { - self.balance(program_id, false) + #[track_caller] + pub fn set_balance_lock(&mut self, program_id: &ProgramId, value: Value) { + let mut actors = self.0.borrow_mut(); + let balance = &mut actors + .get_mut(program_id) + .expect("Can't find existing program") + .1; + + balance.set_lock(value); } fn balance(&self, program_id: &ProgramId, total: bool) -> Value { @@ -241,15 +248,6 @@ impl Actors { } } -/// Simple boolean for whether an account needs to be kept in existence. -#[derive(PartialEq)] -pub(crate) enum MintMode { - /// Operation must not result in the account going out of existence. - KeepAlive, - /// Operation may result in account going out of existence. - AllowDeath, -} - #[derive(Debug, Default)] pub(crate) struct ExtManager { // State metadata @@ -588,12 +586,8 @@ impl ExtManager { ); // Set ED lock - let mut actors = self.actors.borrow_mut(); - actors - .get_mut(&destination) - .expect("Can't fail") - .1 - .set_lock(EXISTENTIAL_DEPOSIT); + self.actors + .set_balance_lock(&destination, EXISTENTIAL_DEPOSIT); } let mut actors = self.actors.borrow_mut(); @@ -718,8 +712,8 @@ impl ExtManager { ) } - pub(crate) fn mint_to(&mut self, id: &ProgramId, value: Value, mint_mode: MintMode) { - if mint_mode == MintMode::KeepAlive && value < crate::EXISTENTIAL_DEPOSIT { + pub(crate) fn mint_to(&mut self, id: &ProgramId, value: Value) { + if value < crate::EXISTENTIAL_DEPOSIT { panic!( "An attempt to mint value ({}) less than existential deposit ({})", value, @@ -734,21 +728,6 @@ impl ExtManager { balance.increase(value); } - pub(crate) fn burn_from(&mut self, id: &ProgramId, value: Value) { - let mut actors = self.actors.borrow_mut(); - let (_, balance) = actors.get_mut(id).expect("Can't find existing program"); - - if balance.total() < value { - panic!( - "Insufficient balance: user ({}) tries to burn \ - ({}) value, while his balance ({:?})", - id, value, balance - ); - } else { - balance.decrease(value, false); - } - } - pub(crate) fn balance_of(&self, id: &ProgramId) -> Value { self.actors .borrow() @@ -1377,22 +1356,13 @@ impl JournalHandler for ExtManager { }), Some(init_message_id), ); + // Transfer the ED from the program-creator to the new program - Balance::transfer( - &mut self.actors, - program_id, - candidate_id, - crate::EXISTENTIAL_DEPOSIT, - true, - ); + self.send_value(program_id, Some(candidate_id), EXISTENTIAL_DEPOSIT); // Set ED lock - let mut actors = self.actors.borrow_mut(); - actors - .get_mut(&candidate_id) - .expect("Can't fail") - .1 - .set_lock(EXISTENTIAL_DEPOSIT); + self.actors + .set_balance_lock(&candidate_id, EXISTENTIAL_DEPOSIT); } else { log::debug!("Program with id {candidate_id:?} already exists"); } diff --git a/gtest/src/program.rs b/gtest/src/program.rs index 2609105c3c5..1a53231e4bc 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -18,7 +18,7 @@ use crate::{ default_users_list, - manager::{ExtManager, GenuineProgram, MintMode, Program as InnerProgram, TestActor, Value}, + manager::{ExtManager, GenuineProgram, Program as InnerProgram, TestActor, Value}, system::System, Result, GAS_ALLOWANCE, }; @@ -733,9 +733,7 @@ impl<'a> Program<'a> { /// Mint balance to the account. pub fn mint(&mut self, value: Value) { - self.manager - .borrow_mut() - .mint_to(&self.id(), value, MintMode::KeepAlive) + self.manager.borrow_mut().mint_to(&self.id(), value) } /// Returns the balance of the account. diff --git a/gtest/src/system.rs b/gtest/src/system.rs index 91bacda5070..de7efc34f8d 100644 --- a/gtest/src/system.rs +++ b/gtest/src/system.rs @@ -19,7 +19,7 @@ use crate::{ log::{BlockRunResult, CoreLog}, mailbox::ActorMailbox, - manager::{Actors, ExtManager, MintMode, Value}, + manager::{Actors, ExtManager, Value}, program::{Program, ProgramIdWrapper}, Gas, GAS_ALLOWANCE, }; @@ -378,9 +378,7 @@ impl System { /// Mint balance to user with given `id` and `value`. pub fn mint_to>(&self, id: ID, value: Value) { let actor_id = id.into().0; - self.0 - .borrow_mut() - .mint_to(&actor_id, value, MintMode::KeepAlive); + self.0.borrow_mut().mint_to(&actor_id, value); } /// Returns balance of user with given `id`. From 6d8115d05fed6780ebb82567fa7ad36e4f2b45ad Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 12 Aug 2024 05:23:40 +0400 Subject: [PATCH 13/29] Fix mailbox test --- gtest/src/mailbox/actor.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/gtest/src/mailbox/actor.rs b/gtest/src/mailbox/actor.rs index 7daa1f8eb4a..78f1c2d5655 100644 --- a/gtest/src/mailbox/actor.rs +++ b/gtest/src/mailbox/actor.rs @@ -124,7 +124,7 @@ impl<'a> ActorMailbox<'a> { #[cfg(test)] mod tests { - use crate::{Log, Program, System, EXISTENTIAL_DEPOSIT}; + use crate::{Log, Program, System, DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT}; use codec::Encode; use demo_constructor::{Call, Calls, Scheme, WASM_BINARY}; use gear_core::ids::ProgramId; @@ -132,7 +132,7 @@ mod tests { fn prepare_program(system: &System) -> (Program<'_>, ([u8; 32], Vec, Log)) { let program = Program::from_binary_with_id(system, 121, WASM_BINARY); - let sender = ProgramId::from(42).into_bytes(); + let sender = ProgramId::from(DEFAULT_USER_ALICE).into_bytes(); let payload = b"sup!".to_vec(); let log = Log::builder().dest(sender).payload_bytes(payload.clone()); @@ -148,8 +148,7 @@ mod tests { let system = System::new(); let (program, (sender, payload, log)) = prepare_program(&system); - let original_balance = 20 * EXISTENTIAL_DEPOSIT; - system.mint_to(sender, original_balance); + let original_balance = system.balance_of(sender); let value_send = 2 * EXISTENTIAL_DEPOSIT; let handle = Calls::builder().send_value(sender, payload, value_send); @@ -157,12 +156,18 @@ mod tests { let res = system.run_next_block(); assert!(res.succeed.contains(&msg_id)); assert!(res.contains(&log)); - assert_eq!(system.balance_of(sender), original_balance - value_send); + assert_eq!( + system.balance_of(sender), + original_balance - value_send - res.spent_value() + ); let mailbox = system.get_mailbox(sender); assert!(mailbox.contains(&log)); assert!(mailbox.claim_value(log).is_ok()); - assert_eq!(system.balance_of(sender), original_balance); + assert_eq!( + system.balance_of(sender), + original_balance - res.spent_value() + ); } #[test] From cd443746b64ee3eadfcfeaa1eb4af010faf23450 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 12 Aug 2024 20:01:15 +0400 Subject: [PATCH 14/29] Update cargo-gbuild tests --- utils/cargo-gbuild/test-program/src/lib.rs | 4 ++-- utils/cargo-gbuild/tests/smoke.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/cargo-gbuild/test-program/src/lib.rs b/utils/cargo-gbuild/test-program/src/lib.rs index ac5e4e501c3..6126023cac7 100644 --- a/utils/cargo-gbuild/test-program/src/lib.rs +++ b/utils/cargo-gbuild/test-program/src/lib.rs @@ -40,7 +40,7 @@ extern "C" fn handle() { #[cfg(test)] mod tests { - use gtest::{Program, System}; + use gtest::{Program, System, constants::DEFAULT_USER_ALICE}; #[test] fn test_init() { @@ -51,7 +51,7 @@ mod tests { system.init_logger(); // Get program from artifact - let user = 0; + let user = DEFAULT_USER_ALICE; let program = Program::current(&system); // Init program diff --git a/utils/cargo-gbuild/tests/smoke.rs b/utils/cargo-gbuild/tests/smoke.rs index 988c63a3901..e8ac4612612 100644 --- a/utils/cargo-gbuild/tests/smoke.rs +++ b/utils/cargo-gbuild/tests/smoke.rs @@ -18,12 +18,12 @@ use anyhow::Result; use cargo_gbuild::GBuild; -use gtest::{state_args, Program, System}; +use gtest::{constants::DEFAULT_USER_ALICE, state_args, Program, System}; use std::{fs, path::PathBuf, process::Command}; fn ping(sys: &System, prog: PathBuf) -> Program<'_> { // Get program from artifact - let user = 0; + let user = DEFAULT_USER_ALICE; let program = Program::from_file(sys, prog); // Init program From 1867a540d0663d5e7eef431b8df7fa946d8654de Mon Sep 17 00:00:00 2001 From: Sabaun Taraki Date: Tue, 13 Aug 2024 00:05:39 +0300 Subject: [PATCH 15/29] Fix CI --- gtest/src/lib.rs | 45 ++++++++++++++++++++++++++++----------------- gtest/src/system.rs | 4 ++-- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index 8777898fcb2..137dad2501b 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -124,13 +124,17 @@ //! let prog = Program::current(&sys); //! //! // Send an init message to the program. -//! let res = prog.send_bytes(USER_ID, b"Doesn't matter"); +//! let init_message_id = prog.send_bytes(USER_ID, b"Doesn't matter"); +//! +//! // Run execution of the block which will contain `init_message_id` +//! let block_run_result = sys.run_next_block(); //! //! // Check whether the program was initialized successfully. -//! assert!(!res.main_failed()); +//! assert!(block_run_result.succeed.contains(&init_message_id)); //! //! // Send a handle message to the program. -//! let res = prog.send_bytes(USER_ID, b"PING"); +//! let handle_message_id = prog.send_bytes(USER_ID, b"PING"); +//! let block_run_result = sys.run_next_block(); //! //! // Check the result of the program execution. //! // 1. Create a log pattern with the expected result. @@ -140,10 +144,10 @@ //! .payload_bytes(b"PONG"); //! //! // 2. Check whether the program was executed successfully. -//! assert!(!res.main_failed()); +//! assert!(block_run_result.succeed.contains(&handle_message_id)); //! //! // 3. Make sure the log entry is in the result. -//! assert!(res.contains(&log)); +//! assert!(block_run_result.contains(&log)); //! } //! } //! ``` @@ -279,22 +283,29 @@ //! //! ## Processing the result of the program execution //! -//! Any sending functions in the lib returns [`RunResult`] structure. +//! Any sending functions in the lib returns an id of the sent message. +//! +//! In order to actually get the result of the program execution the block +//! execution should be triggered (see "Block execution model" section). +//! Block execution function returns the result of the block run +//! ([`BlockRunResult`]) //! //! It contains the final result of the processing message and others, which //! were created during the execution. //! -//! It has 4 main functions: -//! -//! - [`RunResult::log`] — returns the reference to the Vec produced to users -//! messages. You may assert them as you wish, iterating through them. -//! - [`RunResult::main_failed`] — returns bool which shows that there was panic -//! during the execution of the main message. -//! - [`RunResult::others_failed`] — returns bool which shows that there was -//! panic during the execution of the created messages during the main -//! execution. Equals false if no others were called. -//! - [`RunResult::contains`] — returns bool which shows that logs contain a -//! given log. Syntax sugar around `res.log().iter().any(|v| v == arg)`. +//! It has 2 main functions: +//! +//! - [`BlockRunResult::log`] — returns the reference to the Vec produced to +//! users messages. You may assert them as you wish, iterating through them. +//! - [`BlockRunResult::contains`] — returns bool which shows that logs contain +//! a given log. Syntax sugar around `res.log().iter().any(|v| v == arg)`. +//! +//! Fields of the type are public, and some of them can be really useful: +//! +//! - field `succeed` is a set of ids of messages that were successfully +//! executed. +//! - field `failed` is a set of ids of messages that failed during the +//! execution. //! //! To build a log for assertion you need to use [`Log`] structure with its //! builders. All fields here are optional. Assertion with `Log`s from core are diff --git a/gtest/src/system.rs b/gtest/src/system.rs index eb3b7c679a0..a23b262f0f6 100644 --- a/gtest/src/system.rs +++ b/gtest/src/system.rs @@ -203,7 +203,7 @@ impl System { self.run_next_block_with_allowance(Gas(GAS_ALLOWANCE)) } - /// Runs blocks same as [`Self::run_to_next_block`], but with limited + /// Runs blocks same as [`Self::run_next_block`], but with limited /// allowance. pub fn run_next_block_with_allowance(&self, allowance: Gas) -> BlockRunResult { if allowance > Gas(GAS_ALLOWANCE) { @@ -213,7 +213,7 @@ impl System { self.0.borrow_mut().run_new_block(allowance) } - /// Runs blocks same as [`Self::run_to_next_block`], but executes blocks to + /// Runs blocks same as [`Self::run_next_block`], but executes blocks to /// block number `bn` including it. pub fn run_to_block(&self, bn: u32) -> Vec { let mut manager = self.0.borrow_mut(); From 918321476c485fdb49f4f4678898146979327236 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 12 Aug 2024 23:16:10 +0400 Subject: [PATCH 16/29] Review fixes --- gtest/src/balance.rs | 91 ++++++++++++++++++++++++++++++------------- gtest/src/lib.rs | 2 +- gtest/src/log.rs | 4 +- gtest/src/manager.rs | 93 ++++++++++++++++++++++++-------------------- gtest/src/program.rs | 59 ++++++---------------------- gtest/src/system.rs | 14 +++++++ 6 files changed, 143 insertions(+), 120 deletions(-) diff --git a/gtest/src/balance.rs b/gtest/src/balance.rs index 2fd2b967f6e..2214d1978af 100644 --- a/gtest/src/balance.rs +++ b/gtest/src/balance.rs @@ -20,26 +20,29 @@ use std::collections::HashMap; -use crate::{manager::Actors, GAS_MULTIPLIER}; use gear_common::{Gas, GasMultiplier, ProgramId}; -use gear_core::message::Value; +use crate::{constants::Value, manager::Actors, GAS_MULTIPLIER}; + +/// Balance of an actor. #[derive(Debug, Clone, Default)] pub struct Balance { - total: u128, - // Primary used for ED locking - locked: u128, + total: Value, + // Primary used for ED locking for programs + locked: Value, } impl Balance { - pub fn new(total: u128) -> Self { + /// Create a new balance. + pub fn new(total: Value) -> Self { Self { total, locked: 0 } } + /// Lock the balance. #[track_caller] pub fn set_lock(&mut self, value: Value) { if self.available() < value { - unreachable!( + panic!( "Trying to lock more then available balance, total: {}, lock: {}", self.available(), value @@ -48,6 +51,7 @@ impl Balance { self.locked = value; } + /// Unlock the balance. pub fn empty() -> Self { Self { total: 0, @@ -55,42 +59,52 @@ impl Balance { } } + /// Get the available balance. #[track_caller] - pub fn available(&self) -> u128 { + pub fn available(&self) -> Value { self.total - self.locked } - pub fn total(&self) -> u128 { + /// Get the total balance. + pub fn total(&self) -> Value { self.total } + /// Decrease the balance. + /// + /// If `respect_lock` is true, the total balance will not be decreased lower + /// than the locked value. If `respect_lock` is false, the total balance + /// will be decreased lower than the locked value. #[track_caller] - pub fn decrease(&mut self, value: u128, keep_alive: bool) { - if keep_alive { - if self.available() < value { - unreachable!( - "Not enough balance to decrease, available: {}, value: {}", - self.available(), - value - ); - } - } else if self.total < value { - unreachable!( + pub fn decrease(&mut self, value: u128, respect_lock: bool) { + if respect_lock && self.total - value < self.locked { + panic!( + "Not enough balance to decrease, available: {}, value: {}", + self.available(), + value + ); + } + + if !respect_lock && self.total < value { + panic!( "Not enough balance to decrease, total: {}, value: {}", self.total, value ); } self.total -= value; - if !keep_alive && self.total < self.locked { + if self.total < self.locked { self.locked = self.total; } } - pub fn increase(&mut self, value: u128) { + /// Increase the balance. + pub fn increase(&mut self, value: Value) { self.total += value; } + #[track_caller] + /// Transfer the balance. pub(crate) fn transfer( actors: &mut Actors, from: ProgramId, @@ -99,15 +113,20 @@ impl Balance { keep_alive: bool, ) { let mut actors = actors.borrow_mut(); - let (_, from) = actors.get_mut(&from).expect("Actor should exist"); + let (_, from) = actors + .get_mut(&from) + .unwrap_or_else(|| panic!("Sender actor id {from:?} should exist")); from.decrease(value, keep_alive); - let (_, to) = actors.get_mut(&to).expect("Actor should exist"); + + let (_, to) = actors + .get_mut(&to) + .unwrap_or_else(|| panic!("Receiver actor id {to:?} should exist")); to.increase(value); } } impl PartialEq for Balance { - fn eq(&self, other: &u128) -> bool { + fn eq(&self, other: &Value) -> bool { self.total == *other } } @@ -118,12 +137,14 @@ struct AccountBalance { value: Value, } +/// GTest bank. #[derive(Default, Debug)] pub struct Bank { accounts: HashMap, } impl Bank { + /// Create a new bank. #[track_caller] pub fn deposit_value( &mut self, @@ -139,6 +160,7 @@ impl Bank { .value += value; } + /// Deposit gas. #[track_caller] pub fn deposit_gas(&mut self, from: &mut Balance, to: ProgramId, gas: Gas, keep_alive: bool) { let gas_value = GAS_MULTIPLIER.gas_to_value(gas); @@ -149,12 +171,17 @@ impl Bank { .gas += gas_value; } + /// Withdraw gas. #[track_caller] pub fn spend_gas(&mut self, from: ProgramId, gas: Gas, multiplier: GasMultiplier) { let gas_value = multiplier.gas_to_value(gas); - self.accounts.get_mut(&from).expect("must exist").gas -= gas_value; + self.accounts + .get_mut(&from) + .unwrap_or_else(|| panic!("Bank::spend_gas: actor id {from:?} not found in bank")) + .gas -= gas_value; } + /// Withdraw value. #[track_caller] pub fn spend_gas_to( &mut self, @@ -166,6 +193,7 @@ impl Bank { self.withdraw_gas(from, to, gas, multiplier) } + /// Withdraw gas. #[track_caller] pub fn withdraw_gas( &mut self, @@ -175,14 +203,21 @@ impl Bank { multiplier: GasMultiplier, ) { let gas_left_value = multiplier.gas_to_value(gas_left); - self.accounts.get_mut(&from).expect("must exist").gas -= gas_left_value; + self.accounts + .get_mut(&from) + .unwrap_or_else(|| panic!("Bank::withdraw_gas: actor id {from:?} not found in bank")) + .gas -= gas_left_value; let value = multiplier.gas_to_value(gas_left); to.increase(value); } + /// Transfer value. #[track_caller] pub fn transfer_value(&mut self, from: ProgramId, to: &mut Balance, value: Value) { - self.accounts.get_mut(&from).expect("must exist").value -= value; + self.accounts + .get_mut(&from) + .unwrap_or_else(|| panic!("Bank::transfer_value: actor id {from:?} not found in bank")) + .value -= value; to.increase(value); } } diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index 111d079d8b7..474196aa1a5 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -429,7 +429,7 @@ //! // without any arguments. //! ``` //! --> -//#![deny(missing_docs)] +#![deny(missing_docs)] #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] #![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] diff --git a/gtest/src/log.rs b/gtest/src/log.rs index 65e9310ab53..7250cb0794d 100644 --- a/gtest/src/log.rs +++ b/gtest/src/log.rs @@ -18,7 +18,7 @@ use crate::{ program::{Gas, ProgramIdWrapper}, - GAS_MULTIPLIER, + Value, GAS_MULTIPLIER, }; use codec::{Codec, Encode}; use core_processor::configs::BlockInfo; @@ -451,7 +451,7 @@ impl BlockRunResult { } /// Calculate the total spent value. - pub fn spent_value(&self) -> u128 { + pub fn spent_value(&self) -> Value { let spent_gas = self .gas_burned .values() diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 520275c0de9..4637adb356c 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -194,6 +194,20 @@ impl Program { pub(crate) struct Actors(Rc>>); impl Actors { + pub fn new() -> Self { + let mut actors = Actors::default(); + + // Add default users + for &default_user_id in default_users_list() { + actors.insert( + default_user_id.into(), + (TestActor::User, Balance::new(DEFAULT_USERS_INITIAL_BALANCE)), + ); + } + + actors + } + pub fn borrow(&self) -> Ref<'_, BTreeMap> { self.0.borrow() } @@ -283,9 +297,10 @@ pub(crate) struct ExtManager { impl ExtManager { #[track_caller] pub(crate) fn new() -> Self { - let mut manager = Self { + Self { msg_nonce: 1, id_nonce: 1, + actors: Actors::new(), blocks_manager: BlocksManager::new(), messages_processing_enabled: true, random_data: ( @@ -299,19 +314,6 @@ impl ExtManager { 0, ), ..Default::default() - }; - - manager.init_default_users(); - manager - } - - // Should be called once inside `new` method. - fn init_default_users(&mut self) { - for &default_user_id in default_users_list() { - self.actors.insert( - default_user_id.into(), - (TestActor::User, Balance::new(DEFAULT_USERS_INITIAL_BALANCE)), - ); } } @@ -537,25 +539,15 @@ impl ExtManager { panic!("Sending messages allowed only from users id"); } - // Create user if it doesn't exist + // User must exist if !self.actors.contains_key(&source) { - self.actors - .borrow_mut() - .insert(source, (TestActor::User, Balance::default())); + panic!("User {source} doesn't exist; mint value to it first."); } - let charge_ed = self - .actors - .borrow() - .get(&destination) - .expect("Can't fail") - .0 - .is_uninitialized() - && self.actors.total_balance(&destination) == 0; - let ed = if charge_ed { EXISTENTIAL_DEPOSIT } else { 0 }; - + let is_init_msg = dispatch.kind().is_init(); + // We charge ED only for init messages + let maybe_ed = if is_init_msg { EXISTENTIAL_DEPOSIT } else { 0 }; let total_balance = self.actors.total_balance(&source); - let keep_alive_sender = false; let gas_limit = dispatch .gas_limit() @@ -563,26 +555,26 @@ impl ExtManager { let gas_value = GAS_MULTIPLIER.gas_to_value(gas_limit); // Check sender has enough balance to support dispatch - if total_balance < { dispatch.value() + gas_value + ed } { + if total_balance < { dispatch.value() + gas_value + maybe_ed } { panic!( "Insufficient balance: user ({}) tries to send \ ({}) value, ({}) gas and ED {}, while his balance ({:?})", source, dispatch.value(), gas_value, - EXISTENTIAL_DEPOSIT, + maybe_ed, total_balance, ); } // Charge for program ED upon creation - if charge_ed { + if is_init_msg { Balance::transfer( &mut self.actors, source, destination, EXISTENTIAL_DEPOSIT, - keep_alive_sender, + false, ); // Set ED lock @@ -595,11 +587,11 @@ impl ExtManager { // Deposit message value self.bank - .deposit_value(source_balance, source, dispatch.value(), keep_alive_sender); + .deposit_value(source_balance, source, dispatch.value(), false); // Deposit gas self.bank - .deposit_gas(source_balance, source, gas_limit, keep_alive_sender); + .deposit_gas(source_balance, source, gas_limit, false); } #[track_caller] @@ -728,6 +720,16 @@ impl ExtManager { balance.increase(value); } + pub(crate) fn transfer( + &mut self, + from: &ProgramId, + to: &ProgramId, + value: Value, + keep_alive: bool, + ) { + Balance::transfer(&mut self.actors, *from, *to, value, keep_alive); + } + pub(crate) fn balance_of(&self, id: &ProgramId) -> Value { self.actors .borrow() @@ -1185,16 +1187,21 @@ impl JournalHandler for ExtManager { log::debug!("[{message_id}] new dispatch#{}", dispatch.id()); let source = dispatch.source(); + let is_program = self.is_program(&dispatch.destination()); - if dispatch.value() != 0 { - let mut actors = self.actors.borrow_mut(); - let (_, balance) = actors.get_mut(&source).expect("Can't fail"); + let mut deposit_value = |actors: &mut Actors| { + if dispatch.value() != 0 { + let mut actors = actors.borrow_mut(); + let (_, balance) = actors.get_mut(&source).expect("Can't fail"); - self.bank - .deposit_value(balance, source, dispatch.value(), false); - } + self.bank + .deposit_value(balance, source, dispatch.value(), false); + } + }; + + if is_program { + deposit_value(&mut self.actors); - if self.is_program(&dispatch.destination()) { match (dispatch.gas_limit(), reservation) { (Some(gas_limit), None) => self .gas_tree @@ -1217,6 +1224,8 @@ impl JournalHandler for ExtManager { self.dispatches.push_back(dispatch.into_stored()); } else { + deposit_value(&mut self.actors); + let gas_limit = dispatch.gas_limit().unwrap_or_default(); let stored_message = dispatch.into_stored().into_parts().1; diff --git a/gtest/src/program.rs b/gtest/src/program.rs index 1a53231e4bc..f4798a1cc5b 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -861,8 +861,8 @@ mod tests { use super::Program; use crate::{ - manager::Value, Log, ProgramIdWrapper, System, DEFAULT_USERS_INITIAL_BALANCE, - DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT, GAS_MULTIPLIER, + manager::Value, Log, ProgramIdWrapper, System, DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT, + GAS_MULTIPLIER, }; use demo_constructor::{Arg, Scheme}; use gear_common::Origin; @@ -951,8 +951,12 @@ mod tests { assert_eq!(prog.balance(), 2 * EXISTENTIAL_DEPOSIT); prog.send_with_value(user_id, "init".to_string(), EXISTENTIAL_DEPOSIT); - user_spent_balance += sys.run_next_block().spent_value(); - assert_eq!(prog.balance(), 3 * EXISTENTIAL_DEPOSIT); + // Note: ED is charged upon program creation if its balance is not 0. + user_spent_balance += sys.run_next_block().spent_value() + EXISTENTIAL_DEPOSIT; + assert_eq!( + prog.balance(), + 3 * EXISTENTIAL_DEPOSIT + EXISTENTIAL_DEPOSIT + ); assert_eq!( sys.balance_of(user_id), 9 * EXISTENTIAL_DEPOSIT - user_spent_balance @@ -961,7 +965,10 @@ mod tests { prog.send_with_value(user_id, "PING".to_string(), 2 * EXISTENTIAL_DEPOSIT); user_spent_balance += sys.run_next_block().spent_value(); - assert_eq!(prog.balance(), 5 * EXISTENTIAL_DEPOSIT); + assert_eq!( + prog.balance(), + 5 * EXISTENTIAL_DEPOSIT + EXISTENTIAL_DEPOSIT + ); assert_eq!( sys.balance_of(user_id), 7 * EXISTENTIAL_DEPOSIT - user_spent_balance @@ -1415,46 +1422,4 @@ mod tests { assert!(res.succeed.contains(&msg_id)); assert!(mailbox.contains(&Log::builder().payload_bytes(payload).source(new_prog_id))); } - - #[test] - fn test_send_message_decreases_user_balance() { - use demo_constructor::WASM_BINARY; - - let sys = System::new(); - sys.init_logger(); - - let user_id = DEFAULT_USER_ALICE; - let prog = Program::from_binary_with_id(&sys, 1337, WASM_BINARY); - - prog.send(user_id, Scheme::empty()); - let block_result = sys.run_next_block(); - let expected_value = - DEFAULT_USERS_INITIAL_BALANCE - block_result.spent_value() - EXISTENTIAL_DEPOSIT; - - assert_eq!(sys.balance_of(user_id), expected_value); - } - - #[test] - fn test_program_creation_charges_ed() { - use demo_constructor::WASM_BINARY; - - let sys = System::new(); - //sys.init_logger(); - sys.init_verbose_logger(); - - let user_id = DEFAULT_USER_ALICE; - let orig_user_balance = sys.balance_of(user_id); - - let prog_id = 1337; - let prog = Program::from_binary_with_id(&sys, prog_id, WASM_BINARY); - - prog.send(user_id, Scheme::empty()); - let block_result = sys.run_next_block(); - - assert_eq!(sys.balance_of(prog_id), EXISTENTIAL_DEPOSIT); - assert_eq!( - sys.balance_of(user_id), - orig_user_balance - EXISTENTIAL_DEPOSIT - block_result.spent_value() - ); - } } diff --git a/gtest/src/system.rs b/gtest/src/system.rs index de7efc34f8d..1b8ca48761b 100644 --- a/gtest/src/system.rs +++ b/gtest/src/system.rs @@ -381,6 +381,20 @@ impl System { self.0.borrow_mut().mint_to(&actor_id, value); } + /// Transfer balance from user with given `from` id to user with given `to` + /// id. + pub fn transfer( + &mut self, + from: impl Into, + to: impl Into, + value: Value, + keep_alive: bool, + ) { + let from = from.into().0; + let to = to.into().0; + self.0.borrow_mut().transfer(&from, &to, value, keep_alive); + } + /// Returns balance of user with given `id`. pub fn balance_of>(&self, id: ID) -> Value { let actor_id = id.into().0; From 6eda5327297f11e4f3c5137bb08619c8a485e5fa Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 13 Aug 2024 02:16:08 +0400 Subject: [PATCH 17/29] Update gtest doc-comments --- gtest/src/balance.rs | 2 +- gtest/src/lib.rs | 44 +++++++++++++++++++++++++++++++++++++++++++- gtest/src/manager.rs | 2 +- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/gtest/src/balance.rs b/gtest/src/balance.rs index 2214d1978af..3deaf9e2497 100644 --- a/gtest/src/balance.rs +++ b/gtest/src/balance.rs @@ -137,7 +137,7 @@ struct AccountBalance { value: Value, } -/// GTest bank. +/// `gtest` bank. #[derive(Default, Debug)] pub struct Bank { accounts: HashMap, diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index 474196aa1a5..b3cde8ea33f 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -113,6 +113,7 @@ //! mod tests { //! use gtest::{Log, Program, System}; //! +//! // Or you can use the default users from the `gtest::constants`. //! const USER_ID: u64 = 100001; //! //! #[test] @@ -123,6 +124,9 @@ //! // Initialization of the current program structure. //! let prog = Program::current(&sys); //! +//! // Provide user with some balance. +//! sys.min_to(USER_ID, EXISTENTIAL_DEPOSIT * 1000); +//! //! // Send an init message to the program. //! let res = prog.send_bytes(USER_ID, b"Doesn't matter"); //! @@ -247,6 +251,29 @@ //! RUST_LOG="target_1=logging_level,target_2=logging_level" cargo test //! ``` //! +//! ## Pre-requisites for Sending a Message +//! +//! Prior to sending a message, it is necessary to mint sufficient balance for +//! the sender to ensure coverage of the existential deposit and gas costs. +//! +//! ```no_run +//! # let sys = gtest::System::new(); +//! let user_id = 42; +//! sys.mint_to(user_id, EXISTENTIAL_DEPOSIT * 1000); +//! ``` +//! +//! Alternatively, you can use the default users from `gtest::constants`, which +//! have a preallocated balance, as the message sender. +//! +//! ```no_run +//! # use gtest::constants::DEFAULT_USERS_INITIAL_BALANCE; +//! # let sys = gtest::System::new(); +//! assert_eq!( +//! sys.balance_of(gtest::constants::DEFAULT_USER_ALICE), +//! DEFAULT_USERS_INITIAL_BALANCE +//! ); +//! ``` +//! //! ## Sending messages //! //! To send message to the program need to call one of two program's functions: @@ -364,7 +391,22 @@ //! changes the timestamp. If you write time dependent logic, you should spend //! blocks manually. //! -//! ## Balance: +//! ## Balance +//! +//! There are certain invariants in `gtest` regarding user and program balances: +//! +//! * For a user to successfully send a message to the program, they must have +//! sufficient balance to cover the existential deposit and gas costs. +//! * The program charges the existential deposit from the user upon receiving +//! the initial message. +//! +//! As previously mentioned [here](#Pre-requisites-for-Sending-a-Message), +//! a balance for the user must be minted before sending a message. This balance +//! should be sufficient to cover both the existential deposit and gas costs. +//! +//! The `mint_to` method can be utilized to allocate a balance to the user, +//! while the `mint` method serves to allocate a balance to the program. The +//! `balance_of` method may be used to verify the current balance. //! //! ```no_run //! # use gtest::Program; diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 4637adb356c..77e9d03b762 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -554,7 +554,7 @@ impl ExtManager { .unwrap_or_else(|| unreachable!("message from program API always has gas")); let gas_value = GAS_MULTIPLIER.gas_to_value(gas_limit); - // Check sender has enough balance to support dispatch + // Check sender has enough balance to cover dispatch costs if total_balance < { dispatch.value() + gas_value + maybe_ed } { panic!( "Insufficient balance: user ({}) tries to send \ From 7ce2a48d3a252d81287c8d24dd3ed81b011bdfc1 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 13 Aug 2024 15:25:06 +0400 Subject: [PATCH 18/29] Fix tests --- examples/autoreply/src/lib.rs | 4 ++-- examples/custom/src/btree.rs | 6 +++--- examples/distributor/src/lib.rs | 12 ++++++------ examples/gas-burned/src/lib.rs | 4 ++-- examples/new-meta/tests/read_state.rs | 5 ++--- examples/node/src/lib.rs | 10 +++++----- examples/program-factory/src/lib.rs | 18 +++++++++++------- examples/reserve-gas/src/lib.rs | 4 ++-- examples/signal-entry/src/lib.rs | 4 ++-- examples/stack-allocations/src/lib.rs | 4 ++-- examples/syscall-error/src/lib.rs | 4 ++-- examples/wait_wake/src/lib.rs | 8 ++++---- examples/waiter/tests/mx_lock_access.rs | 4 ++-- examples/waiter/tests/rw_lock_access.rs | 4 ++-- gtest/src/program.rs | 6 +++--- 15 files changed, 50 insertions(+), 47 deletions(-) diff --git a/examples/autoreply/src/lib.rs b/examples/autoreply/src/lib.rs index f36016841ca..6b3077082e8 100644 --- a/examples/autoreply/src/lib.rs +++ b/examples/autoreply/src/lib.rs @@ -35,7 +35,7 @@ mod wasm; mod tests { use alloc::vec::Vec; use gstd::ActorId; - use gtest::{Program, System}; + use gtest::{constants::DEFAULT_USER_ALICE, Program, System}; #[test] fn auto_reply_received() { @@ -45,7 +45,7 @@ mod tests { let prog1 = Program::current(&system); let prog1_id = ActorId::try_from(prog1.id().as_ref()).unwrap(); - let from = 42; + let from = DEFAULT_USER_ALICE; // Init Program-1 let init_msg1 = prog1.send(from, ActorId::zero()); diff --git a/examples/custom/src/btree.rs b/examples/custom/src/btree.rs index 2d005b8f960..6ddf46474ff 100644 --- a/examples/custom/src/btree.rs +++ b/examples/custom/src/btree.rs @@ -94,7 +94,7 @@ mod tests { use super::{Reply, Request}; use crate::InitMessage; use alloc::vec; - use gtest::{Log, Program, System}; + use gtest::{constants::DEFAULT_USER_ALICE, Log, Program, System}; #[test] fn program_can_be_initialized() { @@ -103,7 +103,7 @@ mod tests { let program = Program::current(&system); - let from = 42; + let from = DEFAULT_USER_ALICE; program.send(from, InitMessage::BTree); let res = system.run_next_block(); @@ -118,7 +118,7 @@ mod tests { let program = Program::current_opt(&system); - let from = 42; + let from = DEFAULT_USER_ALICE; program.send(from, InitMessage::BTree); diff --git a/examples/distributor/src/lib.rs b/examples/distributor/src/lib.rs index 6cdb3b12012..6798f87aa46 100644 --- a/examples/distributor/src/lib.rs +++ b/examples/distributor/src/lib.rs @@ -58,7 +58,7 @@ mod wasm; #[cfg(test)] mod tests { use super::{Reply, Request}; - use gtest::{Log, Program, System}; + use gtest::{constants::DEFAULT_USER_ALICE, Log, Program, System}; #[test] fn program_can_be_initialized() { @@ -67,7 +67,7 @@ mod tests { let program = Program::current(&system); - let from = 42; + let from = DEFAULT_USER_ALICE; program.send_bytes(from, b"init"); let res = system.run_next_block(); @@ -82,7 +82,7 @@ mod tests { let program = Program::current(&system); - let from = 42; + let from = DEFAULT_USER_ALICE; // Init program.send_bytes(from, b"init"); @@ -112,7 +112,7 @@ mod tests { ) -> (Program, Program, Program) { system.init_logger(); - let from = 42; + let from = DEFAULT_USER_ALICE; let program_1 = Program::current_with_id(system, program_1_id); program_1.send_bytes(from, b"init"); @@ -147,7 +147,7 @@ mod tests { let system = System::new(); let (program_1, program_2, _program_3) = multi_program_setup(&system, 1, 2, 3); - let from = 42; + let from = DEFAULT_USER_ALICE; program_1.send(from, Request::Receive(11)); let res = system.run_next_block(); @@ -181,7 +181,7 @@ mod tests { let (program_1, _program_2, _program_3) = multi_program_setup(&system, 1, 2, 3); let program_4_id = 4; - let from = 42; + let from = DEFAULT_USER_ALICE; let program_4 = Program::current_with_id(&system, program_4_id); program_4.send_bytes(from, b"init"); diff --git a/examples/gas-burned/src/lib.rs b/examples/gas-burned/src/lib.rs index 5b6f056cc6b..6c600fbafab 100644 --- a/examples/gas-burned/src/lib.rs +++ b/examples/gas-burned/src/lib.rs @@ -13,14 +13,14 @@ mod wasm; #[cfg(test)] mod tests { - use gtest::{Gas, Program, System}; + use gtest::{constants::DEFAULT_USER_ALICE, Gas, Program, System}; #[test] fn gas_burned() { let system = System::new(); system.init_logger(); - let from = 42; + let from = DEFAULT_USER_ALICE; let program = Program::current(&system); let init_msg_id = program.send_bytes(from, "init"); diff --git a/examples/new-meta/tests/read_state.rs b/examples/new-meta/tests/read_state.rs index 2caf1b64948..8060bf13022 100644 --- a/examples/new-meta/tests/read_state.rs +++ b/examples/new-meta/tests/read_state.rs @@ -2,7 +2,7 @@ use demo_new_meta::{ MessageInitIn, Person, Wallet, META_EXPORTS_V1, META_EXPORTS_V2, META_WASM_V1, META_WASM_V2, }; use gstd::Encode; -use gtest::{state_args, state_args_encoded, Program, System}; +use gtest::{constants::DEFAULT_USER_ALICE, state_args, state_args_encoded, Program, System}; #[test] fn read_state_bytes_returns_full_state() { @@ -183,10 +183,9 @@ fn read_state_with_two_args_wasm_func_returns_transformed_state() { } fn initialize_current_program(system: &System) -> Program { - const SOME_USER_ID: u64 = 3; let program = Program::current(system); program.send( - SOME_USER_ID, + DEFAULT_USER_ALICE, MessageInitIn { amount: 123, currency: "USD".into(), diff --git a/examples/node/src/lib.rs b/examples/node/src/lib.rs index 3718cbd44fe..a0d4ff32ebe 100644 --- a/examples/node/src/lib.rs +++ b/examples/node/src/lib.rs @@ -61,14 +61,14 @@ mod wasm; #[cfg(test)] mod tests { use super::{Initialization, Operation, Reply, Request}; - use gtest::{Log, Program, System}; + use gtest::{constants::DEFAULT_USER_ALICE, Log, Program, System}; #[test] fn test_message_send_to_failed_program() { let system = System::new(); system.init_logger(); - let from = 42; + let from = DEFAULT_USER_ALICE; let program = Program::current(&system); let msg_id = program.send(from, Request::IsReady); @@ -81,7 +81,7 @@ mod tests { let system = System::new(); system.init_logger(); - let from = 42; + let from = DEFAULT_USER_ALICE; let program = Program::current(&system); let msg_id = program.send(from, Initialization { status: 5 }); @@ -96,7 +96,7 @@ mod tests { let system = System::new(); system.init_logger(); - let from = 42; + let from = DEFAULT_USER_ALICE; let program = Program::current(&system); program.send(from, Initialization { status: 5 }); @@ -131,7 +131,7 @@ mod tests { let system = System::new(); system.init_logger(); - let from = 42; + let from = DEFAULT_USER_ALICE; let program_1_id = 1; let program_2_id = 2; diff --git a/examples/program-factory/src/lib.rs b/examples/program-factory/src/lib.rs index 64b0f2ce91a..3a83d940451 100644 --- a/examples/program-factory/src/lib.rs +++ b/examples/program-factory/src/lib.rs @@ -55,7 +55,11 @@ mod tests { extern crate std; use super::*; - use gtest::{calculate_program_id, constants::UNITS, Program, System}; + use gtest::{ + calculate_program_id, + constants::{DEFAULT_USER_ALICE, UNITS}, + Program, System, + }; use std::io::Write; // Creates a new factory and initializes it. @@ -67,7 +71,7 @@ mod tests { // Instantiate factory let factory = Program::current_with_id(sys, 100); - let user_id = 10001; + let user_id = DEFAULT_USER_ALICE; sys.mint_to(user_id, 100 * UNITS); // Send `init` msg to factory @@ -98,7 +102,7 @@ mod tests { let factory = prepare_factory(&sys); // Send `handle` msg to factory to create a new child - let msg_id = factory.send_bytes(10001, CreateProgram::Default.encode()); + let msg_id = factory.send_bytes(DEFAULT_USER_ALICE, CreateProgram::Default.encode()); let res = sys.run_next_block(); let child_id_expected = calculate_program_id(CHILD_CODE_HASH.into(), &0i32.to_le_bytes(), Some(msg_id)); @@ -116,7 +120,7 @@ mod tests { let payload = CreateProgram::Custom(vec![(CHILD_CODE_HASH, salt.to_vec(), 100_000_000)]); // Send `handle` msg to factory to create a new child - let msg_id = factory.send_bytes(10001, payload.encode()); + let msg_id = factory.send_bytes(DEFAULT_USER_ALICE, payload.encode()); let res = sys.run_next_block(); let child_id_expected = calculate_program_id(CHILD_CODE_HASH.into(), &salt, Some(msg_id)); @@ -125,7 +129,7 @@ mod tests { assert!(sys.is_active_program(child_id_expected)); // Send `handle` msg to create a duplicate - let msg_id = factory.send_bytes(10001, payload.encode()); + let msg_id = factory.send_bytes(DEFAULT_USER_ALICE, payload.encode()); let res = sys.run_next_block(); let child_id_expected = calculate_program_id(CHILD_CODE_HASH.into(), &salt, Some(msg_id)); @@ -146,7 +150,7 @@ mod tests { let non_existing_code_hash = [10u8; 32]; let salt = b"some_salt"; let payload = CreateProgram::Custom(vec![(non_existing_code_hash, salt.to_vec(), 100_000)]); - let msg_id = factory.send_bytes(10001, payload.encode()); + let msg_id = factory.send_bytes(DEFAULT_USER_ALICE, payload.encode()); let res = sys.run_next_block(); let fictional_program_id = calculate_program_id(non_existing_code_hash.into(), salt, Some(msg_id)); @@ -171,7 +175,7 @@ mod tests { b"some_salt".to_vec(), 100_000, )]); - factory.send_bytes(10001, payload.encode()); + factory.send_bytes(DEFAULT_USER_ALICE, payload.encode()); let _ = sys.run_next_block(); } } diff --git a/examples/reserve-gas/src/lib.rs b/examples/reserve-gas/src/lib.rs index ee335804869..b8c2a80fc67 100644 --- a/examples/reserve-gas/src/lib.rs +++ b/examples/reserve-gas/src/lib.rs @@ -69,7 +69,7 @@ mod wasm; mod tests { use crate::InitAction; use alloc::vec; - use gtest::{Program, System}; + use gtest::{constants::DEFAULT_USER_ALICE, Program, System}; #[test] fn program_can_be_initialized() { @@ -79,7 +79,7 @@ mod tests { let program = Program::current(&system); let msg_id = program.send( - 0, + DEFAULT_USER_ALICE, InitAction::Normal(vec![ // orphan reservation; will be removed automatically (50_000, 3), diff --git a/examples/signal-entry/src/lib.rs b/examples/signal-entry/src/lib.rs index 67dfbeb6573..1eec1a3dfc9 100644 --- a/examples/signal-entry/src/lib.rs +++ b/examples/signal-entry/src/lib.rs @@ -66,14 +66,14 @@ mod wasm; #[cfg(test)] mod tests { use crate::HandleAction; - use gtest::{Log, Program, System}; + use gtest::{constants::DEFAULT_USER_ALICE, Log, Program, System}; #[test] fn signal_can_be_sent() { let system = System::new(); system.init_logger(); - let user_id = 42; + let user_id = DEFAULT_USER_ALICE; let program = Program::current(&system); // Initialize program diff --git a/examples/stack-allocations/src/lib.rs b/examples/stack-allocations/src/lib.rs index 371fc252514..a36fad6b8df 100644 --- a/examples/stack-allocations/src/lib.rs +++ b/examples/stack-allocations/src/lib.rs @@ -64,7 +64,7 @@ mod wasm; #[cfg(test)] mod tests { use super::*; - use gtest::{Program, System}; + use gtest::{constants::DEFAULT_USER_ALICE, Program, System}; use parity_scale_codec::Decode; use rand::{Rng, SeedableRng}; @@ -84,7 +84,7 @@ mod tests { assert!(MAX_ACTIONS_AMOUNT * HANDLE_DATA_SIZE <= 64 * 1024 * 10); } - let from = 42; + let from = DEFAULT_USER_ALICE; let system = System::new(); system.init_logger(); diff --git a/examples/syscall-error/src/lib.rs b/examples/syscall-error/src/lib.rs index 2f2e3f5080e..bd5437cb12a 100644 --- a/examples/syscall-error/src/lib.rs +++ b/examples/syscall-error/src/lib.rs @@ -33,7 +33,7 @@ mod wasm; mod tests { extern crate std; - use gtest::{Program, System}; + use gtest::{constants::DEFAULT_USER_ALICE, Program, System}; #[test] fn program_can_be_initialized() { @@ -41,7 +41,7 @@ mod tests { system.init_logger(); let program = Program::current(&system); - let msg_id = program.send_bytes(0, b"dummy"); + let msg_id = program.send_bytes(DEFAULT_USER_ALICE, b"dummy"); let res = system.run_next_block(); assert!(res.succeed.contains(&msg_id)); } diff --git a/examples/wait_wake/src/lib.rs b/examples/wait_wake/src/lib.rs index 25daabcfb6f..29f570e9cce 100644 --- a/examples/wait_wake/src/lib.rs +++ b/examples/wait_wake/src/lib.rs @@ -42,7 +42,7 @@ mod tests { extern crate std; use super::Request; - use gtest::{Log, Program, System}; + use gtest::{constants::DEFAULT_USER_ALICE, Log, Program, System}; #[test] fn program_can_be_initialized() { @@ -51,7 +51,7 @@ mod tests { let program = Program::current(&system); - let from = 42; + let from = DEFAULT_USER_ALICE; program.send_bytes(from, b"init"); let res = system.run_next_block(); @@ -64,7 +64,7 @@ mod tests { let system = System::new(); system.init_logger(); - let from = 42; + let from = DEFAULT_USER_ALICE; let program = Program::current(&system); program.send_bytes(from, b"init"); @@ -102,7 +102,7 @@ mod tests { let system = System::new(); system.init_logger(); - let from = 42; + let from = DEFAULT_USER_ALICE; let program_1 = Program::current(&system); program_1.send_bytes(from, b"init"); diff --git a/examples/waiter/tests/mx_lock_access.rs b/examples/waiter/tests/mx_lock_access.rs index f00269db7fe..498426e713f 100644 --- a/examples/waiter/tests/mx_lock_access.rs +++ b/examples/waiter/tests/mx_lock_access.rs @@ -1,8 +1,8 @@ use demo_waiter::{Command, LockContinuation, LockStaticAccessSubcommand, MxLockContinuation}; use gear_core::ids::MessageId; -use gtest::{Program, System}; +use gtest::{constants::DEFAULT_USER_ALICE, Program, System}; -pub const USER_ID: u64 = 10; +pub const USER_ID: u64 = DEFAULT_USER_ALICE; #[test] fn drop_mx_lock_guard_from_different_msg_fails() { diff --git a/examples/waiter/tests/rw_lock_access.rs b/examples/waiter/tests/rw_lock_access.rs index a17d2f8fd30..ff50d9b810c 100644 --- a/examples/waiter/tests/rw_lock_access.rs +++ b/examples/waiter/tests/rw_lock_access.rs @@ -2,9 +2,9 @@ use demo_waiter::{ Command, LockContinuation, LockStaticAccessSubcommand, RwLockContinuation, RwLockType, }; use gear_core::ids::MessageId; -use gtest::{Program, System}; +use gtest::{constants::DEFAULT_USER_ALICE, Program, System}; -pub const USER_ID: u64 = 10; +pub const USER_ID: u64 = DEFAULT_USER_ALICE; #[test] fn drop_r_lock_guard_from_different_msg_fails() { diff --git a/gtest/src/program.rs b/gtest/src/program.rs index f4798a1cc5b..e6aca8f9b88 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -991,12 +991,12 @@ mod tests { sys.mint_to(sender2, 20 * EXISTENTIAL_DEPOSIT); // Top-up receiver balance - let mut receiver_expected_balance = 3 * EXISTENTIAL_DEPOSIT; + let mut receiver_expected_balance = 10 * EXISTENTIAL_DEPOSIT; sys.mint_to(receiver, receiver_expected_balance); let prog = Program::from_binary_with_id(&sys, 137, demo_piggy_bank::WASM_BINARY); - prog.send_with_gas(receiver, b"init", 1_000_000, 0); + prog.send_bytes(receiver, b"init"); receiver_expected_balance -= sys.run_next_block().spent_value() + EXISTENTIAL_DEPOSIT; assert_eq!(prog.balance(), EXISTENTIAL_DEPOSIT); @@ -1027,7 +1027,7 @@ mod tests { ); // Request to smash the piggy bank and send the value to the receiver address - prog.send_bytes_with_gas(receiver, b"smash", 100_000_000, 0); + prog.send_bytes(receiver, b"smash"); let res = sys.run_next_block(); receiver_expected_balance -= res.spent_value(); let reply_to_id = { From e66c7e74bed65ca80e9391a004a28bf5ed5129f6 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 13 Aug 2024 15:45:58 +0400 Subject: [PATCH 19/29] Fix tests II --- gtest/src/manager.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index cc0b015e41b..e0e5f14fbfb 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -1377,7 +1377,13 @@ impl JournalHandler for ExtManager { ); // Transfer the ED from the program-creator to the new program - self.send_value(program_id, Some(candidate_id), EXISTENTIAL_DEPOSIT); + Balance::transfer( + &mut self.actors, + program_id, + candidate_id, + EXISTENTIAL_DEPOSIT, + true, + ); // Set ED lock self.actors From 8edf691a03a929cf0fbddb1717cccbe123cc114f Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 13 Aug 2024 23:09:09 +0400 Subject: [PATCH 20/29] Add test and additional check --- gtest/src/balance.rs | 11 ++++++++++- gtest/src/program.rs | 19 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/gtest/src/balance.rs b/gtest/src/balance.rs index 3deaf9e2497..72d70b642a7 100644 --- a/gtest/src/balance.rs +++ b/gtest/src/balance.rs @@ -22,7 +22,7 @@ use std::collections::HashMap; use gear_common::{Gas, GasMultiplier, ProgramId}; -use crate::{constants::Value, manager::Actors, GAS_MULTIPLIER}; +use crate::{constants::Value, manager::Actors, EXISTENTIAL_DEPOSIT, GAS_MULTIPLIER}; /// Balance of an actor. #[derive(Debug, Clone, Default)] @@ -100,6 +100,15 @@ impl Balance { /// Increase the balance. pub fn increase(&mut self, value: Value) { + if self.total + value < EXISTENTIAL_DEPOSIT { + panic!( + "Failed to increase balance: the sum {} of the total balance {} \ + and the value {value} cannot be lower than the existential deposit", + self.total + value, + self.total, + ); + } + self.total += value; } diff --git a/gtest/src/program.rs b/gtest/src/program.rs index e6aca8f9b88..a5cda3f6aac 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -1263,7 +1263,7 @@ mod tests { sys.init_logger(); let user_id = 42; - let mut user_balance = 2 * EXISTENTIAL_DEPOSIT; + let mut user_balance = 4 * EXISTENTIAL_DEPOSIT; sys.mint_to(user_id, user_balance); let prog_id = 137; @@ -1422,4 +1422,21 @@ mod tests { assert!(res.succeed.contains(&msg_id)); assert!(mailbox.contains(&Log::builder().payload_bytes(payload).source(new_prog_id))); } + + #[test] + #[should_panic( + expected = "Failed to increase balance: the sum 999850000000 of the total balance 994000000000 \ + and the value 5850000000 cannot be lower than the existential deposit" + )] + fn fails_transfer_when_user_balance_lower_than_ed() { + let sys = System::new(); + sys.init_verbose_logger(); + + let user = 42; + sys.mint_to(user, 2 * EXISTENTIAL_DEPOSIT); + + let prog = Program::from_binary_with_id(&sys, 69, demo_piggy_bank::WASM_BINARY); + prog.send_bytes_with_gas(user, b"init", 1_000_000_000, 0); + sys.run_next_block(); + } } From e2594b22629224b0aa2ed4cfa3c34030ce7947e6 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Wed, 14 Aug 2024 19:45:43 +0400 Subject: [PATCH 21/29] Fix doc-comment --- gtest/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index d27929d48c6..4ad04070c60 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -261,6 +261,7 @@ //! the sender to ensure coverage of the existential deposit and gas costs. //! //! ```no_run +//! # use gtest::constants::EXISTENTIAL_DEPOSIT; //! # let sys = gtest::System::new(); //! let user_id = 42; //! sys.mint_to(user_id, EXISTENTIAL_DEPOSIT * 1000); From 09e7882339e65719cc8ab603fc51fd79d05589ac Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Fri, 16 Aug 2024 00:33:26 +0400 Subject: [PATCH 22/29] refactor Actors storages --- gtest/src/accounts.rs | 169 +++++++++++++ gtest/src/actors.rs | 222 +++++++++++++++++ gtest/src/balance.rs | 232 ------------------ gtest/src/bank.rs | 103 ++++++++ gtest/src/lib.rs | 5 +- gtest/src/manager.rs | 548 ++++++++++-------------------------------- gtest/src/program.rs | 28 +-- gtest/src/system.rs | 67 +++--- 8 files changed, 679 insertions(+), 695 deletions(-) create mode 100644 gtest/src/accounts.rs create mode 100644 gtest/src/actors.rs delete mode 100644 gtest/src/balance.rs create mode 100644 gtest/src/bank.rs diff --git a/gtest/src/accounts.rs b/gtest/src/accounts.rs new file mode 100644 index 00000000000..22a4e4fcc2f --- /dev/null +++ b/gtest/src/accounts.rs @@ -0,0 +1,169 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Accounts storage. + +use std::{cell::RefCell, collections::HashMap, fmt}; + +use crate::{default_users_list, Value, DEFAULT_USERS_INITIAL_BALANCE, EXISTENTIAL_DEPOSIT}; +use gear_common::ProgramId; + +fn init_default_accounts(storage: &mut HashMap) { + for &id in default_users_list() { + let id = id.into(); + storage.insert(id, Balance::new(DEFAULT_USERS_INITIAL_BALANCE)); + } +} + +thread_local! { + static ACCOUNT_STORAGE: RefCell> = RefCell::new({ + let mut storage = HashMap::new(); + init_default_accounts(&mut storage); + storage + }); +} + +#[derive(Debug)] +struct Balance { + amount: Value, +} + +impl Balance { + fn new(amount: Value) -> Self { + Self { amount } + } + + fn balance(&self) -> Value { + self.amount + } + + fn reducible_balance(&self) -> Value { + self.amount - EXISTENTIAL_DEPOSIT + } + + fn decrease(&mut self, amount: Value) { + self.amount -= amount; + } + + fn increase(&mut self, amount: Value) { + self.amount += amount; + } +} + +pub(crate) struct Accounts; + +impl Accounts { + // Checks if account by program id exists. + pub(crate) fn is_exist(id: ProgramId) -> bool { + Self::balance(id) != 0 + } + + // Returns account balance. + pub(crate) fn balance(id: ProgramId) -> Value { + ACCOUNT_STORAGE.with_borrow(|storage| { + storage + .get(&id) + .map(|balance| balance.balance()) + .unwrap_or_default() + }) + } + + // Returns account reducible balance. + pub(crate) fn reducible_balance(id: ProgramId) -> Value { + ACCOUNT_STORAGE.with_borrow(|storage| { + storage + .get(&id) + .map(|balance| balance.reducible_balance()) + .unwrap_or_default() + }) + } + + // Decreases account balance. + pub(crate) fn decrease(id: ProgramId, amount: Value, keep_alive: bool) { + ACCOUNT_STORAGE.with_borrow_mut(|storage| { + if let Some(balance) = storage.get_mut(&id) { + if keep_alive && balance.reducible_balance() < amount { + panic!( + "Not enough balance to decrease, reducible: {}, value: {amount}", + balance.reducible_balance(), + ); + } + if !keep_alive && balance.balance() < amount { + panic!( + "Not enough balance to decrease, total: {}, value: {amount}", + balance.balance(), + ); + } + + balance.decrease(amount); + if balance.balance() < EXISTENTIAL_DEPOSIT { + log::debug!( + "Removing account {id:?} with balance {} below the existential deposit", + balance.balance() + ); + storage.remove(&id); + } + } + }); + } + + // Increases account balance. + pub(crate) fn increase(id: ProgramId, amount: Value) { + ACCOUNT_STORAGE.with_borrow_mut(|storage| { + let balance = storage.entry(id).or_insert(Balance::new(0)); + + if balance.balance() + amount < EXISTENTIAL_DEPOSIT { + panic!( + "Failed to increase balance: the sum {} of the total balance {} \ + and the value {} cannot be lower than the existential deposit", + balance.balance() + amount, + balance.balance(), + amount + ); + } + + balance.increase(amount); + }); + } + + // Transfers value between accounts. + pub(crate) fn transfer(from: ProgramId, to: ProgramId, amount: Value, keep_alive: bool) { + Self::decrease(from, amount, keep_alive); + Self::increase(to, amount); + } + + // Overrides account balance. + pub(crate) fn override_balance(id: ProgramId, amount: Value) { + if amount < EXISTENTIAL_DEPOSIT { + panic!( + "Failed to override balance: the amount {} cannot be lower than the existential deposit", + amount + ); + } + + ACCOUNT_STORAGE.with_borrow_mut(|storage| { + storage.insert(id, Balance::new(amount)); + }); + } +} + +impl fmt::Debug for Accounts { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ACCOUNT_STORAGE.with_borrow(|storage| f.debug_map().entries(storage.iter()).finish()) + } +} diff --git a/gtest/src/actors.rs b/gtest/src/actors.rs new file mode 100644 index 00000000000..51dbcb4db63 --- /dev/null +++ b/gtest/src/actors.rs @@ -0,0 +1,222 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Actors storage. + +use std::{cell::RefCell, collections::BTreeMap, fmt}; + +use core_processor::common::ExecutableActorData; +use gear_common::{CodeId, GearPage, MessageId, PageBuf, ProgramId}; +use gear_core::{ + code::InstrumentedCode, + pages::{numerated::tree::IntervalsTree, WasmPage}, + reservation::GasReservationMap, +}; + +use crate::WasmProgram; + +thread_local! { + static ACTORS_STORAGE: RefCell> = RefCell::new(Default::default()); +} + +pub(crate) struct Actors; + +impl Actors { + // Accesses actor by program id. + #[track_caller] + pub(crate) fn access( + program_id: ProgramId, + access: impl FnOnce(Option<&TestActor>) -> R, + ) -> R { + ACTORS_STORAGE.with_borrow(|storage| access(storage.get(&program_id))) + } + + // Modifies actor by program id. + #[track_caller] + pub(crate) fn modify( + program_id: ProgramId, + modify: impl FnOnce(Option<&mut TestActor>) -> R, + ) -> R { + ACTORS_STORAGE.with_borrow_mut(|storage| modify(storage.get_mut(&program_id))) + } + + // Inserts actor by program id. + pub(crate) fn insert(program_id: ProgramId, actor: TestActor) -> Option { + ACTORS_STORAGE.with_borrow_mut(|storage| storage.insert(program_id, actor)) + } + + // Checks if actor by program id exists. + pub(crate) fn contains_key(program_id: ProgramId) -> bool { + ACTORS_STORAGE.with_borrow(|storage| storage.contains_key(&program_id)) + } + + // Checks if actor by program id is a user. + pub(crate) fn is_user(id: ProgramId) -> bool { + // Non-existent program is a user + ACTORS_STORAGE.with_borrow(|storage| storage.get(&id).is_none()) + } + + // Checks if actor by program id is active. + pub(crate) fn is_active_program(id: ProgramId) -> bool { + ACTORS_STORAGE.with_borrow(|storage| { + matches!( + storage.get(&id), + Some(TestActor::Initialized(_) | TestActor::Uninitialized(_, _)) + ) + }) + } + + // Checks if actor by program id is a program. + pub(crate) fn is_program(id: ProgramId) -> bool { + // if it's not a user, then it's a program + !Self::is_user(id) + } + + // Returns all program ids. + pub(crate) fn program_ids() -> Vec { + ACTORS_STORAGE.with_borrow(|storage| storage.keys().copied().collect()) + } +} + +impl fmt::Debug for Actors { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ACTORS_STORAGE.with_borrow(|storage| f.debug_map().entries(storage.iter()).finish()) + } +} + +#[derive(Debug)] +pub(crate) enum TestActor { + Initialized(Program), + // Contract: program is always `Some`, option is used to take ownership + Uninitialized(Option, Option), + Dormant, +} + +impl TestActor { + // Creates a new uninitialized actor. + pub(crate) fn new(init_message_id: Option, program: Program) -> Self { + TestActor::Uninitialized(init_message_id, Some(program)) + } + + // # Panics + // If actor is initialized or dormant + #[track_caller] + pub(crate) fn set_initialized(&mut self) { + assert!( + self.is_uninitialized(), + "can't transmute actor, which isn't uninitialized" + ); + + if let TestActor::Uninitialized(_, maybe_prog) = self { + *self = TestActor::Initialized( + maybe_prog + .take() + .expect("actor storage contains only `Some` values by contract"), + ); + } + } + + // Checks if actor is dormant. + pub(crate) fn is_dormant(&self) -> bool { + matches!(self, TestActor::Dormant) + } + + // Checks if actor is initialized. + pub(crate) fn is_uninitialized(&self) -> bool { + matches!(self, TestActor::Uninitialized(..)) + } + + // Returns `Some` if actor contains genuine program. + pub(crate) fn genuine_program(&self) -> Option<&GenuineProgram> { + match self { + TestActor::Initialized(Program::Genuine(program)) + | TestActor::Uninitialized(_, Some(Program::Genuine(program))) => Some(program), + _ => None, + } + } + + // Returns `Some` if actor contains genuine program but mutable. + pub(crate) fn genuine_program_mut(&mut self) -> Option<&mut GenuineProgram> { + match self { + TestActor::Initialized(Program::Genuine(program)) + | TestActor::Uninitialized(_, Some(Program::Genuine(program))) => Some(program), + _ => None, + } + } + + // Returns pages data of genuine program. + pub(crate) fn get_pages_data(&self) -> Option<&BTreeMap> { + self.genuine_program().map(|program| &program.pages_data) + } + + // Returns pages data of genuine program but mutable. + pub(crate) fn get_pages_data_mut(&mut self) -> Option<&mut BTreeMap> { + self.genuine_program_mut() + .map(|program| &mut program.pages_data) + } + + // Takes ownership over mock program, putting `None` value instead of it. + pub(crate) fn take_mock(&mut self) -> Option> { + match self { + TestActor::Initialized(Program::Mock(mock)) + | TestActor::Uninitialized(_, Some(Program::Mock(mock))) => mock.take(), + _ => None, + } + } + + // Gets a new executable actor derived from the inner program. + pub(crate) fn get_executable_actor_data( + &self, + ) -> Option<(ExecutableActorData, InstrumentedCode)> { + self.genuine_program().map(|program| { + ( + ExecutableActorData { + allocations: program.allocations.clone(), + code_id: program.code_id, + code_exports: program.code.exports().clone(), + static_pages: program.code.static_pages(), + gas_reservation_map: program.gas_reservation_map.clone(), + memory_infix: Default::default(), + }, + program.code.clone(), + ) + }) + } +} + +#[derive(Debug)] +pub(crate) struct GenuineProgram { + pub code_id: CodeId, + pub code: InstrumentedCode, + pub allocations: IntervalsTree, + pub pages_data: BTreeMap, + pub gas_reservation_map: GasReservationMap, +} + +#[derive(Debug)] +pub(crate) enum Program { + Genuine(GenuineProgram), + // Contract: is always `Some`, option is used to take ownership + Mock(Option>), +} + +impl Program { + pub(crate) fn new_mock(mock: impl WasmProgram + 'static) -> Self { + Program::Mock(Some(Box::new(mock))) + } +} diff --git a/gtest/src/balance.rs b/gtest/src/balance.rs deleted file mode 100644 index 72d70b642a7..00000000000 --- a/gtest/src/balance.rs +++ /dev/null @@ -1,232 +0,0 @@ -// This file is part of Gear. - -// Copyright (C) 2024 Gear Technologies Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Balance management. - -use std::collections::HashMap; - -use gear_common::{Gas, GasMultiplier, ProgramId}; - -use crate::{constants::Value, manager::Actors, EXISTENTIAL_DEPOSIT, GAS_MULTIPLIER}; - -/// Balance of an actor. -#[derive(Debug, Clone, Default)] -pub struct Balance { - total: Value, - // Primary used for ED locking for programs - locked: Value, -} - -impl Balance { - /// Create a new balance. - pub fn new(total: Value) -> Self { - Self { total, locked: 0 } - } - - /// Lock the balance. - #[track_caller] - pub fn set_lock(&mut self, value: Value) { - if self.available() < value { - panic!( - "Trying to lock more then available balance, total: {}, lock: {}", - self.available(), - value - ); - } - self.locked = value; - } - - /// Unlock the balance. - pub fn empty() -> Self { - Self { - total: 0, - locked: 0, - } - } - - /// Get the available balance. - #[track_caller] - pub fn available(&self) -> Value { - self.total - self.locked - } - - /// Get the total balance. - pub fn total(&self) -> Value { - self.total - } - - /// Decrease the balance. - /// - /// If `respect_lock` is true, the total balance will not be decreased lower - /// than the locked value. If `respect_lock` is false, the total balance - /// will be decreased lower than the locked value. - #[track_caller] - pub fn decrease(&mut self, value: u128, respect_lock: bool) { - if respect_lock && self.total - value < self.locked { - panic!( - "Not enough balance to decrease, available: {}, value: {}", - self.available(), - value - ); - } - - if !respect_lock && self.total < value { - panic!( - "Not enough balance to decrease, total: {}, value: {}", - self.total, value - ); - } - - self.total -= value; - if self.total < self.locked { - self.locked = self.total; - } - } - - /// Increase the balance. - pub fn increase(&mut self, value: Value) { - if self.total + value < EXISTENTIAL_DEPOSIT { - panic!( - "Failed to increase balance: the sum {} of the total balance {} \ - and the value {value} cannot be lower than the existential deposit", - self.total + value, - self.total, - ); - } - - self.total += value; - } - - #[track_caller] - /// Transfer the balance. - pub(crate) fn transfer( - actors: &mut Actors, - from: ProgramId, - to: ProgramId, - value: u128, - keep_alive: bool, - ) { - let mut actors = actors.borrow_mut(); - let (_, from) = actors - .get_mut(&from) - .unwrap_or_else(|| panic!("Sender actor id {from:?} should exist")); - from.decrease(value, keep_alive); - - let (_, to) = actors - .get_mut(&to) - .unwrap_or_else(|| panic!("Receiver actor id {to:?} should exist")); - to.increase(value); - } -} - -impl PartialEq for Balance { - fn eq(&self, other: &Value) -> bool { - self.total == *other - } -} - -#[derive(Default, Debug)] -struct AccountBalance { - gas: Value, - value: Value, -} - -/// `gtest` bank. -#[derive(Default, Debug)] -pub struct Bank { - accounts: HashMap, -} - -impl Bank { - /// Create a new bank. - #[track_caller] - pub fn deposit_value( - &mut self, - from: &mut Balance, - to: ProgramId, - value: Value, - keep_alive: bool, - ) { - from.decrease(value, keep_alive); - self.accounts - .entry(to) - .or_insert(AccountBalance { gas: 0, value: 0 }) - .value += value; - } - - /// Deposit gas. - #[track_caller] - pub fn deposit_gas(&mut self, from: &mut Balance, to: ProgramId, gas: Gas, keep_alive: bool) { - let gas_value = GAS_MULTIPLIER.gas_to_value(gas); - from.decrease(gas_value, keep_alive); - self.accounts - .entry(to) - .or_insert(AccountBalance { gas: 0, value: 0 }) - .gas += gas_value; - } - - /// Withdraw gas. - #[track_caller] - pub fn spend_gas(&mut self, from: ProgramId, gas: Gas, multiplier: GasMultiplier) { - let gas_value = multiplier.gas_to_value(gas); - self.accounts - .get_mut(&from) - .unwrap_or_else(|| panic!("Bank::spend_gas: actor id {from:?} not found in bank")) - .gas -= gas_value; - } - - /// Withdraw value. - #[track_caller] - pub fn spend_gas_to( - &mut self, - from: ProgramId, - to: &mut Balance, - gas: Gas, - multiplier: GasMultiplier, - ) { - self.withdraw_gas(from, to, gas, multiplier) - } - - /// Withdraw gas. - #[track_caller] - pub fn withdraw_gas( - &mut self, - from: ProgramId, - to: &mut Balance, - gas_left: Gas, - multiplier: GasMultiplier, - ) { - let gas_left_value = multiplier.gas_to_value(gas_left); - self.accounts - .get_mut(&from) - .unwrap_or_else(|| panic!("Bank::withdraw_gas: actor id {from:?} not found in bank")) - .gas -= gas_left_value; - let value = multiplier.gas_to_value(gas_left); - to.increase(value); - } - - /// Transfer value. - #[track_caller] - pub fn transfer_value(&mut self, from: ProgramId, to: &mut Balance, value: Value) { - self.accounts - .get_mut(&from) - .unwrap_or_else(|| panic!("Bank::transfer_value: actor id {from:?} not found in bank")) - .value -= value; - to.increase(value); - } -} diff --git a/gtest/src/bank.rs b/gtest/src/bank.rs new file mode 100644 index 00000000000..5a7d82fe914 --- /dev/null +++ b/gtest/src/bank.rs @@ -0,0 +1,103 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! `gtest` bank + +use std::collections::HashMap; + +use gear_common::{Gas, GasMultiplier, ProgramId}; + +use crate::{accounts::Accounts, constants::Value, GAS_MULTIPLIER}; + +#[derive(Default, Debug)] +struct BankBalance { + gas: Value, + value: Value, +} + +/// `gtest` bank. +#[derive(Default, Debug)] +pub(crate) struct Bank { + accounts: HashMap, +} + +impl Bank { + // Create a new bank. + #[track_caller] + pub(crate) fn deposit_value(&mut self, id: ProgramId, value: Value, keep_alive: bool) { + Accounts::decrease(id, value, keep_alive); + self.accounts + .entry(id) + .or_insert(BankBalance { gas: 0, value: 0 }) + .value += value; + } + + // Deposit gas. + #[track_caller] + pub(crate) fn deposit_gas(&mut self, id: ProgramId, gas: Gas, keep_alive: bool) { + let gas_value = GAS_MULTIPLIER.gas_to_value(gas); + Accounts::decrease(id, gas_value, keep_alive); + self.accounts + .entry(id) + .or_insert(BankBalance { gas: 0, value: 0 }) + .gas += gas_value; + } + + // Withdraw gas. + #[track_caller] + pub(crate) fn spend_gas( + &mut self, + from: ProgramId, + gas: Gas, + multiplier: GasMultiplier, + ) { + let gas_value = multiplier.gas_to_value(gas); + self.accounts + .get_mut(&from) + .unwrap_or_else(|| panic!("Bank::spend_gas: actor id {from:?} not found in bank")) + .gas -= gas_value; + } + + // Withdraw gas. + #[track_caller] + pub(crate) fn withdraw_gas( + &mut self, + from: ProgramId, + to: ProgramId, + gas_left: Gas, + multiplier: GasMultiplier, + ) { + let gas_left_value = multiplier.gas_to_value(gas_left); + self.accounts + .get_mut(&from) + .unwrap_or_else(|| panic!("Bank::withdraw_gas: actor id {from:?} not found in bank")) + .gas -= gas_left_value; + let value = multiplier.gas_to_value(gas_left); + Accounts::increase(to, value); + } + + // Transfer value. + #[track_caller] + pub(crate) fn transfer_value(&mut self, from: ProgramId, to: ProgramId, value: Value) { + self.accounts + .get_mut(&from) + .unwrap_or_else(|| panic!("Bank::transfer_value: actor id {from:?} not found in bank")) + .value -= value; + Accounts::increase(to, value); + } +} diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index 4ad04070c60..87995cd87e7 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -487,7 +487,9 @@ #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] #![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] -pub mod balance; +mod accounts; +mod actors; +mod bank; mod blocks; mod error; mod gas_tree; @@ -508,6 +510,7 @@ pub use program::{ }; pub use system::System; +pub use constants::Value; pub(crate) use constants::*; /// Module containing constants of Gear protocol. diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 2db3e4de743..b59e612c8a1 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -17,24 +17,25 @@ // along with this program. If not, see . use crate::{ - balance::Bank, + accounts::Accounts, + actors::{Actors, GenuineProgram, Program, TestActor}, + bank::Bank, blocks::BlocksManager, - default_users_list, + constants::Value, gas_tree::GasTreeManager, log::{BlockRunResult, CoreLog}, mailbox::MailboxManager, program::{Gas, WasmProgram}, - Result, TestError, DEFAULT_USERS_INITIAL_BALANCE, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, - EXISTENTIAL_DEPOSIT, GAS_ALLOWANCE, GAS_MULTIPLIER, HOST_FUNC_READ_COST, - HOST_FUNC_WRITE_AFTER_READ_COST, HOST_FUNC_WRITE_COST, INITIAL_RANDOM_SEED, - LOAD_ALLOCATIONS_PER_INTERVAL, LOAD_PAGE_STORAGE_DATA_COST, MAILBOX_THRESHOLD, - MAX_RESERVATIONS, MODULE_CODE_SECTION_INSTANTIATION_BYTE_COST, - MODULE_DATA_SECTION_INSTANTIATION_BYTE_COST, MODULE_ELEMENT_SECTION_INSTANTIATION_BYTE_COST, - MODULE_GLOBAL_SECTION_INSTANTIATION_BYTE_COST, MODULE_INSTRUMENTATION_BYTE_COST, - MODULE_INSTRUMENTATION_COST, MODULE_TABLE_SECTION_INSTANTIATION_BYTE_COST, - MODULE_TYPE_SECTION_INSTANTIATION_BYTE_COST, READ_COST, READ_PER_BYTE_COST, RESERVATION_COST, - RESERVE_FOR, SIGNAL_READ_COST, SIGNAL_WRITE_AFTER_READ_COST, SIGNAL_WRITE_COST, VALUE_PER_GAS, - WAITLIST_COST, WRITE_COST, + Result, TestError, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, EXISTENTIAL_DEPOSIT, + GAS_ALLOWANCE, GAS_MULTIPLIER, HOST_FUNC_READ_COST, HOST_FUNC_WRITE_AFTER_READ_COST, + HOST_FUNC_WRITE_COST, INITIAL_RANDOM_SEED, LOAD_ALLOCATIONS_PER_INTERVAL, + LOAD_PAGE_STORAGE_DATA_COST, MAILBOX_THRESHOLD, MAX_RESERVATIONS, + MODULE_CODE_SECTION_INSTANTIATION_BYTE_COST, MODULE_DATA_SECTION_INSTANTIATION_BYTE_COST, + MODULE_ELEMENT_SECTION_INSTANTIATION_BYTE_COST, MODULE_GLOBAL_SECTION_INSTANTIATION_BYTE_COST, + MODULE_INSTRUMENTATION_BYTE_COST, MODULE_INSTRUMENTATION_COST, + MODULE_TABLE_SECTION_INSTANTIATION_BYTE_COST, MODULE_TYPE_SECTION_INSTANTIATION_BYTE_COST, + READ_COST, READ_PER_BYTE_COST, RESERVATION_COST, RESERVE_FOR, SIGNAL_READ_COST, + SIGNAL_WRITE_AFTER_READ_COST, SIGNAL_WRITE_COST, VALUE_PER_GAS, WAITLIST_COST, WRITE_COST, }; use core_processor::{ common::*, @@ -56,7 +57,7 @@ use gear_core::{ numerated::{iterators::IntervalIterator, tree::IntervalsTree}, GearPage, WasmPage, }, - reservation::{GasReservationMap, GasReserver}, + reservation::GasReserver, }; use gear_core_errors::{ErrorReplyReason, SignalCode, SimpleExecutionError}; use gear_lazy_pages_common::LazyPagesCosts; @@ -64,206 +65,14 @@ use gear_lazy_pages_native_interface::LazyPagesNative; use gear_wasm_instrument::gas_metering::Schedule; use rand::{rngs::StdRng, RngCore, SeedableRng}; use std::{ - cell::{Ref, RefCell, RefMut}, collections::{BTreeMap, BTreeSet, HashMap, VecDeque}, convert::TryInto, mem, - rc::Rc, }; -pub use crate::balance::Balance; - const OUTGOING_LIMIT: u32 = 1024; const OUTGOING_BYTES_LIMIT: u32 = 64 * 1024 * 1024; -pub(crate) type Value = u128; - -#[derive(Debug)] -pub(crate) enum TestActor { - Initialized(Program), - // Contract: program is always `Some`, option is used to take ownership - Uninitialized(Option, Option), - Dormant, - User, -} - -impl TestActor { - fn new(init_message_id: Option, program: Program) -> Self { - TestActor::Uninitialized(init_message_id, Some(program)) - } - - // # Panics - // If actor is initialized or dormant - #[track_caller] - fn set_initialized(&mut self) { - assert!( - self.is_uninitialized(), - "can't transmute actor, which isn't uninitialized" - ); - - if let TestActor::Uninitialized(_, maybe_prog) = self { - *self = TestActor::Initialized( - maybe_prog - .take() - .expect("actor storage contains only `Some` values by contract"), - ); - } - } - - fn is_dormant(&self) -> bool { - matches!(self, TestActor::Dormant) - } - - fn is_uninitialized(&self) -> bool { - matches!(self, TestActor::Uninitialized(..)) - } - - pub(crate) fn genuine_program(&self) -> Option<&GenuineProgram> { - match self { - TestActor::Initialized(Program::Genuine(program)) - | TestActor::Uninitialized(_, Some(Program::Genuine(program))) => Some(program), - _ => None, - } - } - - fn genuine_program_mut(&mut self) -> Option<&mut GenuineProgram> { - match self { - TestActor::Initialized(Program::Genuine(program)) - | TestActor::Uninitialized(_, Some(Program::Genuine(program))) => Some(program), - _ => None, - } - } - - pub fn get_pages_data(&self) -> Option<&BTreeMap> { - self.genuine_program().map(|program| &program.pages_data) - } - - fn get_pages_data_mut(&mut self) -> Option<&mut BTreeMap> { - self.genuine_program_mut() - .map(|program| &mut program.pages_data) - } - - // Takes ownership over mock program, putting `None` value instead of it. - fn take_mock(&mut self) -> Option> { - match self { - TestActor::Initialized(Program::Mock(mock)) - | TestActor::Uninitialized(_, Some(Program::Mock(mock))) => mock.take(), - _ => None, - } - } - - // Gets a new executable actor derived from the inner program. - fn get_executable_actor_data(&self) -> Option<(ExecutableActorData, InstrumentedCode)> { - self.genuine_program().map(|program| { - ( - ExecutableActorData { - allocations: program.allocations.clone(), - code_id: program.code_id, - code_exports: program.code.exports().clone(), - static_pages: program.code.static_pages(), - gas_reservation_map: program.gas_reservation_map.clone(), - memory_infix: Default::default(), - }, - program.code.clone(), - ) - }) - } -} - -#[derive(Debug)] -pub(crate) struct GenuineProgram { - pub code_id: CodeId, - pub code: InstrumentedCode, - pub allocations: IntervalsTree, - pub pages_data: BTreeMap, - pub gas_reservation_map: GasReservationMap, -} - -#[derive(Debug)] -pub(crate) enum Program { - Genuine(GenuineProgram), - // Contract: is always `Some`, option is used to take ownership - Mock(Option>), -} - -impl Program { - pub(crate) fn new_mock(mock: impl WasmProgram + 'static) -> Self { - Program::Mock(Some(Box::new(mock))) - } -} - -#[derive(Default, Debug, Clone)] -pub(crate) struct Actors(Rc>>); - -impl Actors { - pub fn new() -> Self { - let mut actors = Actors::default(); - - // Add default users - for &default_user_id in default_users_list() { - actors.insert( - default_user_id.into(), - (TestActor::User, Balance::new(DEFAULT_USERS_INITIAL_BALANCE)), - ); - } - - actors - } - - pub fn borrow(&self) -> Ref<'_, BTreeMap> { - self.0.borrow() - } - - pub fn borrow_mut(&mut self) -> RefMut<'_, BTreeMap> { - self.0.borrow_mut() - } - - fn insert( - &mut self, - program_id: ProgramId, - actor_and_balance: (TestActor, Balance), - ) -> Option<(TestActor, Balance)> { - self.0.borrow_mut().insert(program_id, actor_and_balance) - } - - pub fn contains_key(&self, program_id: &ProgramId) -> bool { - self.0.borrow().contains_key(program_id) - } - - pub fn total_balance(&self, program_id: &ProgramId) -> Value { - self.balance(program_id, true) - } - - #[track_caller] - pub fn set_balance_lock(&mut self, program_id: &ProgramId, value: Value) { - let mut actors = self.0.borrow_mut(); - let balance = &mut actors - .get_mut(program_id) - .expect("Can't find existing program") - .1; - - balance.set_lock(value); - } - - fn balance(&self, program_id: &ProgramId, total: bool) -> Value { - let actors = self.0.borrow(); - - let Some((_, balance)) = &actors.get(program_id) else { - return 0; - }; - - if total { - balance.total() - } else { - balance.available() - } - } - - fn remove(&mut self, program_id: &ProgramId) -> Option<(TestActor, Balance)> { - self.0.borrow_mut().remove(program_id) - } -} - #[derive(Debug, Default)] pub(crate) struct ExtManager { // State metadata @@ -275,7 +84,6 @@ pub(crate) struct ExtManager { pub(crate) id_nonce: u64, // State - pub(crate) actors: Actors, pub(crate) bank: Bank, pub(crate) opt_binaries: BTreeMap>, pub(crate) meta_binaries: BTreeMap>, @@ -302,7 +110,6 @@ impl ExtManager { Self { msg_nonce: 1, id_nonce: 1, - actors: Actors::new(), blocks_manager: BlocksManager::new(), messages_processing_enabled: true, random_data: ( @@ -324,14 +131,11 @@ impl ExtManager { program_id: ProgramId, program: Program, init_message_id: Option, - ) -> Option<(TestActor, Balance)> { + ) -> Option { if let Program::Genuine(GenuineProgram { code, .. }) = &program { self.store_new_code(code.code().to_vec()); } - self.actors.insert( - program_id, - (TestActor::new(init_message_id, program), Balance::default()), - ) + Actors::insert(program_id, TestActor::new(init_message_id, program)) } pub(crate) fn store_new_code(&mut self, code: Vec) -> CodeId { @@ -351,7 +155,7 @@ impl ExtManager { } pub(crate) fn free_id_nonce(&mut self) -> u64 { - while self.actors.contains_key(&self.id_nonce.into()) { + while Actors::contains_key(self.id_nonce.into()) { self.id_nonce += 1; } self.id_nonce @@ -426,19 +230,16 @@ impl ExtManager { program_id: &ProgramId, memory_pages: BTreeMap, ) { - let mut actors = self.actors.borrow_mut(); - let program = &mut actors - .get_mut(program_id) - .unwrap_or_else(|| panic!("Actor {program_id} not found")) - .0; - - let pages_data = program - .get_pages_data_mut() - .expect("No pages data found for program"); - - for (page, buf) in memory_pages { - pages_data.insert(page, buf); - } + Actors::modify(*program_id, |actor| { + let pages_data = actor + .unwrap_or_else(|| panic!("Actor id {program_id:?} not found")) + .get_pages_data_mut() + .expect("No pages data found for program"); + + for (page, buf) in memory_pages { + pages_data.insert(page, buf); + } + }); } pub(crate) fn validate_and_route_dispatch(&mut self, dispatch: Dispatch) -> MessageId { @@ -454,7 +255,7 @@ impl ExtManager { pub(crate) fn route_dispatch(&mut self, dispatch: Dispatch) -> MessageId { let stored_dispatch = dispatch.into_stored(); - if self.is_user(&stored_dispatch.destination()) { + if Actors::is_user(stored_dispatch.destination()) { panic!("Program API only sends message to programs.") } @@ -507,23 +308,33 @@ impl ExtManager { None => break, }; - let mut actors = self.actors.borrow_mut(); - let (actor, balance) = actors - .get_mut(&dispatch.destination()) - .expect("Somehow message queue contains message for user"); - let balance = balance.available(); - - if actor.is_dormant() { - drop(actors); - self.process_dormant(balance, dispatch); - } else if let Some((data, code)) = actor.get_executable_actor_data() { - drop(actors); - self.process_normal(balance, data, code, dispatch); - } else if let Some(mock) = actor.take_mock() { - drop(actors); - self.process_mock(mock, dispatch); - } else { - unreachable!(); + enum DispatchCase { + Dormant, + ExecutableData(ExecutableActorData, InstrumentedCode), + Mock(Box), + } + + let dispatch_case = Actors::modify(dispatch.destination(), |actor| { + let actor = actor + .unwrap_or_else(|| panic!("Somehow message queue contains message for user")); + if actor.is_dormant() { + DispatchCase::Dormant + } else if let Some((data, code)) = actor.get_executable_actor_data() { + DispatchCase::ExecutableData(data, code) + } else if let Some(mock) = actor.take_mock() { + DispatchCase::Mock(mock) + } else { + unreachable!(); + } + }); + let balance = Accounts::reducible_balance(dispatch.destination()); + + match dispatch_case { + DispatchCase::Dormant => self.process_dormant(balance, dispatch), + DispatchCase::ExecutableData(data, code) => { + self.process_normal(balance, data, code, dispatch) + } + DispatchCase::Mock(mock) => self.process_mock(mock, dispatch), } total_processed += 1; @@ -537,19 +348,19 @@ impl ExtManager { let source = dispatch.source(); let destination = dispatch.destination(); - if self.is_program(&source) { + if Actors::is_program(source) { panic!("Sending messages allowed only from users id"); } // User must exist - if !self.actors.contains_key(&source) { + if !Accounts::is_exist(source) { panic!("User {source} doesn't exist; mint value to it first."); } let is_init_msg = dispatch.kind().is_init(); // We charge ED only for init messages let maybe_ed = if is_init_msg { EXISTENTIAL_DEPOSIT } else { 0 }; - let total_balance = self.actors.total_balance(&source); + let balance = Accounts::balance(source); let gas_limit = dispatch .gas_limit() @@ -557,7 +368,7 @@ impl ExtManager { let gas_value = GAS_MULTIPLIER.gas_to_value(gas_limit); // Check sender has enough balance to cover dispatch costs - if total_balance < { dispatch.value() + gas_value + maybe_ed } { + if balance < { dispatch.value() + gas_value + maybe_ed } { panic!( "Insufficient balance: user ({}) tries to send \ ({}) value, ({}) gas and ED {}, while his balance ({:?})", @@ -565,46 +376,31 @@ impl ExtManager { dispatch.value(), gas_value, maybe_ed, - total_balance, + balance, ); } // Charge for program ED upon creation if is_init_msg { - Balance::transfer( - &mut self.actors, - source, - destination, - EXISTENTIAL_DEPOSIT, - false, - ); - - // Set ED lock - self.actors - .set_balance_lock(&destination, EXISTENTIAL_DEPOSIT); + Accounts::transfer(source, destination, EXISTENTIAL_DEPOSIT, false); } - let mut actors = self.actors.borrow_mut(); - let source_balance = &mut actors.get_mut(&source).expect("Can't fail").1; - // Deposit message value - self.bank - .deposit_value(source_balance, source, dispatch.value(), false); + self.bank.deposit_value(source, dispatch.value(), false); // Deposit gas - self.bank - .deposit_gas(source_balance, source, gas_limit, false); + self.bank.deposit_gas(source, gas_limit, false); } #[track_caller] pub(crate) fn route_dispatch_from_task_pool(&mut self, dispatch: Dispatch) { - if self.is_program(&dispatch.destination()) { + if Actors::is_program(dispatch.destination()) { self.dispatches.push_back(dispatch.into_stored()); } else { let message = dispatch.into_parts().1.into_stored(); if let (Ok(mailbox_msg), true) = ( message.clone().try_into(), - self.is_program(&message.source()), + Actors::is_program(message.source()), ) { self.mailbox .insert(mailbox_msg) @@ -622,14 +418,15 @@ impl ExtManager { payload: Vec, program_id: &ProgramId, ) -> Result> { - let mut actors = self.actors.borrow_mut(); - let (actor, _balance) = actors - .get_mut(program_id) - .ok_or_else(|| TestError::ActorNotFound(*program_id))?; - - if let Some((data, code)) = actor.get_executable_actor_data() { - drop(actors); + let executable_actor_data = Actors::modify(*program_id, |actor| { + if let Some(actor) = actor { + Ok(actor.get_executable_actor_data()) + } else { + Err(TestError::ActorNotFound(*program_id)) + } + })?; + if let Some((data, code)) = executable_actor_data { core_processor::informational::execute_for_reply::, _>( String::from("state"), code, @@ -640,7 +437,9 @@ impl ExtManager { self.blocks_manager.get(), ) .map_err(TestError::ReadStateError) - } else if let Some(mut program_mock) = actor.take_mock() { + } else if let Some(mut program_mock) = + Actors::modify(*program_id, |actor| actor.unwrap().take_mock()) + { program_mock .state() .map_err(|err| TestError::ReadStateError(err.into())) @@ -683,29 +482,6 @@ impl ExtManager { .map_err(TestError::ReadStateError) } - pub(crate) fn is_user(&self, id: &ProgramId) -> bool { - matches!( - self.actors.borrow().get(id), - Some((TestActor::User, _)) | None - ) - } - - pub(crate) fn is_active_program(&self, id: &ProgramId) -> bool { - matches!( - self.actors.borrow().get(id), - Some((TestActor::Initialized(_), _)) | Some((TestActor::Uninitialized(_, _), _)) - ) - } - - pub(crate) fn is_program(&self, id: &ProgramId) -> bool { - matches!( - self.actors.borrow().get(id), - Some((TestActor::Initialized(_), _)) - | Some((TestActor::Uninitialized(_, _), _)) - | Some((TestActor::Dormant, _)) - ) - } - pub(crate) fn mint_to(&mut self, id: &ProgramId, value: Value) { if value < crate::EXISTENTIAL_DEPOSIT { panic!( @@ -715,30 +491,11 @@ impl ExtManager { ); } - let mut actors = self.actors.borrow_mut(); - let (_, balance) = actors - .entry(*id) - .or_insert((TestActor::User, Balance::default())); - balance.increase(value); - } - - pub(crate) fn transfer( - &mut self, - from: &ProgramId, - to: &ProgramId, - value: Value, - keep_alive: bool, - ) { - Balance::transfer(&mut self.actors, *from, *to, value, keep_alive); + Accounts::increase(*id, value); } pub(crate) fn balance_of(&self, id: &ProgramId) -> Value { - self.actors - .borrow() - .get(id) - .map(|(_, balance)| balance.clone()) - .unwrap_or_default() - .total() + Accounts::balance(*id) } pub(crate) fn claim_value_from_mailbox( @@ -759,64 +516,52 @@ impl ExtManager { } #[track_caller] - pub(crate) fn override_balance(&mut self, id: &ProgramId, balance: Value) { - if self.is_user(id) && balance < crate::EXISTENTIAL_DEPOSIT { + pub(crate) fn override_balance(&mut self, &id: &ProgramId, balance: Value) { + if Actors::is_user(id) && balance < crate::EXISTENTIAL_DEPOSIT { panic!( "An attempt to override balance with value ({}) less than existential deposit ({})", balance, crate::EXISTENTIAL_DEPOSIT ); } - - let mut actors = self.actors.borrow_mut(); - let (_, actor_balance) = actors - .entry(*id) - .or_insert((TestActor::User, Balance::default())); - *actor_balance = Balance::new(balance); + Accounts::override_balance(id, balance); } #[track_caller] pub(crate) fn read_memory_pages(&self, program_id: &ProgramId) -> BTreeMap { - let actors = self.actors.borrow(); - let program = &actors - .get(program_id) - .unwrap_or_else(|| panic!("Actor {program_id} not found")) - .0; - - let program = match program { - TestActor::Initialized(program) => program, - TestActor::Uninitialized(_, program) => program.as_ref().unwrap(), - TestActor::Dormant | TestActor::User => panic!("Actor {program_id} isn't a program"), - }; + Actors::access(*program_id, |actor| { + let program = match actor.unwrap_or_else(|| panic!("Actor id {program_id:?} not found")) + { + TestActor::Initialized(program) => program, + TestActor::Uninitialized(_, program) => program.as_ref().unwrap(), + TestActor::Dormant => panic!("Actor {program_id} isn't dormant"), + }; - match program { - Program::Genuine(program) => program.pages_data.clone(), - Program::Mock(_) => panic!("Can't read memory of mock program"), - } + match program { + Program::Genuine(program) => program.pages_data.clone(), + Program::Mock(_) => panic!("Can't read memory of mock program"), + } + }) } #[track_caller] fn init_success(&mut self, program_id: ProgramId) { - let mut actors = self.actors.borrow_mut(); - let (actor, _) = actors - .get_mut(&program_id) - .expect("Can't find existing program"); - - actor.set_initialized(); + Actors::modify(program_id, |actor| { + actor + .unwrap_or_else(|| panic!("Actor id {program_id:?} not found")) + .set_initialized() + }); } #[track_caller] fn init_failure(&mut self, program_id: ProgramId, origin: ProgramId) { - let total_value = { - let mut actors = self.actors.borrow_mut(); - let (actor, balance) = actors - .get_mut(&program_id) - .expect("Can't find existing program"); - *actor = TestActor::Dormant; - - balance.total() - }; - Balance::transfer(&mut self.actors, program_id, origin, total_value, false); + Actors::modify(program_id, |actor| { + let actor = actor.unwrap_or_else(|| panic!("Actor id {program_id:?} not found")); + *actor = TestActor::Dormant + }); + + let value = Accounts::balance(program_id); + Accounts::transfer(program_id, origin, value, false); } fn process_mock(&mut self, mut mock: Box, dispatch: StoredDispatch) { @@ -912,14 +657,11 @@ impl ExtManager { // After run either `init_success` is called or `init_failed`. // So only active (init success) program can be modified - self.actors - .borrow_mut() - .entry(program_id) - .and_modify(|(actor, _)| { - if let TestActor::Initialized(old_mock) = actor { - *old_mock = Program::Mock(Some(mock)); - } - }); + Actors::modify(program_id, |actor| { + if let Some(TestActor::Initialized(old_mock)) = actor { + *old_mock = Program::Mock(Some(mock)); + } + }) } fn process_normal( @@ -1085,10 +827,9 @@ impl ExtManager { id: ProgramId, op: F, ) -> Option { - let mut actors = self.actors.borrow_mut(); - actors - .get_mut(&id) - .and_then(|(actor, _)| actor.genuine_program_mut().map(op)) + Actors::modify(id, |actor| { + actor.and_then(|actor| actor.genuine_program_mut().map(op)) + }) } } @@ -1126,7 +867,7 @@ impl JournalHandler for ExtManager { self.gas_allowance = self.gas_allowance.saturating_sub(Gas(amount)); self.gas_tree .spend(message_id, amount) - .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); + .unwrap_or_else(|e| unreachable!("GasTree corrupted! {e:?}")); self.gas_burned .entry(message_id) @@ -1138,43 +879,38 @@ impl JournalHandler for ExtManager { let (external, multiplier, _) = self .gas_tree .get_origin_node(message_id) - .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); + .unwrap_or_else(|e| unreachable!("GasTree corrupted! {e:?}")); let id: ProgramId = external.into_origin().into(); self.bank.spend_gas(id, amount, multiplier); } fn exit_dispatch(&mut self, id_exited: ProgramId, value_destination: ProgramId) { - let total_value = self.actors.total_balance(&id_exited); - Balance::transfer( - &mut self.actors, - id_exited, - value_destination, - total_value, - false, - ); + Actors::modify(id_exited, |actor| { + let actor = + actor.unwrap_or_else(|| panic!("Can't find existing program {id_exited:?}")); + *actor = TestActor::Dormant + }); - self.actors.remove(&id_exited); + let value = Accounts::balance(id_exited); + Accounts::transfer(id_exited, value_destination, value, false); } fn message_consumed(&mut self, message_id: MessageId) { let outcome = self .gas_tree .consume(message_id) - .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); + .unwrap_or_else(|e| unreachable!("GasTree corrupted! {e:?}")); // Retrieve gas if let Some((imbalance, multiplier, external)) = outcome { // Peeking numeric value from negative imbalance. let gas_left = imbalance.peek(); - let id: ProgramId = external.into_origin().into(); - let mut actors = self.actors.borrow_mut(); - let (_, to) = actors.get_mut(&id).expect("Can't fail"); // Unreserving funds, if left non-zero amount of gas. if gas_left != 0 { - self.bank.withdraw_gas(id, to, gas_left, multiplier); + self.bank.withdraw_gas(id, id, gas_left, multiplier); } } } @@ -1196,20 +932,16 @@ impl JournalHandler for ExtManager { log::debug!("[{message_id}] new dispatch#{}", dispatch.id()); let source = dispatch.source(); - let is_program = self.is_program(&dispatch.destination()); + let is_program = Actors::is_program(dispatch.destination()); - let mut deposit_value = |actors: &mut Actors| { + let mut deposit_value = || { if dispatch.value() != 0 { - let mut actors = actors.borrow_mut(); - let (_, balance) = actors.get_mut(&source).expect("Can't fail"); - - self.bank - .deposit_value(balance, source, dispatch.value(), false); + self.bank.deposit_value(source, dispatch.value(), false); } }; if is_program { - deposit_value(&mut self.actors); + deposit_value(); match (dispatch.gas_limit(), reservation) { (Some(gas_limit), None) => self @@ -1233,7 +965,7 @@ impl JournalHandler for ExtManager { self.dispatches.push_back(dispatch.into_stored()); } else { - deposit_value(&mut self.actors); + deposit_value(); let gas_limit = dispatch.gas_limit().unwrap_or_default(); let stored_message = dispatch.into_stored().into_parts().1; @@ -1332,9 +1064,6 @@ impl JournalHandler for ExtManager { } let to = to.unwrap_or(from); - let mut actors = self.actors.borrow_mut(); - let (_, to) = actors.get_mut(&to).expect("Can't fail"); - self.bank.transfer_value(from, to, value); } @@ -1347,7 +1076,7 @@ impl JournalHandler for ExtManager { ) { if let Some(code) = self.opt_binaries.get(&code_id).cloned() { for (init_message_id, candidate_id) in candidates { - if !self.actors.contains_key(&candidate_id) { + if !Actors::contains_key(candidate_id) { let schedule = Schedule::default(); let code = Code::try_new( code.clone(), @@ -1376,17 +1105,7 @@ impl JournalHandler for ExtManager { ); // Transfer the ED from the program-creator to the new program - Balance::transfer( - &mut self.actors, - program_id, - candidate_id, - EXISTENTIAL_DEPOSIT, - true, - ); - - // Set ED lock - self.actors - .set_balance_lock(&candidate_id, EXISTENTIAL_DEPOSIT); + Accounts::transfer(program_id, candidate_id, EXISTENTIAL_DEPOSIT, true); } else { log::debug!("Program with id {candidate_id:?} already exists"); } @@ -1394,8 +1113,7 @@ impl JournalHandler for ExtManager { } else { log::debug!("No referencing code with code hash {code_id:?} for candidate programs"); for (_, invalid_candidate_id) in candidates { - self.actors - .insert(invalid_candidate_id, (TestActor::Dormant, Balance::empty())); + Actors::insert(invalid_candidate_id, TestActor::Dormant); } } } diff --git a/gtest/src/program.rs b/gtest/src/program.rs index a5cda3f6aac..f91fc7f7128 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -17,10 +17,11 @@ // along with this program. If not, see . use crate::{ + actors::{Actors, GenuineProgram, Program as InnerProgram, TestActor}, default_users_list, - manager::{ExtManager, GenuineProgram, Program as InnerProgram, TestActor, Value}, + manager::ExtManager, system::System, - Result, GAS_ALLOWANCE, + Result, Value, GAS_ALLOWANCE, }; use codec::{Codec, Decode, Encode}; use gear_core::{ @@ -583,17 +584,16 @@ impl<'a> Program<'a> { None, ); - let mut actors = system.actors.borrow_mut(); - let (actor, _) = actors.get_mut(&self.id).expect("Can't fail"); - - let kind = if let TestActor::Uninitialized(id @ None, _) = actor { - *id = Some(message.id()); - DispatchKind::Init - } else { - DispatchKind::Handle - }; + let kind = Actors::modify(self.id, |actor| { + let actor = actor.expect("Can't fail"); + if let TestActor::Uninitialized(id @ None, _) = actor { + *id = Some(message.id()); + DispatchKind::Init + } else { + DispatchKind::Handle + } + }); - drop(actors); system.validate_and_route_dispatch(Dispatch::new(kind, message)) } @@ -861,7 +861,7 @@ mod tests { use super::Program; use crate::{ - manager::Value, Log, ProgramIdWrapper, System, DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT, + Log, ProgramIdWrapper, System, Value, DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT, GAS_MULTIPLIER, }; use demo_constructor::{Arg, Scheme}; @@ -1425,7 +1425,7 @@ mod tests { #[test] #[should_panic( - expected = "Failed to increase balance: the sum 999850000000 of the total balance 994000000000 \ + expected = "Failed to increase balance: the sum 5850000000 of the total balance 0 \ and the value 5850000000 cannot be lower than the existential deposit" )] fn fails_transfer_when_user_balance_lower_than_ed() { diff --git a/gtest/src/system.rs b/gtest/src/system.rs index 42ec2e2f83d..fb63ff574e9 100644 --- a/gtest/src/system.rs +++ b/gtest/src/system.rs @@ -17,11 +17,13 @@ // along with this program. If not, see . use crate::{ + accounts::Accounts, + actors::Actors, log::{BlockRunResult, CoreLog}, mailbox::ActorMailbox, - manager::{Actors, ExtManager, Value}, + manager::ExtManager, program::{Program, ProgramIdWrapper}, - Gas, GAS_ALLOWANCE, + Gas, Value, GAS_ALLOWANCE, }; use codec::{Decode, DecodeAll}; use colored::Colorize; @@ -53,33 +55,36 @@ struct PageKey { } #[derive(Debug)] -struct PagesStorage { - actors: Actors, -} +struct PagesStorage; impl LazyPagesStorage for PagesStorage { fn page_exists(&self, mut key: &[u8]) -> bool { let PageKey { program_id, page, .. } = PageKey::decode_all(&mut key).expect("Invalid key"); - self.actors - .borrow() - .get(&program_id) - .and_then(|(actor, _)| actor.get_pages_data()) - .map(|pages_data| pages_data.contains_key(&page)) - .unwrap_or(false) + + Actors::access(program_id, |actor| { + actor + .and_then(|actor| actor.get_pages_data()) + .map(|pages_data| pages_data.contains_key(&page)) + .unwrap_or(false) + }) } fn load_page(&mut self, mut key: &[u8], buffer: &mut [u8]) -> Option { let PageKey { program_id, page, .. } = PageKey::decode_all(&mut key).expect("Invalid key"); - let actors = self.actors.borrow(); - let (actor, _balance) = actors.get(&program_id)?; - let pages_data = actor.get_pages_data()?; - let page_buf = pages_data.get(&page)?; - buffer.copy_from_slice(page_buf); - Some(page_buf.len() as u32) + + Actors::access(program_id, |actor| { + actor + .and_then(|actor| actor.get_pages_data()) + .and_then(|pages_data| pages_data.get(&page)) + .map(|page_buf| { + buffer.copy_from_slice(page_buf); + page_buf.len() as u32 + }) + }) } } @@ -115,13 +120,10 @@ impl System { } let ext_manager = ExtManager::new(); - - let actors = ext_manager.actors.clone(); - let pages_storage = PagesStorage { actors }; gear_lazy_pages::init( LazyPagesVersion::Version1, LazyPagesInitContext::new(Self::PAGE_STORAGE_PREFIX), - pages_storage, + PagesStorage, ) .expect("Failed to init lazy-pages"); @@ -275,9 +277,7 @@ impl System { /// Returns a [`Program`] by `id`. pub fn get_program>(&self, id: ID) -> Option { let id = id.into().0; - let manager = self.0.borrow(); - - if manager.is_program(&id) { + if Actors::is_program(id) { Some(Program { id, manager: &self.0, @@ -294,12 +294,8 @@ impl System { /// Returns a list of programs. pub fn programs(&self) -> Vec { - let manager = self.0.borrow(); - let actors = manager.actors.borrow(); - actors - .keys() - .copied() - .filter(|id| manager.is_program(id)) + Actors::program_ids() + .into_iter() .map(|id| Program { id, manager: &self.0, @@ -314,7 +310,7 @@ impl System { /// exited or terminated that it can't be called anymore. pub fn is_active_program>(&self, id: ID) -> bool { let program_id = id.into().0; - self.0.borrow().is_active_program(&program_id) + Actors::is_active_program(program_id) } /// Saves code to the storage and returns its code hash @@ -369,7 +365,7 @@ impl System { #[track_caller] pub fn get_mailbox>(&self, id: ID) -> ActorMailbox { let program_id = id.into().0; - if !self.0.borrow().is_user(&program_id) { + if !Actors::is_user(program_id) { panic!("Mailbox available only for users"); } ActorMailbox::new(program_id, &self.0) @@ -392,7 +388,12 @@ impl System { ) { let from = from.into().0; let to = to.into().0; - self.0.borrow_mut().transfer(&from, &to, value, keep_alive); + + if Actors::is_program(from) { + panic!("Attempt to transfer from a program {from:?}"); + } + + Accounts::transfer(from, to, value, keep_alive); } /// Returns balance of user with given `id`. From fffbe78de5b8f4f4158887d2842968ce23bf76e5 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Fri, 16 Aug 2024 03:24:45 +0400 Subject: [PATCH 23/29] Fix fn withdraw_gas params --- gtest/src/bank.rs | 15 +++++++-------- gtest/src/manager/journal.rs | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/gtest/src/bank.rs b/gtest/src/bank.rs index 5a7d82fe914..fd0d23beabc 100644 --- a/gtest/src/bank.rs +++ b/gtest/src/bank.rs @@ -62,14 +62,14 @@ impl Bank { #[track_caller] pub(crate) fn spend_gas( &mut self, - from: ProgramId, + id: ProgramId, gas: Gas, multiplier: GasMultiplier, ) { let gas_value = multiplier.gas_to_value(gas); self.accounts - .get_mut(&from) - .unwrap_or_else(|| panic!("Bank::spend_gas: actor id {from:?} not found in bank")) + .get_mut(&id) + .unwrap_or_else(|| panic!("Bank::spend_gas: actor id {id:?} not found in bank")) .gas -= gas_value; } @@ -77,18 +77,17 @@ impl Bank { #[track_caller] pub(crate) fn withdraw_gas( &mut self, - from: ProgramId, - to: ProgramId, + id: ProgramId, gas_left: Gas, multiplier: GasMultiplier, ) { let gas_left_value = multiplier.gas_to_value(gas_left); self.accounts - .get_mut(&from) - .unwrap_or_else(|| panic!("Bank::withdraw_gas: actor id {from:?} not found in bank")) + .get_mut(&id) + .unwrap_or_else(|| panic!("Bank::withdraw_gas: actor id {id:?} not found in bank")) .gas -= gas_left_value; let value = multiplier.gas_to_value(gas_left); - Accounts::increase(to, value); + Accounts::increase(id, value); } // Transfer value. diff --git a/gtest/src/manager/journal.rs b/gtest/src/manager/journal.rs index f1aa31eb23a..d72a9c5d5ee 100644 --- a/gtest/src/manager/journal.rs +++ b/gtest/src/manager/journal.rs @@ -115,7 +115,7 @@ impl JournalHandler for ExtManager { // Unreserving funds, if left non-zero amount of gas. if gas_left != 0 { - self.bank.withdraw_gas(id, id, gas_left, multiplier); + self.bank.withdraw_gas(id, gas_left, multiplier); } } } From 26548fc8747d6844a3c8c5264ec33d01e512d20d Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 19 Aug 2024 12:39:32 +0400 Subject: [PATCH 24/29] Update gtest/src/lib.rs Co-authored-by: Sabaun Taraki --- gtest/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index 54bdc1c6084..fbfc606e57a 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -255,7 +255,7 @@ //! RUST_LOG="target_1=logging_level,target_2=logging_level" cargo test //! ``` //! -//! ## Pre-requisites for Sending a Message +//! ## Pre-requisites for sending a message //! //! Prior to sending a message, it is necessary to mint sufficient balance for //! the sender to ensure coverage of the existential deposit and gas costs. From ccd310496d81260ebd7b6172a7d499d2f3a816c2 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 19 Aug 2024 14:38:17 +0400 Subject: [PATCH 25/29] Review fixes --- gtest/src/accounts.rs | 42 +++++++++++++++++++++++++++++------- gtest/src/actors.rs | 5 +++++ gtest/src/bank.rs | 17 +++++++++++++-- gtest/src/lib.rs | 18 ++++++++++------ gtest/src/manager.rs | 36 ++++++++++++++----------------- gtest/src/manager/journal.rs | 4 +++- gtest/src/program.rs | 24 ++++++++------------- gtest/src/system.rs | 17 ++++++++++++--- 8 files changed, 108 insertions(+), 55 deletions(-) diff --git a/gtest/src/accounts.rs b/gtest/src/accounts.rs index 22a4e4fcc2f..a6f09d89731 100644 --- a/gtest/src/accounts.rs +++ b/gtest/src/accounts.rs @@ -44,7 +44,15 @@ struct Balance { } impl Balance { + #[track_caller] fn new(amount: Value) -> Self { + if amount < EXISTENTIAL_DEPOSIT { + panic!( + "Failed to create balance: the amount {} cannot be lower than the existential deposit", + amount + ); + } + Self { amount } } @@ -69,7 +77,7 @@ pub(crate) struct Accounts; impl Accounts { // Checks if account by program id exists. - pub(crate) fn is_exist(id: ProgramId) -> bool { + pub(crate) fn exist(id: ProgramId) -> bool { Self::balance(id) != 0 } @@ -118,6 +126,8 @@ impl Accounts { ); storage.remove(&id); } + } else { + panic!("Failed to decrease balance for account {id:?}, balance is zero"); } }); } @@ -125,19 +135,22 @@ impl Accounts { // Increases account balance. pub(crate) fn increase(id: ProgramId, amount: Value) { ACCOUNT_STORAGE.with_borrow_mut(|storage| { - let balance = storage.entry(id).or_insert(Balance::new(0)); + let balance = storage.get(&id).map(Balance::balance).unwrap_or_default(); - if balance.balance() + amount < EXISTENTIAL_DEPOSIT { + if balance + amount < EXISTENTIAL_DEPOSIT { panic!( - "Failed to increase balance: the sum {} of the total balance {} \ - and the value {} cannot be lower than the existential deposit", - balance.balance() + amount, - balance.balance(), + "Failed to increase balance: the sum ({}) of the total balance ({}) \ + and the value ({}) cannot be lower than the existential deposit ({EXISTENTIAL_DEPOSIT})", + balance + amount, + balance, amount ); } - balance.increase(amount); + storage + .entry(id) + .and_modify(|balance| balance.increase(amount)) + .or_insert_with(|| Balance::new(amount)); }); } @@ -160,6 +173,19 @@ impl Accounts { storage.insert(id, Balance::new(amount)); }); } + + // Checks if value can be deposited to account. + pub(crate) fn can_deposit(id: ProgramId, amount: Value) -> bool { + Accounts::balance(id) + amount >= EXISTENTIAL_DEPOSIT + } + + // Clears accounts storage. + pub(crate) fn clear() { + ACCOUNT_STORAGE.with_borrow_mut(|storage| { + storage.clear(); + init_default_accounts(storage); + }); + } } impl fmt::Debug for Accounts { diff --git a/gtest/src/actors.rs b/gtest/src/actors.rs index 51dbcb4db63..39881e4b89a 100644 --- a/gtest/src/actors.rs +++ b/gtest/src/actors.rs @@ -91,6 +91,11 @@ impl Actors { pub(crate) fn program_ids() -> Vec { ACTORS_STORAGE.with_borrow(|storage| storage.keys().copied().collect()) } + + // Clears actors storage. + pub(crate) fn clear() { + ACTORS_STORAGE.with_borrow_mut(|storage| storage.clear()) + } } impl fmt::Debug for Actors { diff --git a/gtest/src/bank.rs b/gtest/src/bank.rs index fd0d23beabc..20c555be1e7 100644 --- a/gtest/src/bank.rs +++ b/gtest/src/bank.rs @@ -86,8 +86,14 @@ impl Bank { .get_mut(&id) .unwrap_or_else(|| panic!("Bank::withdraw_gas: actor id {id:?} not found in bank")) .gas -= gas_left_value; - let value = multiplier.gas_to_value(gas_left); - Accounts::increase(id, value); + + if !Accounts::can_deposit(id, gas_left_value) { + // Unable to deposit value to account. + // In this case unused value will be lost. + return; + } + + Accounts::increase(id, gas_left_value); } // Transfer value. @@ -97,6 +103,13 @@ impl Bank { .get_mut(&from) .unwrap_or_else(|| panic!("Bank::transfer_value: actor id {from:?} not found in bank")) .value -= value; + + if !Accounts::can_deposit(to, value) { + // Unable to deposit value to account. + // In this case unused value will be lost. + return; + } + Accounts::increase(to, value); } } diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index fbfc606e57a..46a6e977536 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -113,7 +113,9 @@ //! mod tests { //! use gtest::{Log, Program, System}; //! -//! // Or you can use the default users from the `gtest::constants`. +//! // Alternatively, you can use the default users: +//! // [`DEFAULT_USER_ALICE`], [`DEFAULT_USER_BOB`], [`DEFAULT_USER_CHARLIE`], [`DEFAULT_USER_EVE`]. +//! // The full list of default users can be obtained with [`default_users_list`]. //! const USER_ID: u64 = 100001; //! //! #[test] @@ -414,11 +416,15 @@ //! //! As previously mentioned [here](#Pre-requisites-for-Sending-a-Message), //! a balance for the user must be minted before sending a message. This balance -//! should be sufficient to cover both the existential deposit and gas costs. -//! -//! The `mint_to` method can be utilized to allocate a balance to the user, -//! while the `mint` method serves to allocate a balance to the program. The -//! `balance_of` method may be used to verify the current balance. +//! should be sufficient to cover the following: the user's existential deposit, +//! the existential deposit of the initialized program (the first message to the +//! program charges the program's existential deposit from the sender), and the +//! message's gas costs. +//! +//! The [`System::mint_to`] method can be utilized to allocate a balance to the +//! user, while the [`Program::mint`] method serves to allocate a balance to the +//! program. The [`System::balance_of`] method may be used to verify the current +//! balance. //! //! ```no_run //! # use gtest::Program; diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 337dca394ab..2cd7d2fcb0c 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -284,7 +284,7 @@ impl ExtManager { enum DispatchCase { Dormant, - ExecutableData(ExecutableActorData, InstrumentedCode), + Normal(ExecutableActorData, InstrumentedCode), Mock(Box), } @@ -294,7 +294,7 @@ impl ExtManager { if actor.is_dormant() { DispatchCase::Dormant } else if let Some((data, code)) = actor.get_executable_actor_data() { - DispatchCase::ExecutableData(data, code) + DispatchCase::Normal(data, code) } else if let Some(mock) = actor.take_mock() { DispatchCase::Mock(mock) } else { @@ -305,7 +305,7 @@ impl ExtManager { match dispatch_case { DispatchCase::Dormant => self.process_dormant(balance, dispatch), - DispatchCase::ExecutableData(data, code) => { + DispatchCase::Normal(data, code) => { self.process_normal(balance, data, code, dispatch) } DispatchCase::Mock(mock) => self.process_mock(mock, dispatch), @@ -327,8 +327,8 @@ impl ExtManager { } // User must exist - if !Accounts::is_exist(source) { - panic!("User {source} doesn't exist; mint value to it first."); + if !Accounts::exist(source) { + panic!("User's {source} balance is zero; mint value to it first."); } let is_init_msg = dispatch.kind().is_init(); @@ -345,7 +345,7 @@ impl ExtManager { if balance < { dispatch.value() + gas_value + maybe_ed } { panic!( "Insufficient balance: user ({}) tries to send \ - ({}) value, ({}) gas and ED {}, while his balance ({:?})", + ({}) value, ({}) gas and ED ({}), while his balance ({:?})", source, dispatch.value(), gas_value, @@ -359,8 +359,10 @@ impl ExtManager { Accounts::transfer(source, destination, EXISTENTIAL_DEPOSIT, false); } - // Deposit message value - self.bank.deposit_value(source, dispatch.value(), false); + if dispatch.value() != 0 { + // Deposit message value + self.bank.deposit_value(source, dispatch.value(), false); + } // Deposit gas self.bank.deposit_gas(source, gas_limit, false); @@ -392,9 +394,9 @@ impl ExtManager { self.blocks_manager.get(), ) .map_err(TestError::ReadStateError) - } else if let Some(mut program_mock) = - Actors::modify(*program_id, |actor| actor.unwrap().take_mock()) - { + } else if let Some(mut program_mock) = Actors::modify(*program_id, |actor| { + actor.expect("Checked before").take_mock() + }) { program_mock .state() .map_err(|err| TestError::ReadStateError(err.into())) @@ -438,14 +440,6 @@ impl ExtManager { } pub(crate) fn mint_to(&mut self, id: &ProgramId, value: Value) { - if value < crate::EXISTENTIAL_DEPOSIT { - panic!( - "An attempt to mint value ({}) less than existential deposit ({})", - value, - crate::EXISTENTIAL_DEPOSIT - ); - } - Accounts::increase(*id, value); } @@ -516,7 +510,9 @@ impl ExtManager { }); let value = Accounts::balance(program_id); - Accounts::transfer(program_id, origin, value, false); + if value != 0 { + Accounts::transfer(program_id, origin, value, false); + } } fn process_mock(&mut self, mut mock: Box, dispatch: StoredDispatch) { diff --git a/gtest/src/manager/journal.rs b/gtest/src/manager/journal.rs index d72a9c5d5ee..73d11884b5b 100644 --- a/gtest/src/manager/journal.rs +++ b/gtest/src/manager/journal.rs @@ -98,7 +98,9 @@ impl JournalHandler for ExtManager { }); let value = Accounts::balance(id_exited); - Accounts::transfer(id_exited, value_destination, value, false); + if value != 0 { + Accounts::transfer(id_exited, value_destination, value, false); + } } fn message_consumed(&mut self, message_id: MessageId) { diff --git a/gtest/src/program.rs b/gtest/src/program.rs index f91fc7f7128..7ec71b31393 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -860,10 +860,7 @@ pub mod gbuild { mod tests { use super::Program; - use crate::{ - Log, ProgramIdWrapper, System, Value, DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT, - GAS_MULTIPLIER, - }; + use crate::{Log, ProgramIdWrapper, System, Value, DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT}; use demo_constructor::{Arg, Scheme}; use gear_common::Origin; @@ -1059,7 +1056,8 @@ mod tests { #[test] #[should_panic( - expected = "An attempt to mint value (1) less than existential deposit (1000000000000)" + expected = "Failed to increase balance: the sum (1) of the total balance (0) and the value (1) \ + cannot be lower than the existential deposit (1000000000000)" )] fn mint_less_than_deposit() { System::new().mint_to(1, 1); @@ -1068,7 +1066,7 @@ mod tests { #[test] #[should_panic( expected = "Insufficient balance: user (0x0500000000000000000000000000000000000000000000000000000000000000) \ - tries to send (1000000000001) value, (4500000000000) gas and ED 1000000000000, while his balance (1000000000000)" + tries to send (1000000000001) value, (4500000000000) gas and ED (1000000000000), while his balance (1000000000000)" )] fn fails_on_insufficient_balance() { let sys = System::new(); @@ -1297,10 +1295,7 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 137, demo_ping::WASM_BINARY); let user_id = ActorId::zero(); - sys.mint_to( - user_id, - EXISTENTIAL_DEPOSIT + GAS_MULTIPLIER.gas_to_value(1), - ); + sys.mint_to(user_id, EXISTENTIAL_DEPOSIT * 2); // set insufficient gas for execution let msg_id = prog.send_with_gas(user_id, "init".to_string(), 1, 0); @@ -1424,11 +1419,7 @@ mod tests { } #[test] - #[should_panic( - expected = "Failed to increase balance: the sum 5850000000 of the total balance 0 \ - and the value 5850000000 cannot be lower than the existential deposit" - )] - fn fails_transfer_when_user_balance_lower_than_ed() { + fn tests_unused_gas_value_not_transferred() { let sys = System::new(); sys.init_verbose_logger(); @@ -1438,5 +1429,8 @@ mod tests { let prog = Program::from_binary_with_id(&sys, 69, demo_piggy_bank::WASM_BINARY); prog.send_bytes_with_gas(user, b"init", 1_000_000_000, 0); sys.run_next_block(); + + // Unspent gas is not returned to the user's balance when the sum of these is lower than ED + assert_eq!(sys.balance_of(user), 0) } } diff --git a/gtest/src/system.rs b/gtest/src/system.rs index 4403bb67e90..ec3411c1a40 100644 --- a/gtest/src/system.rs +++ b/gtest/src/system.rs @@ -373,14 +373,21 @@ impl System { /// Mint balance to user with given `id` and `value`. pub fn mint_to>(&self, id: ID, value: Value) { - let actor_id = id.into().0; - self.0.borrow_mut().mint_to(&actor_id, value); + let id = id.into().0; + + if Actors::is_program(id) { + panic!( + "Attempt to mint value to a program {id:?}, please use `System::transfer` instead" + ); + } + + self.0.borrow_mut().mint_to(&id, value); } /// Transfer balance from user with given `from` id to user with given `to` /// id. pub fn transfer( - &mut self, + &self, from: impl Into, to: impl Into, value: Value, @@ -410,6 +417,10 @@ impl Drop for System { self.0.borrow().gas_tree.reset(); self.0.borrow().mailbox.reset(); self.0.borrow().task_pool.clear(); + + // Clear actors and accounts storages + Actors::clear(); + Accounts::clear(); } } From 6ddf71a762e983d55c32757e7288419dc699d92d Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 19 Aug 2024 17:31:46 +0400 Subject: [PATCH 26/29] Review fixes II --- gtest/src/lib.rs | 15 +++++++-------- gtest/src/program.rs | 17 +++++++---------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index 46a6e977536..592d62f55be 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -113,9 +113,9 @@ //! mod tests { //! use gtest::{Log, Program, System}; //! -//! // Alternatively, you can use the default users: -//! // [`DEFAULT_USER_ALICE`], [`DEFAULT_USER_BOB`], [`DEFAULT_USER_CHARLIE`], [`DEFAULT_USER_EVE`]. -//! // The full list of default users can be obtained with [`default_users_list`]. +//! // Alternatively, you can use the default users from `gtest::constants`: +//! // `DEFAULT_USER_ALICE`, `DEFAULT_USER_BOB`, `DEFAULT_USER_CHARLIE`, `DEFAULT_USER_EVE`. +//! // The full list of default users can be obtained with `gtest::constants::default_users_list`. //! const USER_ID: u64 = 100001; //! //! #[test] @@ -422,9 +422,8 @@ //! message's gas costs. //! //! The [`System::mint_to`] method can be utilized to allocate a balance to the -//! user, while the [`Program::mint`] method serves to allocate a balance to the -//! program. The [`System::balance_of`] method may be used to verify the current -//! balance. +//! user or the program. The [`System::balance_of`] method may be used to verify +//! the current balance. //! //! ```no_run //! # use gtest::Program; @@ -434,9 +433,9 @@ //! sys.mint_to(user_id, 5000); //! assert_eq!(sys.balance_of(user_id), 5000); //! -//! // To give the balance to the program you should use `mint` method: +//! // To give the balance to the program you should use [`System::transfer`] method: //! let mut prog = Program::current(&sys); -//! prog.mint(1000); +//! prog.transfer(user_id, prog.id(), 1000); //! assert_eq!(prog.balance(), 1000); //! ``` //! diff --git a/gtest/src/program.rs b/gtest/src/program.rs index 7ec71b31393..5a8c3063738 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -731,11 +731,6 @@ impl<'a> Program<'a> { D::decode(&mut state_bytes.as_ref()).map_err(Into::into) } - /// Mint balance to the account. - pub fn mint(&mut self, value: Value) { - self.manager.borrow_mut().mint_to(&self.id(), value) - } - /// Returns the balance of the account. pub fn balance(&self) -> Value { self.manager.borrow().balance_of(&self.id()) @@ -939,12 +934,13 @@ mod tests { let user_id = 42; let mut user_spent_balance = 0; - sys.mint_to(user_id, 10 * EXISTENTIAL_DEPOSIT); - assert_eq!(sys.balance_of(user_id), 10 * EXISTENTIAL_DEPOSIT); + sys.mint_to(user_id, 12 * EXISTENTIAL_DEPOSIT); + assert_eq!(sys.balance_of(user_id), 12 * EXISTENTIAL_DEPOSIT); - let mut prog = Program::from_binary_with_id(&sys, 137, demo_ping::WASM_BINARY); + let program_id = 137; + let prog = Program::from_binary_with_id(&sys, program_id, demo_ping::WASM_BINARY); - prog.mint(2 * EXISTENTIAL_DEPOSIT); + sys.transfer(user_id, program_id, 2 * EXISTENTIAL_DEPOSIT, true); assert_eq!(prog.balance(), 2 * EXISTENTIAL_DEPOSIT); prog.send_with_value(user_id, "init".to_string(), EXISTENTIAL_DEPOSIT); @@ -1430,7 +1426,8 @@ mod tests { prog.send_bytes_with_gas(user, b"init", 1_000_000_000, 0); sys.run_next_block(); - // Unspent gas is not returned to the user's balance when the sum of these is lower than ED + // Unspent gas is not returned to the user's balance when the sum of these is + // lower than ED assert_eq!(sys.balance_of(user), 0) } } From 820cbd1fa1b333ca6de89fa3217196d4aacd0265 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 19 Aug 2024 18:05:47 +0400 Subject: [PATCH 27/29] Fix docs --- gtest/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index 592d62f55be..a4ed57e736d 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -435,7 +435,7 @@ //! //! // To give the balance to the program you should use [`System::transfer`] method: //! let mut prog = Program::current(&sys); -//! prog.transfer(user_id, prog.id(), 1000); +//! sys.transfer(user_id, prog.id(), 1000); //! assert_eq!(prog.balance(), 1000); //! ``` //! From 8c17db40f2374db7c549dfac1d6d2d39de80328a Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 19 Aug 2024 18:23:09 +0400 Subject: [PATCH 28/29] Fix docs II --- gtest/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index a4ed57e736d..ab50524c030 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -435,7 +435,7 @@ //! //! // To give the balance to the program you should use [`System::transfer`] method: //! let mut prog = Program::current(&sys); -//! sys.transfer(user_id, prog.id(), 1000); +//! sys.transfer(user_id, prog.id(), 1000, true); //! assert_eq!(prog.balance(), 1000); //! ``` //! From 1317078f109a27ff1d790c945531898912e880df Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 19 Aug 2024 19:02:11 +0400 Subject: [PATCH 29/29] Review fixes III --- gtest/src/accounts.rs | 2 +- gtest/src/manager.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gtest/src/accounts.rs b/gtest/src/accounts.rs index a6f09d89731..f66e8a767ad 100644 --- a/gtest/src/accounts.rs +++ b/gtest/src/accounts.rs @@ -77,7 +77,7 @@ pub(crate) struct Accounts; impl Accounts { // Checks if account by program id exists. - pub(crate) fn exist(id: ProgramId) -> bool { + pub(crate) fn exists(id: ProgramId) -> bool { Self::balance(id) != 0 } diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 2cd7d2fcb0c..6485de30ecd 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -327,7 +327,7 @@ impl ExtManager { } // User must exist - if !Accounts::exist(source) { + if !Accounts::exists(source) { panic!("User's {source} balance is zero; mint value to it first."); }