From 9fcabda348406843a9ddac228fdaba3a3af7424b Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Mon, 20 Nov 2023 17:32:43 +0400 Subject: [PATCH] bugfix(runtime): Correct charging on delayed sendings from reservations (#3496) --- Cargo.lock | 9 + Cargo.toml | 2 + core-processor/src/ext.rs | 45 ++- core/src/reservation.rs | 9 + .../delayed-reservation-sender/Cargo.toml | 19 + examples/delayed-reservation-sender/build.rs | 21 ++ .../delayed-reservation-sender/src/lib.rs | 50 +++ .../delayed-reservation-sender/src/wasm.rs | 75 ++++ pallets/gear/Cargo.toml | 1 + pallets/gear/src/benchmarking/syscalls.rs | 2 +- pallets/gear/src/internal.rs | 30 +- pallets/gear/src/tests.rs | 329 ++++++++++++------ 12 files changed, 454 insertions(+), 138 deletions(-) create mode 100644 examples/delayed-reservation-sender/Cargo.toml create mode 100644 examples/delayed-reservation-sender/build.rs create mode 100644 examples/delayed-reservation-sender/src/lib.rs create mode 100644 examples/delayed-reservation-sender/src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index 7829228449b..b29e56aa4b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2130,6 +2130,14 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "demo-delayed-reservation-sender" +version = "0.1.0" +dependencies = [ + "gear-wasm-builder", + "gstd", +] + [[package]] name = "demo-delayed-sender" version = "0.1.0" @@ -7569,6 +7577,7 @@ dependencies = [ "demo-compose", "demo-constructor", "demo-custom", + "demo-delayed-reservation-sender", "demo-delayed-sender", "demo-distributor", "demo-futures-unordered", diff --git a/Cargo.toml b/Cargo.toml index 7ef743886fd..f50ec0fcf5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "examples/autoreply", "examples/calc-hash", "examples/custom", + "examples/delayed-reservation-sender", "examples/compose", "examples/constructor", "examples/delayed-sender", @@ -384,6 +385,7 @@ demo-calc-hash = { path = "examples/calc-hash" } demo-calc-hash-in-one-block = { path = "examples/calc-hash/in-one-block" } demo-calc-hash-over-blocks = { path = "examples/calc-hash/over-blocks" } demo-custom = { path = "examples/custom" } +demo-delayed-reservation-sender = { path = "examples/delayed-reservation-sender" } demo-compose = { path = "examples/compose" } demo-constructor = { path = "examples/constructor", default-features = false } demo-delayed-sender = { path = "examples/delayed-sender" } diff --git a/core-processor/src/ext.rs b/core-processor/src/ext.rs index fed8880e141..b988993fb09 100644 --- a/core-processor/src/ext.rs +++ b/core-processor/src/ext.rs @@ -505,6 +505,46 @@ impl Ext { } } + // TODO: gasful sending (#1828). + /// Check reservation gas for gasless sending. + /// Gas of reservation for sending should be [mailbox_threshold + delay price; +inf). + fn check_reservation_gas_limit( + &mut self, + reservation_id: &ReservationId, + delay: u32, + ) -> Result<(), FallibleExtError> { + let limit = self + .context + .gas_reserver + .limit_of(reservation_id) + .ok_or(ReservationError::InvalidReservationId)?; + + // Almost unreachable since reservation couldn't be created + // with gas less than mailbox_threshold. + // + // TODO: review this place once more in #1828. + let limit = limit + .checked_sub(self.context.mailbox_threshold) + .ok_or(MessageError::InsufficientGasLimit)?; + + // Checking that reservation could be charged for + // dispatch stash with given delay. + if delay != 0 { + // Take delay and get cost of block. + // reserve = wait_cost * (delay + reserve_for). + let cost_per_block = self.context.dispatch_hold_cost; + let waiting_reserve = (self.context.reserve_for as u64) + .saturating_add(delay as u64) + .saturating_mul(cost_per_block); + + if limit < waiting_reserve { + return Err(MessageError::InsufficientGasForDelayedSending.into()); + } + } + + Ok(()) + } + fn reduce_gas(&mut self, gas_limit: GasLimit) -> Result<(), FallibleExtError> { if self.context.gas_counter.reduce(gas_limit) != ChargeResult::Enough { Err(FallibleExecutionError::NotEnoughGas.into()) @@ -804,13 +844,11 @@ impl Externalities for Ext { ) -> Result { self.check_forbidden_destination(msg.destination())?; self.check_message_value(msg.value())?; - self.check_gas_limit(msg.gas_limit())?; + self.check_reservation_gas_limit(&id, delay)?; // TODO: gasful sending (#1828) self.charge_message_value(msg.value())?; self.charge_sending_fee(delay)?; - self.charge_for_dispatch_stash_hold(delay)?; - self.context.gas_reserver.mark_used(id)?; let msg_id = self @@ -843,6 +881,7 @@ impl Externalities for Ext { ) -> Result { self.check_forbidden_destination(self.context.message_context.reply_destination())?; self.check_message_value(msg.value())?; + self.check_reservation_gas_limit(&id, 0)?; // TODO: gasful sending (#1828) self.charge_message_value(msg.value())?; self.charge_sending_fee(0)?; diff --git a/core/src/reservation.rs b/core/src/reservation.rs index 31718e8bab0..4e7f2f7a49f 100644 --- a/core/src/reservation.rs +++ b/core/src/reservation.rs @@ -151,6 +151,15 @@ impl GasReserver { } } + /// Returns amount of gas in reservation, if exists. + pub fn limit_of(&self, reservation_id: &ReservationId) -> Option { + self.states.get(reservation_id).and_then(|v| match v { + GasReservationState::Exists { amount, .. } + | GasReservationState::Created { amount, .. } => Some(*amount), + _ => None, + }) + } + /// Reserves gas. /// /// Creates a new reservation and returns its id. diff --git a/examples/delayed-reservation-sender/Cargo.toml b/examples/delayed-reservation-sender/Cargo.toml new file mode 100644 index 00000000000..e313b5080da --- /dev/null +++ b/examples/delayed-reservation-sender/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "demo-delayed-reservation-sender" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +gstd.workspace = true + +[build-dependencies] +gear-wasm-builder.workspace = true + +[features] +debug = ["gstd/debug"] +default = ["std"] +std = [] diff --git a/examples/delayed-reservation-sender/build.rs b/examples/delayed-reservation-sender/build.rs new file mode 100644 index 00000000000..4c502a3ddee --- /dev/null +++ b/examples/delayed-reservation-sender/build.rs @@ -0,0 +1,21 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 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 . + +fn main() { + gear_wasm_builder::build(); +} diff --git a/examples/delayed-reservation-sender/src/lib.rs b/examples/delayed-reservation-sender/src/lib.rs new file mode 100644 index 00000000000..0e63cbb1ca8 --- /dev/null +++ b/examples/delayed-reservation-sender/src/lib.rs @@ -0,0 +1,50 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 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 . + +#![no_std] + +use gstd::codec::{Decode, Encode}; + +#[cfg(feature = "std")] +mod code { + include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +} + +#[cfg(feature = "std")] +pub use code::WASM_BINARY_OPT as WASM_BINARY; + +pub const SENDING_EXPECT: &str = "Failed to send delayed message from reservation"; + +#[derive(Encode, Decode, Debug, Clone, Copy)] +#[codec(crate = gstd::codec)] +pub enum ReservationSendingShowcase { + ToSourceInPlace { + reservation_amount: u64, + reservation_delay: u32, + sending_delay: u32, + }, + ToSourceAfterWait { + reservation_amount: u64, + reservation_delay: u32, + wait_for: u32, + sending_delay: u32, + }, +} + +#[cfg(not(feature = "std"))] +mod wasm; diff --git a/examples/delayed-reservation-sender/src/wasm.rs b/examples/delayed-reservation-sender/src/wasm.rs new file mode 100644 index 00000000000..8887f1203ec --- /dev/null +++ b/examples/delayed-reservation-sender/src/wasm.rs @@ -0,0 +1,75 @@ +// This file is part of Gear. + +// Copyright (C) 2023 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 . + +use crate::{ReservationSendingShowcase, SENDING_EXPECT}; +use gstd::{exec, msg, prelude::*, ReservationId}; + +static mut CALLED_BEFORE: bool = false; +static mut RESERVATION_ID: Option = None; + +#[no_mangle] +extern "C" fn handle() { + let showcase = msg::load().expect("Failed to load request"); + + match showcase { + ReservationSendingShowcase::ToSourceInPlace { + reservation_amount, + reservation_delay, + sending_delay, + } => { + let reservation_id = ReservationId::reserve(reservation_amount, reservation_delay) + .expect("Failed to reserve gas"); + + msg::send_bytes_delayed_from_reservation( + reservation_id, + msg::source(), + [], + 0, + sending_delay, + ) + .expect(SENDING_EXPECT); + } + ReservationSendingShowcase::ToSourceAfterWait { + reservation_amount, + reservation_delay, + wait_for, + sending_delay, + } => { + if unsafe { !CALLED_BEFORE } { + let reservation_id = ReservationId::reserve(reservation_amount, reservation_delay) + .expect("Failed to reserve gas"); + + unsafe { + CALLED_BEFORE = true; + RESERVATION_ID = Some(reservation_id); + } + + exec::wait_for(wait_for); + } + + msg::send_bytes_delayed_from_reservation( + unsafe { RESERVATION_ID.expect("Unset") }, + msg::source(), + [], + 0, + sending_delay, + ) + .expect(SENDING_EXPECT); + } + } +} diff --git a/pallets/gear/Cargo.toml b/pallets/gear/Cargo.toml index b0e76956394..74f38e368cf 100644 --- a/pallets/gear/Cargo.toml +++ b/pallets/gear/Cargo.toml @@ -113,6 +113,7 @@ demo-out-of-memory.workspace = true demo-ping.workspace = true demo-sync-duplicate.workspace = true demo-custom.workspace = true +demo-delayed-reservation-sender.workspace = true test-syscalls.workspace = true page_size.workspace = true frame-support-test = { workspace = true, features = ["std"] } diff --git a/pallets/gear/src/benchmarking/syscalls.rs b/pallets/gear/src/benchmarking/syscalls.rs index 48e6869b26e..24db02c23bd 100644 --- a/pallets/gear/src/benchmarking/syscalls.rs +++ b/pallets/gear/src/benchmarking/syscalls.rs @@ -182,7 +182,7 @@ where program.gas_reservation_map.insert( ReservationId::from(x as u64), GasReservationSlot { - amount: 1_000, + amount: 100_000, start: 1, finish: 100, }, diff --git a/pallets/gear/src/internal.rs b/pallets/gear/src/internal.rs index 5fbb0831d39..169a6535ee7 100644 --- a/pallets/gear/src/internal.rs +++ b/pallets/gear/src/internal.rs @@ -535,8 +535,7 @@ where let hold_builder = HoldBoundBuilder::::new(StorageType::DispatchStash); // Calculating correct gas amount for delay. - let bn_delay = delay.saturated_into::>(); - let delay_hold = hold_builder.clone().duration(bn_delay); + let delay_hold = hold_builder.duration(delay.saturated_into()); let gas_for_delay = delay_hold.lock_amount(); let interval_finish = if to_user { @@ -591,22 +590,18 @@ where Self::remove_gas_reservation_with_task(dispatch.source(), reservation_id); } - // Calculating correct hold bound to lock gas. - let maximal_hold = hold_builder.maximum_for_message(dispatch.id()); - let hold = delay_hold.min(maximal_hold); - // Locking funds for holding. - let lock_id = hold.lock_id().unwrap_or_else(|| { + let lock_id = delay_hold.lock_id().unwrap_or_else(|| { unreachable!("DispatchStash storage is guaranteed to have an associated lock id") }); - GasHandlerOf::::lock(dispatch.id(), lock_id, hold.lock_amount()) + GasHandlerOf::::lock(dispatch.id(), lock_id, delay_hold.lock_amount()) .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); - if hold.expected_duration().is_zero() { + if delay_hold.expected_duration().is_zero() { unreachable!("Hold duration cannot be zero"); } - hold.expected() + delay_hold.expected() } else { match (dispatch.gas_limit(), reservation) { (Some(gas_limit), None) => Self::split_with_value( @@ -629,25 +624,18 @@ where } } - // `HoldBound` builder. - let hold_builder = HoldBoundBuilder::::new(StorageType::DispatchStash); - - // Calculating correct hold bound to lock gas. - let maximal_hold = hold_builder.maximum_for_message(dispatch.id()); - let hold = delay_hold.min(maximal_hold); - // Locking funds for holding. - let lock_id = hold.lock_id().unwrap_or_else(|| { + let lock_id = delay_hold.lock_id().unwrap_or_else(|| { unreachable!("DispatchStash storage is guaranteed to have an associated lock id") }); - GasHandlerOf::::lock(dispatch.id(), lock_id, hold.lock_amount()) + GasHandlerOf::::lock(dispatch.id(), lock_id, delay_hold.lock_amount()) .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); - if hold.expected_duration().is_zero() { + if delay_hold.expected_duration().is_zero() { unreachable!("Hold duration cannot be zero"); } - hold.expected() + delay_hold.expected() }; if !dispatch.value().is_zero() { diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 1c539cd4c38..a89a619ace2 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -83,6 +83,222 @@ use utils::*; type Gas = <::GasProvider as common::GasProvider>::GasTree; +#[test] +fn calculate_gas_delayed_reservations_sending() { + use demo_delayed_reservation_sender::{ReservationSendingShowcase, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000u64, + 0, + false, + )); + + let pid = get_last_program_id(); + + run_to_next_block(None); + assert!(Gear::is_initialized(pid)); + + // I. In-place case + assert!(Gear::calculate_gas_info( + USER_1.into_origin(), + HandleKind::Handle(pid), + ReservationSendingShowcase::ToSourceInPlace { + reservation_amount: 10 * ::MailboxThreshold::get(), + reservation_delay: 1_000, + sending_delay: 10, + } + .encode(), + 0, + true, + true, + ) + .is_ok()); + + // II. After-wait case (never failed before, added for test coverage). + assert!(Gear::calculate_gas_info( + USER_1.into_origin(), + HandleKind::Handle(pid), + ReservationSendingShowcase::ToSourceAfterWait { + reservation_amount: 10 * ::MailboxThreshold::get(), + reservation_delay: 1_000, + wait_for: 3, + sending_delay: 10, + } + .encode(), + 0, + true, + true, + ) + .is_ok()); + }); +} + +#[test] +fn delayed_reservations_sending_validation() { + use demo_delayed_reservation_sender::{ + ReservationSendingShowcase, SENDING_EXPECT, WASM_BINARY, + }; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000u64, + 0, + false, + )); + + let pid = get_last_program_id(); + + run_to_next_block(None); + assert!(Gear::is_initialized(pid)); + + // I. In place sending can't appear if not enough gas limit in gas reservation. + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + ReservationSendingShowcase::ToSourceInPlace { + reservation_amount: 10 * ::MailboxThreshold::get(), + reservation_delay: 1_000, + sending_delay: 1_000 * ::MailboxThreshold::get() as u32 + + CostsPerBlockOf::::reserve_for() + / CostsPerBlockOf::::dispatch_stash() as u32, + } + .encode(), + BlockGasLimitOf::::get(), + 0, + false, + )); + + let mid = utils::get_last_message_id(); + + run_to_next_block(None); + + let error_text = if cfg!(any(feature = "debug", debug_assertions)) { + format!( + "{SENDING_EXPECT}: {:?}", + GstdError::Core( + ExtError::Message(MessageError::InsufficientGasForDelayedSending).into() + ) + ) + } else { + String::from("no info") + }; + + assert_failed( + mid, + ActorExecutionErrorReplyReason::Trap(TrapExplanation::Panic(error_text.into())), + ); + + // II. After-wait sending can't appear if not enough gas limit in gas reservation. + let wait_for = 5; + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + ReservationSendingShowcase::ToSourceAfterWait { + reservation_amount: 10 * ::MailboxThreshold::get(), + reservation_delay: 1_000, + wait_for, + sending_delay: 1_000 * ::MailboxThreshold::get() as u32 + + CostsPerBlockOf::::reserve_for() + / CostsPerBlockOf::::dispatch_stash() as u32, + } + .encode(), + BlockGasLimitOf::::get(), + 0, + false, + )); + + let mid = utils::get_last_message_id(); + + run_for_blocks(wait_for + 1, None); + + let error_text = if cfg!(any(feature = "debug", debug_assertions)) { + format!( + "{SENDING_EXPECT}: {:?}", + GstdError::Core( + ExtError::Message(MessageError::InsufficientGasForDelayedSending).into() + ) + ) + } else { + String::from("no info") + }; + + assert_failed( + mid, + ActorExecutionErrorReplyReason::Trap(TrapExplanation::Panic(error_text.into())), + ); + }); +} + +#[test] +fn delayed_reservations_to_mailbox() { + use demo_delayed_reservation_sender::{ReservationSendingShowcase, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000u64, + 0, + false, + )); + + let pid = get_last_program_id(); + + run_to_next_block(None); + assert!(Gear::is_initialized(pid)); + + let sending_delay = 10; + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + ReservationSendingShowcase::ToSourceInPlace { + reservation_amount: 10 * ::MailboxThreshold::get(), + reservation_delay: 1, + sending_delay, + } + .encode(), + BlockGasLimitOf::::get(), + 0, + false, + )); + + let mid = utils::get_last_message_id(); + + run_to_next_block(None); + + assert_succeed(mid); + + assert!(MailboxOf::::is_empty(&USER_1)); + + run_for_blocks(sending_delay, None); + + assert!(!MailboxOf::::is_empty(&USER_1)); + + let mailed_msg = utils::get_last_mail(USER_1); + let expiration = utils::get_mailbox_expiration(mailed_msg.id()); + + run_to_block(expiration, None); + + assert!(MailboxOf::::is_empty(&USER_1)); + }); +} + #[test] fn default_wait_lock_timeout() { use demo_async_tester::{Kind, WASM_BINARY}; @@ -1789,119 +2005,6 @@ fn delayed_send_program_message_with_reservation() { } } -#[test] -fn delayed_send_program_message_with_low_reservation() { - use demo_proxy_reservation_with_gas::{InputArgs, WASM_BINARY as PROXY_WGAS_WASM_BINARY}; - - // Testing that correct gas amount will be reserved and paid for holding. - fn scenario(delay: BlockNumber) { - // Upload empty program that receive the message. - assert_ok!(Gear::upload_program( - RuntimeOrigin::signed(USER_1), - ProgramCodeKind::OutgoingWithValueInHandle.to_bytes(), - DEFAULT_SALT.to_vec(), - EMPTY_PAYLOAD.to_vec(), - DEFAULT_GAS_LIMIT * 100, - 0, - false, - )); - - let program_address = utils::get_last_program_id(); - let reservation_amount = ::MailboxThreshold::get(); - - // Upload program that sends message to another program. - assert_ok!(Gear::upload_program( - RuntimeOrigin::signed(USER_1), - PROXY_WGAS_WASM_BINARY.to_vec(), - DEFAULT_SALT.to_vec(), - InputArgs { - destination: <[u8; 32]>::from(program_address).into(), - delay, - reservation_amount, - } - .encode(), - DEFAULT_GAS_LIMIT * 100, - 0, - false, - )); - - let proxy = utils::get_last_program_id(); - - run_to_next_block(None); - assert!(Gear::is_initialized(proxy)); - assert!(Gear::is_initialized(program_address)); - - assert_ok!(Gear::send_message( - RuntimeOrigin::signed(USER_1), - proxy, - 0u64.encode(), - DEFAULT_GAS_LIMIT * 100, - 0, - false, - )); - let proxy_msg_id = utils::get_last_message_id(); - - // Run blocks to make message get into dispatch stash. - run_to_block(3, None); - - let delay_holding_fee = gas_price( - CostsPerBlockOf::::dispatch_stash().saturating_mul( - delay - .saturating_add(CostsPerBlockOf::::reserve_for()) - .saturated_into(), - ), - ); - - let reservation_holding_fee = gas_price( - 80u64 - .saturating_add(CostsPerBlockOf::::reserve_for().unique_saturated_into()) - .saturating_mul(CostsPerBlockOf::::reservation()), - ); - - let delayed_id = MessageId::generate_outgoing(proxy_msg_id, 0); - - // Check that delayed task was created - assert!(TaskPoolOf::::contains( - &(delay + 3), - &ScheduledTask::SendDispatch(delayed_id) - )); - - // Check that correct amount locked for dispatch stash - let gas_locked_in_gas_node = - gas_price(Gas::get_lock(delayed_id, LockId::DispatchStash).unwrap()); - assert_eq!(gas_locked_in_gas_node, delay_holding_fee); - - // Gas should be reserved while message is being held in storage. - assert_eq!( - GearBank::::account_total(&USER_1), - gas_price(reservation_amount) + reservation_holding_fee - ); - - // Run blocks to release message. - run_to_block(delay + 2, None); - - // Check that delayed task was created - assert!(TaskPoolOf::::contains( - &(delay + 3), - &ScheduledTask::SendDispatch(delayed_id) - )); - - // Block where message processed - run_to_next_block(None); - - // Check that last event is MessagesDispatched. - assert_last_dequeued(2); - - assert_eq!(GearBank::::account_total(&USER_1), 0); - } - - init_logger(); - - for i in 2..4 { - new_test_ext().execute_with(|| scenario(i)); - } -} - #[test] fn delayed_program_creation_no_code() { init_logger();