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

[Assets] Implement pallet-assets-holder #4530

Open
wants to merge 81 commits into
base: master
Choose a base branch
from

Conversation

pandres95
Copy link
Contributor

@pandres95 pandres95 commented May 21, 2024

Closes #4315

@pandres95 pandres95 requested a review from a team as a code owner May 21, 2024 05:43
@pandres95 pandres95 marked this pull request as draft May 21, 2024 05:43
…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.
@pandres95 pandres95 marked this pull request as ready for review July 11, 2024 09:45
@pandres95 pandres95 requested review from athei and a team as code owners July 11, 2024 09:45
@bkchr
Copy link
Member

bkchr commented Jul 24, 2024

@joepetrowski @muharem can you please take a look at this and if we need this?

Copy link
Contributor

@muharem muharem left a 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.

@pandres95
Copy link
Contributor Author

pandres95 commented Jul 25, 2024

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 pallet-assets and instead recommended extending them. That is, precisely the reasoning for having created pallet-assets-freezer.

@muharem
Copy link
Contributor

muharem commented Jul 25, 2024

@pandres95 yes, it should be done as an extension. there is also a died hook, the new trait make sense to me now.
so I would only advise to have a single PR, since HeldBalance along does not bring anything, and only breaking changes.
if introduced and merged separately, it is possible to get the HeldBalance and the holder pallet in different release versions. Two separate audits would be required. also I think we can review it better in a single PR.

@pandres95 pandres95 changed the title [Assets] Define HeldBalance [Assets] Implement pallet-assets-holder Jul 29, 2024
@paritytech-review-bot paritytech-review-bot bot requested a review from a team August 1, 2024 02:28
@pandres95
Copy link
Contributor Author

@muharem the pallet implementation is done and ready to review

@pandres95 pandres95 requested a review from muharem August 1, 2024 02:33
Copy link
Contributor

@muharem muharem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few minor requests

prdoc/pr_4530.prdoc Outdated Show resolved Hide resolved
prdoc/pr_4530.prdoc Outdated Show resolved Hide resolved
substrate/frame/assets/src/mock.rs Outdated Show resolved Hide resolved
substrate/frame/assets-freezer/src/impls.rs Show resolved Hide resolved
substrate/frame/assets/src/functions.rs Outdated Show resolved Hide resolved
substrate/frame/assets/src/functions.rs Show resolved Hide resolved
prdoc/pr_4530.prdoc Show resolved Hide resolved
substrate/frame/assets-holder/src/lib.rs Show resolved Hide resolved
substrate/frame/assets/src/tests.rs Show resolved Hide resolved
@@ -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)?
Copy link
Contributor

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.

Copy link
Contributor Author

@pandres95 pandres95 Nov 5, 2024

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:

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.

Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see #6342

Copy link
Contributor Author

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?

Copy link
Contributor

@gui1117 gui1117 Nov 6, 2024

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

Copy link
Contributor Author

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)?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same storage is tainted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

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,
)?);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same storage is tainted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@pandres95 pandres95 Nov 6, 2024

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.

substrate/frame/assets/src/functions.rs Show resolved Hide resolved
@@ -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)?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

storage is tainted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d2e2d18

substrate/frame/assets/src/types.rs Outdated Show resolved Hide resolved
@gui1117 gui1117 self-requested a review November 4, 2024 06:04
@gui1117
Copy link
Contributor

gui1117 commented Nov 4, 2024

Looks good, some last changes

Copy link
Contributor Author

@pandres95 pandres95 left a 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)

substrate/frame/assets-holder/src/lib.rs Outdated Show resolved Hide resolved
…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.
@pandres95 pandres95 requested a review from ggwpez November 6, 2024 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Define and implement Hold for Assets
9 participants