-
Notifications
You must be signed in to change notification settings - Fork 683
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
[Assets] Implement pallet-assets-holder
#4530
base: master
Are you sure you want to change the base?
Conversation
…nce based on whether there's held assets or not
c627c51
to
89ea43a
Compare
…th the tokens' _balance components_ model. On the most recent documentation about tokens (both _fungible_ and _fungibles_ (sets)), the model for calculating the different balance components is explained. The prior implementation of `pallet-assets` featured a weird definition of how to handle them that was not in line with the expected (see <https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/tokens/fungible/index.html#holds-and-freezes>) definition of tokens. This commit changes this implementation for methods `reducible_balance` and `can_decrease` to introduce the calculation of `spendable` balance, and the consequences derived of decreasing a balance that may include both `frozen` and `held` balances.
… working together
@joepetrowski @muharem can you please take a look at this and if we need this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pandres95 I do not really understand the plan. I see only first step in the issue. also are you sure we need this new trait? can we do the same with fungibles traits? I see that we have a similar FrozenBalance
trait, but it probably was introduced before the fungibles was. can we bring the hold implementation with a single PR? it would make it more clear and we get it faster. this PR wont bring anything useful, only breaking changes.
Sure! Can complete the second step in the same PR. The reason behind this trait is that in some prior meeting Gav advised against exhaustively modifying some core pallets like |
@pandres95 yes, it should be done as an extension. there is also a |
HeldBalance
pallet-assets-holder
@muharem the pallet implementation is done and ready to review |
fd3adef
to
8d46fa3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
few minor requests
…alance on hold, but frozen value is zero, as the account would need to exist
… to reflect the changes in the balance model, so a first reader can understand the differences between _free_ balance, _reducible_ balance and balance _on hold_.
@@ -361,7 +386,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { | |||
T::Currency::unreserve(&who, deposit); | |||
} | |||
|
|||
if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) { | |||
if let Remove = Self::dead_account(id.clone(), &who, &mut details, &account.reason, false)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This operation happens after the unreserve. So in case we return an error, the storage is tainted.
do_refund
is used in trait implementation, so if a user of the trait doesn't revert when receiving an error then we have an issue.
We should do this operation in a transaction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Been testing this.
My conclusion is the following: even though the deposit is unreserved earlier in the code, if an error is returned, the storage is not tainted.
I understand it's easy to think that by calling T::Currency::unreserved
, which doesn't return a Result
type would cause that change to be directly written in the storage. It's not necessarily like that.
Consider how FRAME is built. Storage is not directly touched, but handled in a transactional layered way. So, storage is written if and only if the call execution is correct.
This is also why we assert_noop
instead of doing assert_err
. An example of that, here:
polkadot-sdk/substrate/frame/assets/src/tests.rs
Line 1405 in 6bebe7e
assert_noop!( |
I added a small test to prove the storage is not tainted (@muharem, we should probably keep this test, wdyt?) on 3e797b1
.
But overall, the behaviour is the same: storage is never modified if an extrinsic fails, regardless of how it is modified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do_refund
is not strictly a call implementation. It is also used in traits in the implementation of fungibles::Refund
.
The trait fungibles::Refund
doesn't specifies that the storage must be rollbacked in case of error.
Somebody can write:
// Ignore because we don't need to enforce the success.
let _ignore = fungibles::Refund::refund(asset_id, some_account);
And now storage is inconsistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see #6342
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand you're proposing these T::Currency::unreserve
calls should be wrapped inside a transactional
macro? Or does the entire call need to be wrapped?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess wrapping from the unreserve
call until dead_account
or the end of the block into one transactional is more efficient.
Because the early checks can return without starting the transactional.
EDIT: but both implementation would satisfy me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in d2e2d18
@@ -397,7 +424,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { | |||
|
|||
T::Currency::unreserve(&depositor, deposit); | |||
|
|||
if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) { | |||
if let Remove = Self::dead_account(id.clone(), &who, &mut details, &account.reason, false)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same storage is tainted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #4530 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in d2e2d18
details, | ||
&source_account.reason, | ||
false, | ||
)?); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same storage is tainted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #4530 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in d2e2d18
.
Note: This function didn't change as it didn't have a reference to T::Currency::unreserve
prior to dead_account
.
@@ -782,7 +828,9 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { | |||
} else if let Some(deposit) = v.reason.take_deposit() { | |||
T::Currency::unreserve(&who, deposit); | |||
} | |||
if let Remove = Self::dead_account(&who, &mut details, &v.reason, false) { | |||
if let Remove = | |||
Self::dead_account(id.clone(), &who, &mut details, &v.reason, false)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
storage is tainted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #4530 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in d2e2d18
Looks good, some last changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change this (thanks @ggwpez)
…wpez for the suggestion)
…rency::unreserve` that are followed by a potential failure of `dead_account`. This would prevent cases where `refund` is called externally by another pallet, and not as a part of a direct extrinsic call within the pallet.
…illustrate the changes.
Closes #4315