Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FRAME] Short-circuit fungible self transfer #2118

Merged
merged 10 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bridges/primitives/relayers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ where
impl<T, Relayer> PaymentProcedure<Relayer, T::Balance> for PayRewardFromAccount<T, Relayer>
where
T: frame_support::traits::fungible::Mutate<Relayer>,
Relayer: Decode + Encode,
Relayer: Decode + Encode + Eq,
{
type Error = sp_runtime::DispatchError;

Expand Down
6 changes: 3 additions & 3 deletions cumulus/primitives/utility/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ struct AssetTraderRefunder {
/// Important: Errors if the Trader is being called twice by 2 BuyExecution instructions
/// Alternatively we could just return payment in the aforementioned case
pub struct TakeFirstAssetTrader<
AccountId,
AccountId: Eq,
FeeCharger: ChargeWeightInFungibles<AccountId, ConcreteAssets>,
Matcher: MatchesFungibles<ConcreteAssets::AssetId, ConcreteAssets::Balance>,
ConcreteAssets: fungibles::Mutate<AccountId> + fungibles::Balanced<AccountId>,
Expand All @@ -112,7 +112,7 @@ pub struct TakeFirstAssetTrader<
PhantomData<(AccountId, FeeCharger, Matcher, ConcreteAssets, HandleRefund)>,
);
impl<
AccountId,
AccountId: Eq,
FeeCharger: ChargeWeightInFungibles<AccountId, ConcreteAssets>,
Matcher: MatchesFungibles<ConcreteAssets::AssetId, ConcreteAssets::Balance>,
ConcreteAssets: fungibles::Mutate<AccountId> + fungibles::Balanced<AccountId>,
Expand Down Expand Up @@ -241,7 +241,7 @@ impl<
}

impl<
AccountId,
AccountId: Eq,
FeeCharger: ChargeWeightInFungibles<AccountId, ConcreteAssets>,
Matcher: MatchesFungibles<ConcreteAssets::AssetId, ConcreteAssets::Balance>,
ConcreteAssets: fungibles::Mutate<AccountId> + fungibles::Balanced<AccountId>,
Expand Down
8 changes: 4 additions & 4 deletions polkadot/xcm/xcm-builder/src/fungibles_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
AccountId: Eq + Clone, /* can't get away without it since Currency is generic over it. */
> TransactAsset for FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>
{
fn internal_transfer_asset(
Expand Down Expand Up @@ -150,7 +150,7 @@ impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
AccountId: Eq + Clone, /* can't get away without it since Currency is generic over it. */
CheckAsset: AssetChecking<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
>
Expand Down Expand Up @@ -185,7 +185,7 @@ impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
AccountId: Eq + Clone, /* can't get away without it since Currency is generic over it. */
CheckAsset: AssetChecking<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
> TransactAsset
Expand Down Expand Up @@ -325,7 +325,7 @@ impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
AccountId: Eq + Clone, /* can't get away without it since Currency is generic over it. */
CheckAsset: AssetChecking<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
> TransactAsset
Expand Down
54 changes: 47 additions & 7 deletions substrate/frame/balances/src/tests/currency_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@
//! Tests regarding the functionality of the `Currency` trait set implementations.

use super::*;
use crate::NegativeImbalance;
use frame_support::traits::{
BalanceStatus::{Free, Reserved},
Currency,
ExistenceRequirement::{self, AllowDeath, KeepAlive},
Hooks, LockIdentifier, LockableCurrency, NamedReservableCurrency, ReservableCurrency,
WithdrawReasons,
use crate::{Event, NegativeImbalance};
use frame_support::{
traits::{
BalanceStatus::{Free, Reserved},
Currency,
ExistenceRequirement::{self, AllowDeath, KeepAlive},
Hooks, LockIdentifier, LockableCurrency, NamedReservableCurrency, ReservableCurrency,
WithdrawReasons,
},
StorageNoopGuard,
};
use frame_system::Event as SysEvent;

const ID_1: LockIdentifier = *b"1 ";
const ID_2: LockIdentifier = *b"2 ";
Expand Down Expand Up @@ -1363,3 +1367,39 @@ fn freezing_and_locking_should_work() {
assert_eq!(System::consumers(&1), 0);
});
}

#[test]
fn self_transfer_noop() {
ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| {
assert_eq!(Balances::total_issuance(), 0);
let _ = Balances::deposit_creating(&1, 100);

// The account is set up properly:
assert_eq!(
events(),
[
Event::Deposit { who: 1, amount: 100 }.into(),
SysEvent::NewAccount { account: 1 }.into(),
Event::Endowed { account: 1, free_balance: 100 }.into(),
]
);
assert_eq!(Balances::free_balance(1), 100);
assert_eq!(Balances::total_issuance(), 100);

// Transfers to self are No-OPs:
let _g = StorageNoopGuard::new();
for i in 0..200 {
let r = Balances::transfer_allow_death(Some(1).into(), 1, i);

if i <= 100 {
assert_ok!(r);
} else {
assert!(r.is_err());
}

assert!(events().is_empty());
assert_eq!(Balances::free_balance(1), 100, "Balance unchanged by self transfer");
assert_eq!(Balances::total_issuance(), 100, "TI unchanged by self transfers");
}
});
}
2 changes: 1 addition & 1 deletion substrate/frame/broker/src/test_fungibles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ where

impl<
Instance: Get<u32>,
AccountId: Encode,
AccountId: Encode + Eq,
AssetId: tokens::AssetId + Copy,
MinimumBalance: TypedGet,
HoldReason,
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/nis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ impl<T> fungible::Unbalanced<T> for NoCounterpart<T> {
}
fn set_total_issuance(_: Self::Balance) {}
}
impl<T> FunMutate<T> for NoCounterpart<T> {}
impl<T: Eq> FunMutate<T> for NoCounterpart<T> {}
impl<T> Convert<Perquintill, u32> for NoCounterpart<T> {
fn convert(_: Perquintill) -> u32 {
0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ impl<
impl<
F: fungibles::Mutate<AccountId>,
A: Get<<F as fungibles::Inspect<AccountId>>::AssetId>,
AccountId,
AccountId: Eq,
> Mutate<AccountId> for ItemOf<F, A, AccountId>
{
fn mint_into(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
Expand Down
12 changes: 11 additions & 1 deletion substrate/frame/support/src/traits/tokens/fungible/regular.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,10 @@ pub trait Unbalanced<AccountId>: Inspect<AccountId> {
}

/// Trait for providing a basic fungible asset.
pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId>
where
AccountId: Eq,
{
/// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't
/// possible then an `Err` is returned and nothing is changed.
fn mint_into(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
Expand Down Expand Up @@ -303,6 +306,9 @@ pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
}

/// Transfer funds from one account into another.
///
/// A transfer where the source and destination account are identical is treated as No-OP after
/// checking the preconditions.
fn transfer(
source: &AccountId,
dest: &AccountId,
Expand All @@ -311,6 +317,10 @@ pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
) -> Result<Self::Balance, DispatchError> {
let _extra = Self::can_withdraw(source, amount).into_result(preservation != Expendable)?;
Self::can_deposit(dest, amount, Extant).into_result()?;
if source == dest {
return Ok(amount)
}

Self::decrease_balance(source, amount, BestEffort, preservation, Polite)?;
// This should never fail as we checked `can_deposit` earlier. But we do a best-effort
// anyway.
Expand Down
12 changes: 11 additions & 1 deletion substrate/frame/support/src/traits/tokens/fungibles/regular.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,10 @@ pub trait Unbalanced<AccountId>: Inspect<AccountId> {
}

/// Trait for providing a basic fungible asset.
pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId>
where
AccountId: Eq,
{
/// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't
/// possible then an `Err` is returned and nothing is changed.
fn mint_into(
Expand Down Expand Up @@ -353,6 +356,9 @@ pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
}

/// Transfer funds from one account into another.
///
/// A transfer where the source and destination account are identical is treated as No-OP after
/// checking the preconditions.
fn transfer(
asset: Self::AssetId,
source: &AccountId,
Expand All @@ -363,6 +369,10 @@ pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
let _extra = Self::can_withdraw(asset.clone(), source, amount)
.into_result(preservation != Expendable)?;
Self::can_deposit(asset.clone(), dest, amount, Extant).into_result()?;
if source == dest {
ggwpez marked this conversation as resolved.
Show resolved Hide resolved
return Ok(amount)
}

Self::decrease_balance(asset.clone(), source, amount, BestEffort, preservation, Polite)?;
// This should never fail as we checked `can_deposit` earlier. But we do a best-effort
// anyway.
Expand Down
12 changes: 9 additions & 3 deletions substrate/frame/support/src/traits/tokens/pay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,13 @@ pub enum PaymentStatus {
}

/// Simple implementation of `Pay` which makes a payment from a "pot" - i.e. a single account.
pub struct PayFromAccount<F, A>(sp_std::marker::PhantomData<(F, A)>);
impl<A: TypedGet, F: fungible::Mutate<A::Type>> Pay for PayFromAccount<F, A> {
pub struct PayFromAccount<F, A>(core::marker::PhantomData<(F, A)>);
impl<A, F> Pay for PayFromAccount<F, A>
where
A: TypedGet,
F: fungible::Mutate<A::Type>,
A::Type: Eq,
{
type Balance = F::Balance;
type Beneficiary = A::Type;
type AssetKind = ();
Expand All @@ -110,11 +115,12 @@ impl<A: TypedGet, F: fungible::Mutate<A::Type>> Pay for PayFromAccount<F, A> {

/// Simple implementation of `Pay` for assets which makes a payment from a "pot" - i.e. a single
/// account.
pub struct PayAssetFromAccount<F, A>(sp_std::marker::PhantomData<(F, A)>);
pub struct PayAssetFromAccount<F, A>(core::marker::PhantomData<(F, A)>);
impl<A, F> frame_support::traits::tokens::Pay for PayAssetFromAccount<F, A>
where
A: TypedGet,
F: fungibles::Mutate<A::Type> + fungibles::Create<A::Type>,
A::Type: Eq,
{
type Balance = F::Balance;
type Beneficiary = A::Type;
Expand Down
Loading