Skip to content

Commit

Permalink
feat(pallet-gear): Migrate to fungible traits
Browse files Browse the repository at this point in the history
  • Loading branch information
ukint-vs committed Jul 18, 2023
1 parent 285edb8 commit 1af05e4
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 80 deletions.
106 changes: 80 additions & 26 deletions pallets/gear/src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
use crate::{
Authorship, BalanceOf, Config, CostsPerBlockOf, CurrencyOf, DispatchStashOf, Event, ExtManager,
GasBalanceOf, GasHandlerOf, GasNodeIdOf, MailboxOf, Pallet, QueueOf, SchedulingCostOf,
TaskPoolOf, WaitlistOf,
GasBalanceOf, GasHandlerOf, GasNodeIdOf, HoldReason, MailboxOf, Pallet, QueueOf,
SchedulingCostOf, TaskPoolOf, WaitlistOf,
};
use alloc::collections::BTreeSet;
use common::{
Expand All @@ -37,7 +37,11 @@ use common::{
};
use core::cmp::{Ord, Ordering};
use core_processor::common::ActorExecutionErrorReplyReason;
use frame_support::traits::{BalanceStatus, Currency, ExistenceRequirement, ReservableCurrency};
use frame_support::traits::{
fungible::{Inspect, InspectHold, Mutate, MutateHold},
tokens::{Fortitude, Precision, Preservation, Restriction},
BalanceStatus, Currency, ExistenceRequirement, ReservableCurrency,
};
use frame_system::pallet_prelude::BlockNumberFor;
use gear_core::{
ids::{MessageId, ProgramId, ReservationId},
Expand Down Expand Up @@ -223,35 +227,67 @@ where
return;
}

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

// If destination account can reserve minimum balance, it means that
// account exists and can receive repatriation of reserved funds.
//
// Otherwise need to transfer them directly.

// Checking balance existence of destination address.
if CurrencyOf::<T>::can_reserve(to, CurrencyOf::<T>::minimum_balance()) {
if CurrencyOf::<T>::can_hold(
&HoldReason::StorageDepositReserve.into(),
to,
CurrencyOf::<T>::minimum_balance(),
) {
// Repatriating reserved to existent account.
let unrevealed =
CurrencyOf::<T>::repatriate_reserved(from, to, value, BalanceStatus::Free)
.unwrap_or_else(|e| {
unreachable!("Failed to repatriate reserved funds: {:?}", e)
});
let unrevealed = CurrencyOf::<T>::transfer_on_hold(
&HoldReason::StorageDepositReserve.into(),
from,
to,
value,
Precision::BestEffort,
Restriction::Free,
Fortitude::Polite,
)
.unwrap_or_else(|e| unreachable!("Failed to transfer on hold funds: {:?}", e));

// Validating unrevealed funds after repatriation.
if !unrevealed.is_zero() {
unreachable!("Reserved funds wasn't fully repatriated: {:?}", unrevealed)
if unrevealed < value {
// CurrencyOf::<T>::transfer(
// from,
// to,
// value.saturating_sub(unrevealed),
// Preservation::Expendable,
// )
// .unwrap_or_else(|e| {
// unreachable!(
// "Reserved funds wasn't fully repatriated: {:?} {:?}, {:?}",
// unrevealed, value, e
// )
// });
unreachable!(
"Reserved funds wasn't fully repatriated: {:?} {:?}",
unrevealed, value
)
}
} else {
// Unreserving funds from sender to transfer them directly.
let unrevealed = CurrencyOf::<T>::unreserve(from, value);
let unrevealed = CurrencyOf::<T>::release(
&HoldReason::StorageDepositReserve.into(),
from,
value,
Precision::BestEffort,
)
.unwrap_or_else(|e| unreachable!("Failed to release hold funds: {:?}", e));

// Validating unrevealed funds after unreserve.
if !unrevealed.is_zero() {
if unrevealed < value {
unreachable!("Not all requested value was unreserved");
}

// Transfer to inexistent account.
CurrencyOf::<T>::transfer(from, to, value, ExistenceRequirement::AllowDeath)
CurrencyOf::<T>::transfer(from, to, value, Preservation::Expendable)
.unwrap_or_else(|e| unreachable!("Failed to transfer value: {:?}", e));
}
}
Expand Down Expand Up @@ -312,10 +348,16 @@ where
let value = T::GasPrice::gas_price(gas_left);

// Unreserving funds.
let unrevealed = CurrencyOf::<T>::unreserve(&external, value);
let unrevealed = CurrencyOf::<T>::release(
&HoldReason::StorageDepositReserve.into(),
&external,
value,
Precision::BestEffort,
)
.unwrap_or_else(|e| unreachable!("Failed to release hold funds: {:?}", e));

// Validating unrevealed funds after unreserve.
if !unrevealed.is_zero() {
if unrevealed < value {
unreachable!("Not all requested value was unreserved");
}
}
Expand Down Expand Up @@ -587,7 +629,7 @@ where

// Taking data for funds manipulations.
let from = <T::AccountId as Origin>::from_origin(dispatch.source().into_origin());
let value = dispatch.value().unique_saturated_into();
let value: BalanceOf<T> = dispatch.value().unique_saturated_into();

// `HoldBound` builder.
let hold_builder = HoldBoundBuilder::<T>::new(StorageType::DispatchStash);
Expand Down Expand Up @@ -709,9 +751,11 @@ where
};

if !dispatch.value().is_zero() {
// Reserving value from source for future transfer or unreserve.
CurrencyOf::<T>::reserve(&from, value)
.unwrap_or_else(|e| unreachable!("Unable to reserve requested value {:?}", e));
let ed = CurrencyOf::<T>::minimum_balance();
// Hold value from source for future transfer or release.
log::info!(target: "gear::internal", "balance: {:?}, value: {:?}, from: {:?}", CurrencyOf::<T>::balance(&from), value, &from);
CurrencyOf::<T>::hold(&HoldReason::StorageDepositReserve.into(), &from, value)
.unwrap_or_else(|e| unreachable!("Failed to hold funds: {:?}", e));
}

// Saving id to allow moving dispatch further.
Expand Down Expand Up @@ -799,7 +843,7 @@ where
// Taking data for funds manipulations.
let from = <T::AccountId as Origin>::from_origin(message.source().into_origin());
let to = <T::AccountId as Origin>::from_origin(message.destination().into_origin());
let value = message.value().unique_saturated_into();
let value: BalanceOf<T> = message.value().unique_saturated_into();

// If gas limit can cover threshold, message will be added to mailbox,
// task created and funds reserved.
Expand All @@ -816,9 +860,19 @@ where
GasHandlerOf::<T>::cut(msg_id, message.id(), gas_limit)
.unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e));

// Reserving value from source for future transfer or unreserve.
CurrencyOf::<T>::reserve(&from, value)
.unwrap_or_else(|e| unreachable!("Unable to reserve requested value {:?}", e));
if !value.is_zero() {
let ed = CurrencyOf::<T>::minimum_balance();
// log::info!(target: "gear::internal", "balance: {:?}, value: {:?}, from: {:?}", CurrencyOf::<T>::total_balance(&from), value, &from);
log::info!(target: "gear::internal", "hold balance: {:?}, value: {:?}, from: {:?}", CurrencyOf::<T>::balance(&from), value, &from);
// Hold value from source for future transfer or release.
CurrencyOf::<T>::hold(
&HoldReason::StorageDepositReserve.into(),
&from,
value.saturating_sub(ed),
// value,
)
.unwrap_or_else(|e| unreachable!("Failed to hold funds: {:?}", e));
}

// Lock the entire `gas_limit` since the only purpose of it is payment for storage.
GasHandlerOf::<T>::lock(message.id(), LockId::Mailbox, gas_limit)
Expand All @@ -844,7 +898,7 @@ where
Some(hold.expected())
} else {
// Permanently transferring funds.
CurrencyOf::<T>::transfer(&from, &to, value, ExistenceRequirement::AllowDeath)
CurrencyOf::<T>::transfer(&from, &to, value, Preservation::Expendable)
.unwrap_or_else(|e| unreachable!("Failed to transfer value: {:?}", e));

if message.details().is_none() {
Expand Down Expand Up @@ -1041,7 +1095,7 @@ where
from,
&block_author,
Self::rent_fee_for(blocks_to_pay),
ExistenceRequirement::AllowDeath,
Preservation::Expendable,
)?;

let task = ScheduledTask::PauseProgram(program_id);
Expand Down
89 changes: 61 additions & 28 deletions pallets/gear/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ use frame_support::{
ensure,
pallet_prelude::*,
traits::{
fungible::{Inspect, InspectHold, Mutate, MutateHold},
tokens::Preservation,
ConstBool, Currency, ExistenceRequirement, Get, LockableCurrency, Randomness,
ReservableCurrency, StorageVersion,
},
Expand Down Expand Up @@ -107,7 +109,7 @@ type ExecutionEnvironment<EP = DispatchKind> = gear_backend_sandbox::SandboxEnvi

pub(crate) type CurrencyOf<T> = <T as Config>::Currency;
pub(crate) type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
pub(crate) type SentOf<T> = <<T as Config>::Messenger as Messenger>::Sent;
pub(crate) type DbWeightOf<T> = <T as frame_system::Config>::DbWeight;
pub(crate) type DequeuedOf<T> = <<T as Config>::Messenger as Messenger>::Dequeued;
Expand Down Expand Up @@ -194,7 +196,12 @@ pub mod pallet {
type Randomness: Randomness<Self::Hash, Self::BlockNumber>;

/// Balances management trait for gas/value migrations.
type Currency: LockableCurrency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
type Currency: Inspect<Self::AccountId>
+ Mutate<Self::AccountId>
+ MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;

/// Overarching hold reason.
type RuntimeHoldReason: From<HoldReason>;

/// Gas to Currency converter
type GasPrice: GasPrice<Balance = BalanceOf<Self>>;
Expand Down Expand Up @@ -479,6 +486,13 @@ pub mod pallet {
FailureRedeemingVoucher,
}

/// A reason for the pallet contracts placing a hold on funds.
#[pallet::composite_enum]
pub enum HoldReason {
/// The Pallet has reserved it for storage deposit.
StorageDepositReserve,
}

#[cfg(feature = "runtime-benchmarks")]
#[pallet::storage]
pub(crate) type BenchmarkStorage<T> = StorageMap<_, Identity, u32, Vec<u8>>;
Expand Down Expand Up @@ -626,10 +640,14 @@ pub mod pallet {

let reserve_fee = T::GasPrice::gas_price(gas_limit);

// First we reserve enough funds on the account to pay for `gas_limit`
// First we hold enough funds on the account to pay for `gas_limit`
// and to transfer declared value.
CurrencyOf::<T>::reserve(&who, reserve_fee + value)
.map_err(|_| Error::<T>::InsufficientBalance)?;
CurrencyOf::<T>::hold(
&HoldReason::StorageDepositReserve.into(),
&who,
reserve_fee + value,
)
.map_err(|_| Error::<T>::InsufficientBalance)?;

let origin = who.clone().into_origin();

Expand Down Expand Up @@ -1215,8 +1233,12 @@ pub mod pallet {

// First we reserve enough funds on the account to pay for `gas_limit`
// and to transfer declared value.
<T as Config>::Currency::reserve(&who, reserve_fee + value)
.map_err(|_| Error::<T>::InsufficientBalance)?;
CurrencyOf::<T>::hold(
&HoldReason::StorageDepositReserve.into(),
&who,
reserve_fee + value,
)
.map_err(|_| Error::<T>::InsufficientBalance)?;

Ok(packet)
}
Expand Down Expand Up @@ -1508,14 +1530,22 @@ pub mod pallet {
// Message is not guaranteed to be executed, that's why value is not immediately transferred.
// That's because destination can fail to be initialized, while this dispatch message is next
// in the queue.
CurrencyOf::<T>::reserve(&who, value.unique_saturated_into())
.map_err(|_| Error::<T>::InsufficientBalance)?;
CurrencyOf::<T>::hold(
&HoldReason::StorageDepositReserve.into(),
&who,
value.unique_saturated_into(),
)
.map_err(|_| Error::<T>::InsufficientBalance)?;

let gas_limit_reserve = T::GasPrice::gas_price(gas_limit);

// First we reserve enough funds on the account to pay for `gas_limit`
CurrencyOf::<T>::reserve(&who, gas_limit_reserve)
.map_err(|_| Error::<T>::InsufficientBalance)?;
CurrencyOf::<T>::hold(
&HoldReason::StorageDepositReserve.into(),
&who,
gas_limit_reserve,
)
.map_err(|_| Error::<T>::InsufficientBalance)?;

Self::create(who.clone(), message.id(), gas_limit, false);

Expand All @@ -1541,7 +1571,7 @@ pub mod pallet {
message.destination().into_origin(),
),
value.unique_saturated_into(),
ExistenceRequirement::AllowDeath,
Preservation::Expendable,
)
.map_err(|_| Error::<T>::InsufficientBalance)?;

Expand Down Expand Up @@ -1610,12 +1640,16 @@ pub mod pallet {
// Converting applied gas limit into value to reserve.
let gas_limit_reserve = T::GasPrice::gas_price(gas_limit);

// Reserving funds for gas limit and value sending.
// Hold funds for gas limit and value sending.
//
// Note, that message is not guaranteed to be successfully executed,
// that's why value is not immediately transferred.
CurrencyOf::<T>::reserve(&origin, gas_limit_reserve + value)
.map_err(|_| Error::<T>::InsufficientBalance)?;
CurrencyOf::<T>::hold(
&HoldReason::StorageDepositReserve.into(),
&origin,
gas_limit_reserve + value,
)
.map_err(|_| Error::<T>::InsufficientBalance)?;

// Creating reply message.
let message = ReplyMessage::from_packet(
Expand Down Expand Up @@ -1864,7 +1898,7 @@ pub mod pallet {

let rent_fee = Self::rent_fee_for(block_count);
ensure!(
CurrencyOf::<T>::free_balance(&who) >= rent_fee,
CurrencyOf::<T>::balance(&who) >= rent_fee,
Error::<T>::InsufficientBalance
);

Expand All @@ -1885,13 +1919,8 @@ pub mod pallet {
TaskPoolOf::<T>::add(expiration_block, task)
.unwrap_or_else(|e| unreachable!("Scheduling logic invalidated! {:?}", e));

CurrencyOf::<T>::transfer(
&who,
&block_author,
rent_fee,
ExistenceRequirement::AllowDeath,
)
.unwrap_or_else(|e| unreachable!("Failed to transfer rent: {:?}", e));
CurrencyOf::<T>::transfer(&who, &block_author, rent_fee, Preservation::Expendable)
.unwrap_or_else(|e| unreachable!("Failed to transfer rent: {:?}", e));

Self::deposit_event(Event::ProgramChanged {
id: program_id,
Expand Down Expand Up @@ -1955,10 +1984,14 @@ pub mod pallet {
// transferred. That's because destination can fail to be initialized by the time
// this dispatch message is next in the queue.
//
// Note: reservaton is made from the user's account as voucher can only be used
// Note: hold is made from the user's account as voucher can only be used
// to pay for gas or settle transaction fees, but not as source for value transfer.
CurrencyOf::<T>::reserve(&who, value.unique_saturated_into())
.map_err(|_| Error::<T>::InsufficientBalance)?;
CurrencyOf::<T>::hold(
&HoldReason::StorageDepositReserve.into(),
&who,
value.unique_saturated_into(),
)
.map_err(|_| Error::<T>::InsufficientBalance)?;

let gas_limit_reserve = T::GasPrice::gas_price(gas_limit);

Expand Down Expand Up @@ -2035,12 +2068,12 @@ pub mod pallet {
// Checking that program, origin replies to, is not terminated.
ensure!(Self::is_active(destination), Error::<T>::InactiveProgram);

// Reserving funds for sending `value`. The funds are reserved on the
// Hold funds for sending `value`. The funds are hold on the
// account of the reply sender.
//
// Note, that message is not guaranteed to be successfully executed,
// that's why value is not immediately transferred.
CurrencyOf::<T>::reserve(&origin, value)
CurrencyOf::<T>::hold(&HoldReason::StorageDepositReserve.into(), &origin, value)
.map_err(|_| Error::<T>::InsufficientBalance)?;

let reply_id = MessageId::generate_reply(mailboxed.id());
Expand Down
Loading

0 comments on commit 1af05e4

Please sign in to comment.