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

Enable Merklized Distributions in Assets Pallet #5400

Open
wants to merge 83 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
d50d5e4
initial ideas
shawntabrizi Mar 28, 2024
7f4422e
Update proving_trie.rs
shawntabrizi Mar 28, 2024
bdc0c84
create trait
shawntabrizi Mar 28, 2024
da385ab
use trait
shawntabrizi Mar 28, 2024
6ab4bfb
clean up trait and basic trie further
shawntabrizi Mar 28, 2024
1cfb29f
use trait in session historical
shawntabrizi Mar 28, 2024
3080c7b
fix api, add basic end to end test
shawntabrizi Mar 28, 2024
39b0fca
fix test
shawntabrizi Mar 28, 2024
599f576
Revert "use trait in session historical"
shawntabrizi Mar 28, 2024
a1c8886
Merge branch 'master' into shawntabrizi-proving-trie
shawntabrizi Mar 29, 2024
6bde2a3
Merge branch 'master' into shawntabrizi-proving-trie
Ank4n Apr 4, 2024
ead7951
Update substrate/primitives/runtime/src/proving_trie.rs
shawntabrizi Apr 4, 2024
0cac048
Merge branch 'master' into shawntabrizi-proving-trie
shawntabrizi Aug 15, 2024
acfe734
fix some feedback
shawntabrizi Aug 15, 2024
45f4287
update name
shawntabrizi Aug 15, 2024
efbe1c9
docs and multi value proof
shawntabrizi Aug 15, 2024
cfa62ce
improve test
shawntabrizi Aug 15, 2024
885fd94
add multi-value query test
shawntabrizi Aug 15, 2024
18a5c2e
Merge branch 'master' into shawntabrizi-proving-trie
shawntabrizi Aug 15, 2024
5e3e518
Create pr_3881.prdoc
shawntabrizi Aug 15, 2024
f35cacc
Merge branch 'shawntabrizi-proving-trie' of https://github.com/shawnt…
shawntabrizi Aug 15, 2024
e6c3759
use v1
shawntabrizi Aug 17, 2024
89679c3
Merge branch 'master' into shawntabrizi-proving-trie
shawntabrizi Aug 17, 2024
b40c96a
initial idea
shawntabrizi Aug 19, 2024
37052ee
some bs code to make things compile
shawntabrizi Aug 19, 2024
08f2b35
more stuff
shawntabrizi Aug 19, 2024
118a51c
complete logic
shawntabrizi Aug 19, 2024
44bc5bb
add verification check
shawntabrizi Aug 19, 2024
6e853ba
Merge branch 'master' into shawntabrizi-proving-trie
shawntabrizi Sep 2, 2024
89abcd0
initial idea
shawntabrizi Aug 19, 2024
8ad3381
some bs code to make things compile
shawntabrizi Aug 19, 2024
ae6fab7
more stuff
shawntabrizi Aug 19, 2024
115f89b
complete logic
shawntabrizi Aug 19, 2024
e5f0056
add verification check
shawntabrizi Aug 19, 2024
766790a
Merge branch 'shawntabrizi-assets-distribution' of https://github.com…
shawntabrizi Sep 2, 2024
b80589c
large refactor to proving trie
shawntabrizi Sep 3, 2024
83c66e7
fix tests
shawntabrizi Sep 3, 2024
5a4ff43
use basic proving trie
shawntabrizi Sep 3, 2024
394cc6f
Merge branch 'master' into shawntabrizi-assets-distribution
shawntabrizi Sep 3, 2024
84574e1
undo changes to binary merkle tree
shawntabrizi Sep 3, 2024
444b1f7
remove comment code
shawntabrizi Sep 3, 2024
f47b12e
make api more runtime friendly
shawntabrizi Sep 3, 2024
0305bc7
Merge remote-tracking branch 'upstream/master' into shawntabrizi-asse…
shawntabrizi Sep 4, 2024
ee90711
add basic test, and fix missing check for tracking distribution
shawntabrizi Sep 6, 2024
61162b6
introduce distribution info and active
shawntabrizi Sep 6, 2024
ae2ff18
introduce end_distribution
shawntabrizi Sep 6, 2024
fb63207
add clean distribution
shawntabrizi Sep 7, 2024
e713698
Merge branch 'master' into shawntabrizi-assets-distribution
shawntabrizi Sep 7, 2024
988b201
weight stuff
shawntabrizi Sep 7, 2024
233d339
temp fix for ui
shawntabrizi Sep 7, 2024
48fe9c6
Merge branch 'master' into pr/5400
shawntabrizi Sep 16, 2024
122f9cc
make work with binary tree
shawntabrizi Sep 17, 2024
4d26deb
clean dead code
shawntabrizi Sep 17, 2024
8e6fcd5
Merge branch 'master' into shawntabrizi-assets-distribution
shawntabrizi Sep 17, 2024
b17a7c4
Merge branch 'master' into shawntabrizi-assets-distribution
shawntabrizi Sep 17, 2024
fcfda22
two benchmarks
shawntabrizi Sep 17, 2024
6bf7b91
Merge branch 'shawntabrizi-assets-distribution' of https://github.com…
shawntabrizi Sep 17, 2024
42cdc26
more generic, remove keys limit
shawntabrizi Sep 17, 2024
12ed0fb
test fixes
shawntabrizi Sep 17, 2024
9c8ab8a
update API name for consistent
shawntabrizi Sep 17, 2024
b4f480c
destroy distribution benchmark
shawntabrizi Sep 17, 2024
40bd086
need to abstract this
shawntabrizi Sep 17, 2024
7e22961
initial stuff
shawntabrizi Sep 17, 2024
1b13d9a
more
shawntabrizi Sep 17, 2024
1ac0545
fix up binary tree
shawntabrizi Sep 17, 2024
7aaabe3
refactor
shawntabrizi Sep 18, 2024
9013156
fixes
shawntabrizi Sep 18, 2024
c98b882
update api for a single opaque blob
shawntabrizi Sep 18, 2024
42259eb
Merge branch 'master' into shawntabrizi-assets-distribution
shawntabrizi Sep 18, 2024
2f1f18a
Merge branch 'master' into pr/5400
shawntabrizi Sep 23, 2024
72dd0a1
Merge branch 'master' into pr/5400
shawntabrizi Sep 30, 2024
860f346
fix build
shawntabrizi Sep 30, 2024
1c306f6
Merge branch 'master' into pr/5400
shawntabrizi Sep 30, 2024
3693527
Merge branch 'shawntabrizi-assets-distribution' of https://github.com…
shawntabrizi Sep 30, 2024
528e71f
weird rust trait stuff
shawntabrizi Oct 2, 2024
b1e5387
Merge branch 'master' into pr/5400
shawntabrizi Oct 2, 2024
9f108c3
fix no default
shawntabrizi Oct 2, 2024
fe2e08a
allow default as no trie
shawntabrizi Oct 2, 2024
2adc76f
use separate count
shawntabrizi Oct 2, 2024
22f26d9
remove map at the end of delete
shawntabrizi Oct 2, 2024
c0a4032
Update substrate/frame/assets/src/lib.rs
shawntabrizi Oct 2, 2024
15be9c8
remove dead code
shawntabrizi Oct 2, 2024
8b5701c
Merge branch 'master' into pr/5400
shawntabrizi Oct 7, 2024
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -541,4 +541,24 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}

fn mint_distribution() -> Weight {
Weight::default()
}

fn claim_distribution() -> Weight {
Weight::default()
}

fn trie_hash(_h: u32) -> Weight {
Weight::default()
}

fn end_distribution() -> Weight {
Weight::default()
}

fn destroy_distribution(_n: u32) -> Weight {
Weight::default()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -538,4 +538,24 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}

fn mint_distribution() -> Weight {
Weight::default()
}

fn claim_distribution() -> Weight {
Weight::default()
}

fn trie_hash(_h: u32) -> Weight {
Weight::default()
}

fn end_distribution() -> Weight {
Weight::default()
}

fn destroy_distribution(_n: u32) -> Weight {
Weight::default()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -538,4 +538,24 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}

fn mint_distribution() -> Weight {
Weight::default()
Copy link
Contributor

Choose a reason for hiding this comment

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

TODO prior to merge

}

fn trie_hash(_h: u32) -> Weight {
Weight::default()
}

fn claim_distribution() -> Weight {
Weight::default()
}

fn end_distribution() -> Weight {
Weight::default()
}

fn destroy_distribution(_n: u32) -> Weight {
Weight::default()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -547,4 +547,24 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}

fn mint_distribution() -> Weight {
Weight::default()
}

fn claim_distribution() -> Weight {
Weight::default()
}

fn trie_hash(_h: u32) -> Weight {
Weight::default()
}

fn end_distribution() -> Weight {
Weight::default()
}

fn destroy_distribution(_n: u32) -> Weight {
Weight::default()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -545,4 +545,20 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}

fn mint_distribution() -> Weight {
Weight::default()
}

fn claim_distribution() -> Weight {
Weight::default()
}

fn end_distribution() -> Weight {
Weight::default()
}

fn destroy_distribution(_n: u32) -> Weight {
Weight::default()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -539,4 +539,24 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}

fn mint_distribution() -> Weight {
Weight::default()
}

fn claim_distribution() -> Weight {
Weight::default()
}

fn trie_hash(_h: u32) -> Weight {
Weight::default()
}

fn end_distribution() -> Weight {
Weight::default()
}

fn destroy_distribution(_n: u32) -> Weight {
Weight::default()
}
}
1 change: 1 addition & 0 deletions substrate/frame/assets/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ sp-core = { workspace = true }
[dev-dependencies]
sp-io = { workspace = true, default-features = true }
pallet-balances = { workspace = true, default-features = true }
binary-merkle-tree = { workspace = true }

[features]
default = ["std"]
Expand Down
73 changes: 72 additions & 1 deletion substrate/frame/assets/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use frame_benchmarking::v1::{
};
use frame_support::traits::{EnsureOrigin, Get, UnfilteredDispatchable};
use frame_system::RawOrigin as SystemOrigin;
use sp_runtime::traits::Bounded;
use sp_runtime::traits::{Bounded, Hash};

use crate::Pallet as Assets;

Expand Down Expand Up @@ -564,5 +564,76 @@ benchmarks_instance_pallet! {
assert_last_event::<T, I>(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into());
}

// This function is O(1), so placing any hash as a merkle root should work.
mint_distribution {
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
let before_count = CountForMerklizedDistribution::<T, I>::get();
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), DistributionHashOf::<T, I>::default())
verify {
let count = CountForMerklizedDistribution::<T, I>::get();
assert_eq!(count, before_count + 1);
assert_last_event::<T, I>(Event::DistributionIssued { distribution_id: before_count, asset_id: asset_id.into(), merkle_root: DistributionHashOf::<T, I>::default() }.into());
}

// Calculate the cost of `h` hashes.
trie_hash {
let h in 0 .. 1_000;
let mut hash = T::Hash::default();
}: {
for _ in 0..h {
// Our hashes will be composed of two sub-hashes.
hash = T::Hashing::hash_of(&(hash, hash))
}
} verify {
if h > 0 {
assert!(hash != T::Hash::default());
}
}

// This function is O(1), so ending any distribution should work.
end_distribution {
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
let before_count = CountForMerklizedDistribution::<T, I>::get();
Assets::<T, I>::mint_distribution(
SystemOrigin::Signed(caller.clone()).into(),
asset_id.clone(),
DistributionHashOf::<T, I>::default(),
)?;
let count = CountForMerklizedDistribution::<T, I>::get();
assert_eq!(count, before_count + 1);
assert_last_event::<T, I>(Event::DistributionIssued { distribution_id: before_count, asset_id: asset_id.into(), merkle_root: DistributionHashOf::<T, I>::default() }.into());
}: _(SystemOrigin::Signed(caller.clone()), before_count)
verify {
assert_last_event::<T, I>(Event::DistributionEnded { distribution_id: before_count }.into());
}

// This function is O(N), where N is the number of items destroyed in one extrinsic call.
// This benchmark cheats a little to avoid having to do hundreds or thousands of merkle proofs.
// Instead we call low level storage to populate the `MerklizedDistributionTracker` for our needs.
// If the logic of the `do_destroy_distribution` function changes, then this also needs to be updated.
destroy_distribution {
let c in 0 .. T::RemoveItemsLimit::get();
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
let before_count = CountForMerklizedDistribution::<T, I>::get();
Assets::<T, I>::mint_distribution(
SystemOrigin::Signed(caller.clone()).into(),
asset_id.clone(),
DistributionHashOf::<T, I>::default(),
)?;
Assets::<T, I>::end_distribution(
SystemOrigin::Signed(caller.clone()).into(),
before_count,
)?;
for i in 0..c {
let account_id: T::AccountId = account("target", i, SEED);
MerklizedDistributionTracker::<T, I>::insert(before_count, account_id, ());
}
assert_eq!(MerklizedDistributionTracker::<T, I>::iter().count() as u32, c);
}: _(SystemOrigin::Signed(caller.clone()), before_count)
verify {
assert_last_event::<T, I>(Event::DistributionCleaned { distribution_id: before_count }.into());
assert_eq!(MerklizedDistributionTracker::<T, I>::iter().count() as u32, 0);
}

impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test)
}
131 changes: 131 additions & 0 deletions substrate/frame/assets/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,137 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Ok(())
}

/// Creates a distribution in storage for asset `id`, which can be claimed via
/// `do_claim_distribution`.
pub(super) fn do_mint_distribution(
id: T::AssetId,
merkle_root: DistributionHashOf<T, I>,
maybe_check_issuer: Option<T::AccountId>,
Ank4n marked this conversation as resolved.
Show resolved Hide resolved
) -> DispatchResult {
let details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);

if let Some(check_issuer) = maybe_check_issuer {
ensure!(check_issuer == details.issuer, Error::<T, I>::NoPermission);
}

let info = DistributionInfo {
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps you have already considered this, but I'm still a little concerned that an account can create a bunch of distributions without paying any storage cost.

We could limit this by either

  • Requiring a deposit when creating a distribution.
  • Or, limit it to one active distribution per asset ID.

wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

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

If we don't expect multiple active distribution for an asset, probably that is the most straightforward. We can use AssetId as the key for MerklizedDistribution.

Copy link
Member Author

Choose a reason for hiding this comment

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

i am okay with you adding storage deposit to this PR. i would like to hand it off to someone else to own and take to finish line

asset_id: id.clone(),
merkle_root: merkle_root.clone(),
active: true,
};

let distribution_id: u32 = CountForMerklizedDistribution::<T, I>::get();
MerklizedDistribution::<T, I>::insert(&distribution_id, info);
CountForMerklizedDistribution::<T, I>::put(
distribution_id.checked_add(1).ok_or(ArithmeticError::Overflow)?,
);

Self::deposit_event(Event::DistributionIssued {
distribution_id,
asset_id: id,
merkle_root,
});

Ok(())
}

/// A wrapper around `do_mint`, allowing a `merkle_proof` to control the amount minted and to
/// whom.
pub(super) fn do_claim_distribution(
distribution_id: DistributionCounter,
merkle_proof: Vec<u8>,
hashes: u32,
) -> DispatchResult {
let proof =
codec::Decode::decode(&mut &merkle_proof[..]).map_err(|_| Error::<T, I>::BadProof)?;

let expected_hashes = T::VerifyExistenceProof::proof_to_hashes(&proof)
.map_err(|_| Error::<T, I>::BadProof)?;

ensure!(hashes >= expected_hashes, Error::<T, I>::TooManyHashes);

let DistributionInfo { asset_id, merkle_root, active } =
MerklizedDistribution::<T, I>::get(distribution_id).ok_or(Error::<T, I>::Unknown)?;

ensure!(active, Error::<T, I>::DistributionEnded);

let leaf = T::VerifyExistenceProof::verify_proof(proof, &merkle_root)?;
let (beneficiary, amount) =
codec::Decode::decode(&mut &leaf[..]).map_err(|_| Error::<T, I>::CannotDecodeLeaf)?;

ensure!(
!MerklizedDistributionTracker::<T, I>::contains_key(distribution_id, &beneficiary),
Error::<T, I>::AlreadyClaimed
);

Self::do_mint(asset_id, &beneficiary, amount, None)?;
MerklizedDistributionTracker::<T, I>::insert(&distribution_id, &beneficiary, ());

Ok(())
}

/// Ends the asset distribution of `distribution_id`.
pub(super) fn do_end_distribution(
distribution_id: DistributionCounter,
maybe_check_issuer: Option<T::AccountId>,
) -> DispatchResult {
let mut info =
MerklizedDistribution::<T, I>::get(&distribution_id).ok_or(Error::<T, I>::Unknown)?;
let details = Asset::<T, I>::get(&info.asset_id).ok_or(Error::<T, I>::Unknown)?;

if let Some(check_issuer) = maybe_check_issuer {
ensure!(check_issuer == details.issuer, Error::<T, I>::NoPermission);
}

info.active = false;

MerklizedDistribution::<T, I>::insert(&distribution_id, info);
Ank4n marked this conversation as resolved.
Show resolved Hide resolved

Self::deposit_event(Event::DistributionEnded { distribution_id });

Ok(())
}

/// Cleans up the distribution tracker of `distribution_id`.
/// Iterates and cleans up data in the `MerklizedDistributionTracker` map `RemoveItemsLimit` at
/// a time. This function may need to be called multiple times to complete successfully.
pub(super) fn do_destroy_distribution(
distribution_id: DistributionCounter,
) -> DispatchResultWithPostInfo {
let info =
MerklizedDistribution::<T, I>::get(&distribution_id).ok_or(Error::<T, I>::Unknown)?;

ensure!(!info.active, Error::<T, I>::DistributionActive);

let mut refund_count = 0u32;
let distribution_iterator =
MerklizedDistributionTracker::<T, I>::iter_key_prefix(&distribution_id);

let mut all_refunded = true;
for who in distribution_iterator {
if refund_count >= T::RemoveItemsLimit::get() {
// Not everyone was able to be refunded this time around.
all_refunded = false;
break
}

MerklizedDistributionTracker::<T, I>::remove(&distribution_id, &who);
refund_count += 1;
}

if all_refunded {
MerklizedDistribution::<T, I>::remove(&distribution_id);
Self::deposit_event(Event::<T, I>::DistributionCleaned { distribution_id });
// Refund weight only the amount we actually used.
Ok(Some(T::WeightInfo::destroy_distribution(refund_count)).into())
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the incentive to clean up state? Shouldn't we refund all fees?

Copy link
Member Author

Choose a reason for hiding this comment

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

anyone can clean up state... but yeah we could add a deposit here for doing a distribution

} else {
Self::deposit_event(Event::<T, I>::DistributionPartiallyCleaned { distribution_id });
// No weight to refund since we did not finish the loop.
Ok(().into())
}
}

/// Increases the asset `id` balance of `beneficiary` by `amount`.
///
/// LOW-LEVEL: Does not alter the supply of asset or emit an event. Use `do_mint` if you need
Expand Down
Loading
Loading