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 5711edb49b3..a5bc7437b42 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);
IntoIterator::into_iter([
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 fa288276848..c140ef14ca3 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 1ca6d9c2a6f..e88ffecfe89 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() {
@@ -42,7 +42,7 @@ mod tests {
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/accounts.rs b/gtest/src/accounts.rs
new file mode 100644
index 00000000000..f66e8a767ad
--- /dev/null
+++ b/gtest/src/accounts.rs
@@ -0,0 +1,195 @@
+// 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 {
+ #[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 }
+ }
+
+ 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 exists(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);
+ }
+ } else {
+ panic!("Failed to decrease balance for account {id:?}, balance is zero");
+ }
+ });
+ }
+
+ // Increases account balance.
+ pub(crate) fn increase(id: ProgramId, amount: Value) {
+ ACCOUNT_STORAGE.with_borrow_mut(|storage| {
+ let balance = storage.get(&id).map(Balance::balance).unwrap_or_default();
+
+ 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 ({EXISTENTIAL_DEPOSIT})",
+ balance + amount,
+ balance,
+ amount
+ );
+ }
+
+ storage
+ .entry(id)
+ .and_modify(|balance| balance.increase(amount))
+ .or_insert_with(|| Balance::new(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));
+ });
+ }
+
+ // 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 {
+ 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..39881e4b89a
--- /dev/null
+++ b/gtest/src/actors.rs
@@ -0,0 +1,227 @@
+// 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())
+ }
+
+ // Clears actors storage.
+ pub(crate) fn clear() {
+ ACTORS_STORAGE.with_borrow_mut(|storage| storage.clear())
+ }
+}
+
+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/bank.rs b/gtest/src/bank.rs
new file mode 100644
index 00000000000..20c555be1e7
--- /dev/null
+++ b/gtest/src/bank.rs
@@ -0,0 +1,115 @@
+// 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,
+ id: ProgramId,
+ gas: Gas,
+ multiplier: GasMultiplier,
+ ) {
+ let gas_value = multiplier.gas_to_value(gas);
+ self.accounts
+ .get_mut(&id)
+ .unwrap_or_else(|| panic!("Bank::spend_gas: actor id {id:?} not found in bank"))
+ .gas -= gas_value;
+ }
+
+ // Withdraw gas.
+ #[track_caller]
+ pub(crate) fn withdraw_gas(
+ &mut self,
+ id: ProgramId,
+ gas_left: Gas,
+ multiplier: GasMultiplier,
+ ) {
+ let gas_left_value = multiplier.gas_to_value(gas_left);
+ self.accounts
+ .get_mut(&id)
+ .unwrap_or_else(|| panic!("Bank::withdraw_gas: actor id {id:?} not found in bank"))
+ .gas -= gas_left_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.
+ #[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;
+
+ 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/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 7903cfa4723..ab50524c030 100644
--- a/gtest/src/lib.rs
+++ b/gtest/src/lib.rs
@@ -113,6 +113,9 @@
//! mod tests {
//! use gtest::{Log, Program, System};
//!
+//! // 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]
@@ -123,6 +126,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 init_message_id = prog.send_bytes(USER_ID, b"Doesn't matter");
//!
@@ -251,6 +257,30 @@
//! 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
+//! # use gtest::constants::EXISTENTIAL_DEPOSIT;
+//! # 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:
@@ -375,7 +405,25 @@
//! 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 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 or the program. The [`System::balance_of`] method may be used to verify
+//! the current balance.
//!
//! ```no_run
//! # use gtest::Program;
@@ -385,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);
+//! sys.transfer(user_id, prog.id(), 1000, true);
//! assert_eq!(prog.balance(), 1000);
//! ```
//!
@@ -444,6 +492,9 @@
#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")]
#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")]
+mod accounts;
+mod actors;
+mod bank;
mod blocks;
mod error;
mod gas_tree;
@@ -464,6 +515,7 @@ pub use program::{
};
pub use system::System;
+pub use constants::Value;
pub(crate) use constants::*;
/// Module containing constants of Gear protocol.
@@ -583,4 +635,28 @@ pub mod constants {
pub const HOST_FUNC_WRITE_AFTER_READ_COST: Gas = 115129057;
/// Loading page data from storage cost.
pub const LOAD_PAGE_STORAGE_DATA_COST: Gas = 10630903;
+
+ /* 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..7250cb0794d 100644
--- a/gtest/src/log.rs
+++ b/gtest/src/log.rs
@@ -16,7 +16,10 @@
// 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::{
+ program::{Gas, ProgramIdWrapper},
+ Value, GAS_MULTIPLIER,
+};
use codec::{Codec, Encode};
use core_processor::configs::BlockInfo;
use gear_core::{
@@ -447,6 +450,16 @@ impl BlockRunResult {
);
}
+ /// Calculate the total spent value.
+ pub fn spent_value(&self) -> Value {
+ 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/mailbox/actor.rs b/gtest/src/mailbox/actor.rs
index 9b051ce9f61..71de187759e 100644
--- a/gtest/src/mailbox/actor.rs
+++ b/gtest/src/mailbox/actor.rs
@@ -127,7 +127,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;
@@ -135,7 +135,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());
@@ -151,8 +151,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);
@@ -160,12 +159,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]
diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs
index 8414cb18edb..6485de30ecd 100644
--- a/gtest/src/manager.rs
+++ b/gtest/src/manager.rs
@@ -20,22 +20,26 @@ mod journal;
mod task;
use crate::{
+ accounts::Accounts,
+ actors::{Actors, GenuineProgram, Program, TestActor},
+ bank::Bank,
blocks::BlocksManager,
+ constants::Value,
gas_tree::GasTreeManager,
log::{BlockRunResult, CoreLog},
mailbox::MailboxManager,
program::{Gas, WasmProgram},
task_pool::TaskPoolManager,
Result, TestError, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, EXISTENTIAL_DEPOSIT,
- GAS_ALLOWANCE, 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,
+ 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::*,
@@ -53,178 +57,21 @@ use gear_core::{
ids::{prelude::*, CodeId, MessageId, ProgramId, ReservationId},
memory::PageBuf,
message::{Dispatch, DispatchKind, ReplyMessage, ReplyPacket, StoredDispatch, StoredMessage},
- pages::{numerated::tree::IntervalsTree, GearPage, WasmPage},
- reservation::GasReservationMap,
+ pages::GearPage,
};
use gear_core_errors::{ErrorReplyReason, SimpleExecutionError};
use gear_lazy_pages_common::LazyPagesCosts;
use gear_lazy_pages_native_interface::LazyPagesNative;
use rand::{rngs::StdRng, RngCore, SeedableRng};
use std::{
- cell::{Ref, RefCell, RefMut},
collections::{BTreeMap, BTreeSet, HashMap, VecDeque},
convert::TryInto,
mem,
- rc::Rc,
};
const OUTGOING_LIMIT: u32 = 1024;
const OUTGOING_BYTES_LIMIT: u32 = 64 * 1024 * 1024;
-pub(crate) type Balance = 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 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)
- }
-
- fn remove(&mut self, program_id: &ProgramId) -> Option<(TestActor, Balance)> {
- self.0.borrow_mut().remove(program_id)
- }
-}
-
-/// 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
@@ -236,7 +83,7 @@ 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>,
pub(crate) dispatches: VecDeque,
@@ -283,12 +130,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), 0))
+ Actors::insert(program_id, TestActor::new(init_message_id, program))
}
pub(crate) fn store_new_code(&mut self, code: Vec) -> CodeId {
@@ -308,7 +154,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
@@ -317,7 +163,7 @@ impl ExtManager {
/// Insert message into the delayed queue.
fn send_delayed_dispatch(&mut self, dispatch: Dispatch, delay: u32) {
let message_id = dispatch.id();
- let task = if self.is_program(&dispatch.destination()) {
+ let task = if Actors::is_program(dispatch.destination()) {
ScheduledTask::SendDispatch(message_id)
} else {
// TODO #4122, `to_mailbox` must be counted from provided gas
@@ -357,19 +203,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 {
@@ -385,7 +228,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.")
}
@@ -439,23 +282,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;
-
- 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,
+ Normal(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::Normal(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::Normal(data, code) => {
+ self.process_normal(balance, data, code, dispatch)
+ }
+ DispatchCase::Mock(mock) => self.process_mock(mock, dispatch),
}
total_processed += 1;
@@ -466,29 +319,53 @@ 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 Actors::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, 0));
+ // User must exist
+ if !Accounts::exists(source) {
+ panic!("User's {source} balance is zero; 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 balance = Accounts::balance(source);
+
+ 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);
- if *balance < dispatch.value() {
+ // Check sender has enough balance to cover dispatch costs
+ if balance < { dispatch.value() + gas_value + maybe_ed } {
panic!(
- "Insufficient value: user ({}) tries to send \
- ({}) value, while his balance ({})",
- dispatch.source(),
+ "Insufficient balance: user ({}) tries to send \
+ ({}) value, ({}) gas and ED ({}), while his balance ({:?})",
+ source,
dispatch.value(),
- balance
+ gas_value,
+ maybe_ed,
+ balance,
);
- } else {
- *balance -= dispatch.value();
- if *balance < crate::EXISTENTIAL_DEPOSIT {
- *balance = 0;
- }
}
+
+ // Charge for program ED upon creation
+ if is_init_msg {
+ Accounts::transfer(source, destination, EXISTENTIAL_DEPOSIT, 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);
}
/// Call non-void meta function from actor stored in manager.
@@ -498,14 +375,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,
@@ -516,7 +394,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.expect("Checked before").take_mock()
+ }) {
program_mock
.state()
.map_err(|err| TestError::ReadStateError(err.into()))
@@ -559,49 +439,12 @@ 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 mint_to(&mut self, id: &ProgramId, value: Value) {
+ Accounts::increase(*id, value);
}
- 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: Balance, mint_mode: MintMode) {
- if mint_mode == MintMode::KeepAlive && value < crate::EXISTENTIAL_DEPOSIT {
- panic!(
- "An attempt to mint value ({}) less than existential deposit ({})",
- value,
- crate::EXISTENTIAL_DEPOSIT
- );
- }
-
- let mut actors = self.actors.borrow_mut();
- let (_, balance) = actors.entry(*id).or_insert((TestActor::User, 0));
- *balance = balance.saturating_add(value);
- }
-
- pub(crate) fn balance_of(&self, id: &ProgramId) -> Balance {
- self.actors
- .borrow()
- .get(id)
- .map(|(_, balance)| *balance)
- .unwrap_or_default()
+ pub(crate) fn balance_of(&self, id: &ProgramId) -> Value {
+ Accounts::balance(*id)
}
pub(crate) fn claim_value_from_mailbox(
@@ -622,58 +465,54 @@ impl ExtManager {
}
#[track_caller]
- pub(crate) fn override_balance(&mut self, id: &ProgramId, balance: Balance) {
- 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, 0));
- *actor_balance = 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) {
- let mut actors = self.actors.borrow_mut();
- let (actor, _) = actors
- .get_mut(&program_id)
- .expect("Can't find existing program");
-
- *actor = TestActor::Dormant;
+ fn init_failure(&mut self, program_id: ProgramId, origin: ProgramId) {
+ 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);
+ if value != 0 {
+ Accounts::transfer(program_id, origin, value, false);
+ }
}
fn process_mock(&mut self, mut mock: Box, dispatch: StoredDispatch) {
@@ -771,14 +610,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(
@@ -944,9 +780,8 @@ 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))
+ })
}
}
diff --git a/gtest/src/manager/journal.rs b/gtest/src/manager/journal.rs
index 85182ac953d..73d11884b5b 100644
--- a/gtest/src/manager/journal.rs
+++ b/gtest/src/manager/journal.rs
@@ -19,9 +19,11 @@
/// Implementation of the `JournalHandler` trait for the `ExtManager`.
use std::collections::BTreeMap;
-use super::{Balance, ExtManager, Gas, GenuineProgram, MintMode, Program, TestActor};
+use crate::{accounts::Accounts, actors::Actors, Value, EXISTENTIAL_DEPOSIT};
+
+use super::{ExtManager, Gas, GenuineProgram, Program, TestActor};
use core_processor::common::{DispatchOutcome, JournalHandler};
-use gear_common::{scheduler::ScheduledTask, Origin};
+use gear_common::{gas_provider::Imbalance as _, scheduler::ScheduledTask, Origin};
use gear_core::{
code::{Code, CodeAndId, InstrumentedCodeAndId},
ids::{CodeId, MessageId, ProgramId, ReservationId},
@@ -53,8 +55,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, .. } => {
@@ -68,7 +72,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)
@@ -76,18 +80,46 @@ 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.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);
+ Actors::modify(id_exited, |actor| {
+ let actor =
+ actor.unwrap_or_else(|| panic!("Can't find existing program {id_exited:?}"));
+ *actor = TestActor::Dormant
+ });
+
+ let value = Accounts::balance(id_exited);
+ if value != 0 {
+ Accounts::transfer(id_exited, value_destination, value, false);
}
}
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));
+ .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();
+
+ // Unreserving funds, if left non-zero amount of gas.
+ if gas_left != 0 {
+ self.bank.withdraw_gas(id, gas_left, multiplier);
+ }
+ }
}
fn send_dispatch(
@@ -107,8 +139,17 @@ impl JournalHandler for ExtManager {
log::debug!("[{message_id}] new dispatch#{}", dispatch.id());
let source = dispatch.source();
+ let is_program = Actors::is_program(dispatch.destination());
+
+ let mut deposit_value = || {
+ if dispatch.value() != 0 {
+ self.bank.deposit_value(source, dispatch.value(), false);
+ }
+ };
+
+ if is_program {
+ deposit_value();
- if self.is_program(&dispatch.destination()) {
match (dispatch.gas_limit(), reservation) {
(Some(gas_limit), None) => self
.gas_tree
@@ -131,6 +172,8 @@ impl JournalHandler for ExtManager {
self.dispatches.push_back(dispatch.into_stored());
} else {
+ deposit_value();
+
let gas_limit = dispatch.gas_limit().unwrap_or_default();
let stored_message = dispatch.into_stored().into_parts().1;
@@ -233,31 +276,14 @@ 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;
- }
- }
-
- self.mint_to(to, value, MintMode::KeepAlive);
- } else {
- self.mint_to(&from, value, MintMode::KeepAlive);
- }
+ let to = to.unwrap_or(from);
+ self.bank.transfer_value(from, to, value);
}
#[track_caller]
@@ -269,7 +295,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(),
@@ -296,8 +322,9 @@ 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);
+ Accounts::transfer(program_id, candidate_id, EXISTENTIAL_DEPOSIT, true);
} else {
log::debug!("Program with id {candidate_id:?} already exists");
}
@@ -305,8 +332,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, 0));
+ Actors::insert(invalid_candidate_id, TestActor::Dormant);
}
}
}
diff --git a/gtest/src/program.rs b/gtest/src/program.rs
index 5a346971e47..5a8c3063738 100644
--- a/gtest/src/program.rs
+++ b/gtest/src/program.rs
@@ -17,9 +17,11 @@
// along with this program. If not, see .
use crate::{
- manager::{Balance, ExtManager, GenuineProgram, MintMode, Program as InnerProgram, TestActor},
+ actors::{Actors, GenuineProgram, Program as InnerProgram, TestActor},
+ default_users_list,
+ manager::ExtManager,
system::System,
- Result, GAS_ALLOWANCE,
+ Result, Value, GAS_ALLOWANCE,
};
use codec::{Codec, Decode, Encode};
use gear_core::{
@@ -398,6 +400,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()
@@ -578,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))
}
@@ -726,15 +731,8 @@ 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: Balance) {
- 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())
}
@@ -857,7 +855,7 @@ pub mod gbuild {
mod tests {
use super::Program;
- use crate::{Log, ProgramIdWrapper, System};
+ use crate::{Log, ProgramIdWrapper, System, Value, DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT};
use demo_constructor::{Arg, Scheme};
use gear_common::Origin;
@@ -870,7 +868,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 +904,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,23 +933,39 @@ mod tests {
sys.init_logger();
let user_id = 42;
- sys.mint_to(user_id, 10 * crate::EXISTENTIAL_DEPOSIT);
- assert_eq!(sys.balance_of(user_id), 10 * crate::EXISTENTIAL_DEPOSIT);
+ let mut user_spent_balance = 0;
+ 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 * crate::EXISTENTIAL_DEPOSIT);
- assert_eq!(prog.balance(), 2 * crate::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(), 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, "init".to_string(), 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
+ );
- 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);
+ 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 + EXISTENTIAL_DEPOSIT
+ );
+ assert_eq!(
+ sys.balance_of(user_id),
+ 7 * EXISTENTIAL_DEPOSIT - user_spent_balance
+ );
}
#[test]
@@ -965,33 +979,50 @@ 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 = 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_bytes(receiver, b"init");
- sys.run_next_block();
- 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);
- 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);
+ 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 * EXISTENTIAL_DEPOSIT - sender0_spent_value
+ );
+ 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 * EXISTENTIAL_DEPOSIT - sender1_spent_value
+ );
+ 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 * 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(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,36 +1044,37 @@ mod tests {
.is_ok());
assert_eq!(
sys.balance_of(receiver),
- (2 + 4 + 6) * crate::EXISTENTIAL_DEPOSIT
+ (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]
#[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);
}
#[test]
- #[should_panic(expected = "Insufficient value: user \
- (0x0100000000000000000000000000000000000000000000000000000000000000) tries \
- to send (1000000000001) value, while his balance (1000000000000)")]
+ #[should_panic(
+ expected = "Insufficient balance: user (0x0500000000000000000000000000000000000000000000000000000000000000) \
+ tries to send (1000000000001) value, (4500000000000) gas and ED (1000000000000), 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);
- 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();
}
@@ -1051,36 +1083,46 @@ mod tests {
let sys = System::new();
sys.init_logger();
+ 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");
- sys.run_next_block();
+ 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");
- 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");
+ 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();
// 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 * EXISTENTIAL_DEPOSIT + receiver_expected_balance
+ );
+ // Program is alive and holds the ED
+ assert_eq!(prog.balance(), EXISTENTIAL_DEPOSIT);
}
struct CleanupFolderOnDrop {
@@ -1102,7 +1144,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 +1198,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 +1230,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 +1256,31 @@ mod tests {
let sys = System::new();
sys.init_logger();
- let user_id = [42; 32];
- let prog = Program::from_binary_with_id(&sys, 137, WASM_BINARY);
+ let user_id = 42;
+ let mut user_balance = 4 * EXISTENTIAL_DEPOSIT;
+ sys.mint_to(user_id, user_balance);
+
+ 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() + EXISTENTIAL_DEPOSIT;
+
assert!(result.succeed.contains(&msg_id));
+ assert_eq!(sys.balance_of(prog_id), EXISTENTIAL_DEPOSIT);
+ assert_eq!(sys.balance_of(user_id), user_balance);
- 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();
+
+ // 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);
}
#[test]
@@ -1234,6 +1291,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 * 2);
// set insufficient gas for execution
let msg_id = prog.send_with_gas(user_id, "init".to_string(), 1, 0);
@@ -1258,7 +1316,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 +1365,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 +1413,21 @@ mod tests {
assert!(res.succeed.contains(&msg_id));
assert!(mailbox.contains(&Log::builder().payload_bytes(payload).source(new_prog_id)));
}
+
+ #[test]
+ fn tests_unused_gas_value_not_transferred() {
+ 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();
+
+ // 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 39abc27cd63..ec3411c1a40 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, Balance, ExtManager, MintMode},
+ 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,22 +365,46 @@ 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)
}
/// Mint balance to user with given `id` and `value`.
- pub fn mint_to>(&self, id: ID, value: Balance) {
- let actor_id = id.into().0;
- self.0
- .borrow_mut()
- .mint_to(&actor_id, value, MintMode::KeepAlive);
+ pub fn mint_to>(&self, id: ID, value: 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(
+ &self,
+ from: impl Into,
+ to: impl Into,
+ value: Value,
+ keep_alive: bool,
+ ) {
+ let from = from.into().0;
+ let to = to.into().0;
+
+ 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`.
- 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)
}
@@ -397,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();
}
}
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