Skip to content

Commit

Permalink
feat(pallet-gear): Introduce extrinsic to transfer value from termina…
Browse files Browse the repository at this point in the history
…ted/exited program (#3949)
  • Loading branch information
ark0f authored Jul 17, 2024
1 parent 7576449 commit e1f61f9
Show file tree
Hide file tree
Showing 9 changed files with 608 additions and 72 deletions.
11 changes: 11 additions & 0 deletions gsdk/src/metadata/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2188,6 +2188,12 @@ pub mod runtime_types {
#[codec(index = 7)]
#[doc = "See [`Pallet::set_execute_inherent`]."]
set_execute_inherent { value: ::core::primitive::bool },
#[codec(index = 8)]
#[doc = "See [`Pallet::claim_value_to_inheritor`]."]
claim_value_to_inheritor {
program_id: runtime_types::gprimitives::ActorId,
depth: ::core::num::NonZeroU32,
},
}
#[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)]
#[doc = "The `Error` enum of this pallet."]
Expand Down Expand Up @@ -2252,6 +2258,9 @@ pub mod runtime_types {
#[codec(index = 14)]
#[doc = "The program rent logic is disabled."]
ProgramRentDisabled,
#[codec(index = 15)]
#[doc = "Program is active."]
ActiveProgram,
}
#[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)]
#[doc = "The `Event` enum of this pallet"]
Expand Down Expand Up @@ -8099,6 +8108,7 @@ pub mod calls {
ClaimValue,
Run,
SetExecuteInherent,
ClaimValueToInheritor,
}
impl CallInfo for GearCall {
const PALLET: &'static str = "Gear";
Expand All @@ -8112,6 +8122,7 @@ pub mod calls {
Self::ClaimValue => "claim_value",
Self::Run => "run",
Self::SetExecuteInherent => "set_execute_inherent",
Self::ClaimValueToInheritor => "claim_value_to_inheritor",
}
}
}
Expand Down
45 changes: 44 additions & 1 deletion pallets/gear/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ use sp_runtime::{
traits::{Bounded, CheckedAdd, One, UniqueSaturatedInto, Zero},
Digest, DigestItem, Perbill, Saturating,
};
use sp_std::prelude::*;
use sp_std::{num::NonZeroU32, prelude::*};

const MAX_PAYLOAD_LEN: u32 = 32 * 64 * 1024;
const MAX_PAYLOAD_LEN_KB: u32 = MAX_PAYLOAD_LEN / 1024;
Expand Down Expand Up @@ -594,6 +594,49 @@ benchmarks! {
assert!(MailboxOf::<T>::is_empty(&caller))
}

claim_value_to_inheritor {
let d in 1 .. 1024;

let minimum_balance = CurrencyOf::<T>::minimum_balance();

let caller: T::AccountId = benchmarking::account("caller", 0, 0);

let mut inheritor = caller.clone().cast();
let mut programs = vec![];
for i in 0..d {
let program_id = benchmarking::account::<T::AccountId>("program", i, 100);
programs.push(program_id.clone());
let _ = CurrencyOf::<T>::deposit_creating(&program_id, minimum_balance);
let program_id = program_id.cast();
benchmarking::set_program::<ProgramStorageOf::<T>, _>(program_id, vec![], 1.into());

ProgramStorageOf::<T>::update_program_if_active(program_id, |program, _bn| {
if i % 2 == 0 {
*program = common::Program::Terminated(inheritor);
} else {
*program = common::Program::Exited(inheritor);
}
})
.unwrap();

inheritor = program_id;
}

let program_id = inheritor;

init_block::<T>(None);
}: _(RawOrigin::Signed(caller.clone()), program_id, NonZeroU32::MAX)
verify {
assert_eq!(
CurrencyOf::<T>::free_balance(&caller),
minimum_balance * d.unique_saturated_into()
);

for program_id in programs {
assert_eq!(CurrencyOf::<T>::free_balance(&program_id), BalanceOf::<T>::zero());
}
}

// This benchmarks the additional weight that is charged when a program is executed the
// first time after a new schedule was deployed: For every new schedule a program needs
// to re-run the instrumentation once.
Expand Down
42 changes: 33 additions & 9 deletions pallets/gear/src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ use common::{
storage::*,
GasTree, LockId, LockableTree, Origin,
};
use core::cmp::{Ord, Ordering};
use core::{
cmp::{Ord, Ordering},
num::NonZeroUsize,
};
use frame_support::traits::{fungible, Currency, ExistenceRequirement};
use frame_system::pallet_prelude::BlockNumberFor;
use gear_core::{
Expand Down Expand Up @@ -189,6 +192,12 @@ where
}
}

#[derive(Debug, Eq, PartialEq)]
pub(crate) enum InheritorForError {
Cyclic { holders: BTreeSet<ProgramId> },
NotFound,
}

// Internal functionality implementation.
impl<T: Config> Pallet<T>
where
Expand Down Expand Up @@ -922,22 +931,37 @@ where
);
}

pub(crate) fn inheritor_for(program_id: ProgramId) -> ProgramId {
pub(crate) fn inheritor_for(
program_id: ProgramId,
max_depth: NonZeroUsize,
) -> Result<(ProgramId, BTreeSet<ProgramId>), InheritorForError> {
let max_depth = max_depth.get();

let mut inheritor = program_id;
let mut holders: BTreeSet<_> = [program_id].into();

let mut visited_ids: BTreeSet<_> = [program_id].into();
loop {
let next_inheritor =
Self::first_inheritor_of(inheritor).ok_or(InheritorForError::NotFound)?;

while let Some(id) =
Self::exit_inheritor_of(inheritor).or_else(|| Self::termination_inheritor_of(inheritor))
{
if !visited_ids.insert(id) {
inheritor = next_inheritor;

// don't insert user or active program
// because it's the final inheritor we already return
if Self::first_inheritor_of(next_inheritor).is_none() {
break;
}

inheritor = id
if holders.len() == max_depth {
break;
}

if !holders.insert(next_inheritor) {
return Err(InheritorForError::Cyclic { holders });
}
}

inheritor
Ok((inheritor, holders))
}

/// This fn and [`split_with_value`] works the same: they call api of gas
Expand Down
111 changes: 86 additions & 25 deletions pallets/gear/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub use crate::{
pub use gear_core::{gas::GasInfo, message::ReplyInfo};
pub use weights::WeightInfo;

use crate::internal::InheritorForError;
use alloc::{
format,
string::{String, ToString},
Expand All @@ -61,7 +62,7 @@ use common::{
self, event::*, gas_provider::GasNodeId, scheduler::*, storage::*, BlockLimiter, CodeMetadata,
CodeStorage, GasProvider, GasTree, Origin, Program, ProgramStorage, QueueRunner,
};
use core::marker::PhantomData;
use core::{marker::PhantomData, num::NonZeroU32};
use core_processor::{
common::{DispatchOutcome as CoreDispatchOutcome, ExecutableActorData, JournalNote},
configs::{BlockConfig, BlockInfo},
Expand Down Expand Up @@ -455,6 +456,8 @@ pub mod pallet {
GearRunAlreadyInBlock,
/// The program rent logic is disabled.
ProgramRentDisabled,
/// Program is active.
ActiveProgram,
}

#[cfg(feature = "runtime-benchmarks")]
Expand Down Expand Up @@ -901,30 +904,13 @@ pub mod pallet {
|| ProgramStorageOf::<T>::program_exists(program_id)
}

/// Returns exit argument of an exited program.
pub fn exit_inheritor_of(program_id: ProgramId) -> Option<ProgramId> {
ProgramStorageOf::<T>::get_program(program_id)
.map(|program| {
if let Program::Exited(inheritor) = program {
Some(inheritor)
} else {
None
}
})
.unwrap_or_default()
}

/// Returns inheritor of terminated (failed it's init) program.
pub fn termination_inheritor_of(program_id: ProgramId) -> Option<ProgramId> {
ProgramStorageOf::<T>::get_program(program_id)
.map(|program| {
if let Program::Terminated(inheritor) = program {
Some(inheritor)
} else {
None
}
})
.unwrap_or_default()
/// Returns inheritor of an exited/terminated program.
pub fn first_inheritor_of(program_id: ProgramId) -> Option<ProgramId> {
ProgramStorageOf::<T>::get_program(program_id).and_then(|program| match program {
Program::Active(_) => None,
Program::Exited(id) => Some(id),
Program::Terminated(id) => Some(id),
})
}

/// Returns MessageId for newly created user message.
Expand Down Expand Up @@ -1675,6 +1661,81 @@ pub mod pallet {

Ok(())
}

/// Transfers value from chain of terminated or exited programs to its final inheritor.
///
/// `depth` parameter is how far to traverse to inheritor.
/// A value of 10 is sufficient for most cases.
///
/// # Example of chain
///
/// - Program #1 exits (e.g `gr_exit syscall) with argument pointing to user.
/// Balance of program #1 has been sent to user.
/// - Program #2 exits with inheritor pointing to program #1.
/// Balance of program #2 has been sent to exited program #1.
/// - Program #3 exits with inheritor pointing to program #2
/// Balance of program #1 has been sent to exited program #2.
///
/// So chain of inheritors looks like: Program #3 -> Program #2 -> Program #1 -> User.
///
/// We have programs #1 and #2 with stuck value on their balances.
/// The balances should've been transferred to user (final inheritor) according to the chain.
/// But protocol doesn't traverse the chain automatically, so user have to call this extrinsic.
#[pallet::call_index(8)]
#[pallet::weight(<T as Config>::WeightInfo::claim_value_to_inheritor(depth.get()))]
pub fn claim_value_to_inheritor(
origin: OriginFor<T>,
program_id: ProgramId,
depth: NonZeroU32,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;

let depth = depth.try_into().unwrap_or_else(|e| {
unreachable!("NonZeroU32 to NonZeroUsize conversion must be infallible: {e}")
});
let (destination, holders) = match Self::inheritor_for(program_id, depth) {
Ok(res) => res,
Err(InheritorForError::Cyclic { holders }) => {
// TODO: send value to treasury (#3979)
log::debug!("Cyclic inheritor detected for {program_id}");
return Ok(Some(<T as Config>::WeightInfo::claim_value_to_inheritor(
holders.len() as u32,
))
.into());
}
Err(InheritorForError::NotFound) => return Err(Error::<T>::ActiveProgram.into()),
};

let destination = destination.cast();

let holders_amount = holders.len();
for holder in holders {
// transfer is the same as in `Self::clean_inactive_program` except
// existential deposit is already unlocked because
// we work only with terminated/exited programs

let holder = holder.cast();
let balance = <CurrencyOf<T> as fungible::Inspect<_>>::reducible_balance(
&holder,
Preservation::Expendable,
Fortitude::Polite,
);

if !balance.is_zero() {
CurrencyOf::<T>::transfer(
&holder,
&destination,
balance,
ExistenceRequirement::AllowDeath,
)?;
}
}

Ok(Some(<T as Config>::WeightInfo::claim_value_to_inheritor(
holders_amount as u32,
))
.into())
}
}

impl<T: Config> Pallet<T>
Expand Down
13 changes: 2 additions & 11 deletions pallets/gear/src/manager/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,7 @@ where

match p {
Program::Active(program) => {
Self::remove_gas_reservation_map(
id_exited,
core::mem::take(&mut program.gas_reservation_map),
);

Self::clean_inactive_program(
id_exited,
program.memory_infix,
value_destination,
);
Self::clean_inactive_program(id_exited, program, value_destination)
}
_ => unreachable!("Action executed only for active program"),
}
Expand Down Expand Up @@ -359,7 +350,7 @@ where
}

fn send_value(&mut self, from: ProgramId, to: Option<ProgramId>, value: u128) {
let to = Pallet::<T>::inheritor_for(to.unwrap_or(from)).cast();
let to = to.unwrap_or(from).cast();
let from = from.cast();
let value = value.unique_saturated_into();

Expand Down
Loading

0 comments on commit e1f61f9

Please sign in to comment.