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 70 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 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
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: 2 additions & 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,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 @@ -538,4 +538,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 @@ -538,4 +538,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()
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 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,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 @@ -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,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()
}
}
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
79 changes: 79 additions & 0 deletions substrate/frame/assets/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,29 @@ fn assert_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::Runti
frame_system::Pallet::<T>::assert_has_event(generic_event.into());
}

// fn generate_merkle_trie<T: Config<I>, I: 'static>(items: u32) -> (DistributionHashOf<T, I>,
Copy link
Contributor

@kianenigma kianenigma Sep 19, 2024

Choose a reason for hiding this comment

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

not needed?

// DistributionProofOf<T, I>) { use codec::Encode;
// let flat_distribution = Vec::<Vec<u8>>::with_capacity(items as usize);
// for i in 0..items {
// let account: T::AccountId = account("target", i, SEED);
// let balance: T::Balance = i.into();

// flat_distribution.push((account, balance).encode());
// }

// let root = binary_merkle_tree::merkle_root::<<T as frame_system::Config>::Hashing, _>(
// flat_distribution.clone(),
// );

// let proof = binary_merkle_tree::merkle_proof::<
// <T as frame_system::Config>::Hashing,
// _,
// _,
// >(flat_distribution.clone(), items);

// return (root, proof)
// }

benchmarks_instance_pallet! {
create {
let asset_id = default_asset_id::<T, I>();
Expand Down Expand Up @@ -564,5 +587,61 @@ 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 = MerklizedDistribution::<T, I>::count();
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), DistributionHashOf::<T, I>::default())
verify {
let count = MerklizedDistribution::<T, I>::count();
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());
}

// 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 = MerklizedDistribution::<T, I>::count();
Assets::<T, I>::mint_distribution(
SystemOrigin::Signed(caller.clone()).into(),
asset_id.clone(),
DistributionHashOf::<T, I>::default(),
)?;
let count = MerklizedDistribution::<T, I>::count();
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 = MerklizedDistribution::<T, I>::count();
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)
}
122 changes: 122 additions & 0 deletions substrate/frame/assets/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,128 @@ 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>,
Copy link
Contributor

Choose a reason for hiding this comment

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

why Option? Is always called with Some() ?

) -> 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 {
asset_id: id.clone(),
merkle_root: merkle_root.clone(),
active: true,
};

let distribution_id: u32 = MerklizedDistribution::<T, I>::count();
MerklizedDistribution::<T, I>::insert(&distribution_id, info);

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>,
) -> DispatchResult {
let proof =
codec::Decode::decode(&mut &merkle_proof[..]).map_err(|_| Error::<T, I>::BadProof)?;

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)
.map_err(|()| Error::<T, I>::BadProof)?;
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this never cleaned? Would we not just keep bloating state then?


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 {
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?

} 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