Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5fab071
Implement initial structure of merkle-airdrop
vrom911 Apr 3, 2025
8b64f64
Improve structure, add all template modules
vrom911 Apr 3, 2025
e25ae63
Implement create_airdrop and fix tests and mock
vrom911 Apr 3, 2025
f4658cc
Implement verify merkle proof, add tests
vrom911 Apr 4, 2025
d0821eb
Use binary_merkle_tree instead of custom function
vrom911 Apr 4, 2025
4218151
Implement fund function with tests
vrom911 Apr 4, 2025
37cbf6e
implement claim function
vrom911 Apr 4, 2025
490b98f
Clean up
vrom911 Apr 4, 2025
302d9cd
Use poseidon hasher in verify fn
vrom911 Apr 4, 2025
2cbe7e9
Try to create merkle root for testing
vrom911 Apr 5, 2025
48a7587
Add doc for testing
vrom911 Apr 7, 2025
70cf90d
Remove rust-toolchain.toml from git tracking
vrom911 Apr 8, 2025
2a39a34
Remove js script
vrom911 Apr 8, 2025
b0901cf
Make claim recipient be anyone
vrom911 Apr 8, 2025
d89ae82
Revert the signer-receiver change, add test fns
vrom911 Apr 9, 2025
c007397
changed hash fn to blake2
Apr 26, 2025
b38475b
Apply fmt
vrom911 May 12, 2025
1d8e2f0
Fix tests
vrom911 May 12, 2025
1f83a32
Add validate_unsigned
vrom911 May 13, 2025
41b2b80
Fix link to merkle-airdrop-cli
vrom911 May 13, 2025
bce58a4
Address review comments
vrom911 May 13, 2025
05b5d7c
Remove MaxAirdrops
vrom911 May 13, 2025
b4da63d
Add UnsignedClaimPriority in the config
vrom911 May 13, 2025
5932102
Add delete airdrop functionality
vrom911 May 13, 2025
7c60b2b
Replace deprecated Currency
vrom911 May 13, 2025
63543dc
Only allow airdrop creators to delete it
vrom911 May 14, 2025
7051471
Allow creators to claim remain balance when delete
vrom911 May 14, 2025
37bc9c6
Clean up docs
vrom911 May 14, 2025
b40d561
Remove unsignedValidation, add tests
vrom911 May 15, 2025
862d275
Remove unnecessary error
vrom911 May 15, 2025
baf9fec
Generate weights from the nenchmarking
vrom911 May 16, 2025
7c8e8d4
Use the existing weights template
vrom911 May 16, 2025
988b2f7
Use unsigned acc for claims
vrom911 May 16, 2025
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
20 changes: 20 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ members = [
"pallets/faucet",
"pallets/qpow",
"pallets/wormhole",
"pallets/merkle-airdrop",
"primitives/faucet",
"primitives/consensus/qpow",
"primitives/crypto/wormhole",
Expand Down Expand Up @@ -112,6 +113,7 @@ sp-storage = { version = "22.0.0", default-features = false }
sp-transaction-pool = { version = "35.0.0", default-features = false }
sp-version = { version = "38.0.0", default-features = false }
substrate-wasm-builder = { version = "25.0.0", default-features = false }
pallet-merkle-airdrop = { version = "0.1.0", path = "./pallets/merkle-airdrop", default-features = false }

log = { version = "0.4.22", default-features = false }
hex = {version = "0.4.3", default-features = false }
Expand Down
59 changes: 59 additions & 0 deletions pallets/merkle-airdrop/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[package]
name = "pallet-merkle-airdrop"
description = "A pallet for distributing tokens via Merkle proofs"
version = "0.1.0"
license = "MIT-0"
authors.workspace = true
homepage.workspace = true
repository.workspace = true
edition.workspace = true
publish = false

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { workspace = true, default-features = false, features = ["derive"] }
scale-info = { workspace = true, default-features = false, features = ["derive"] }
frame-benchmarking = { optional = true, workspace = true }
frame-support.workspace = true
frame-system.workspace = true
pallet-balances.workspace = true
sp-runtime.workspace = true
sp-core.workspace = true
sp-io.workspace = true
log = { version = "0.4.22", default-features = false }
sha2 = { version = "0.10", default-features = false }
binary-merkle-tree = { version = "16.0.0", default-features = false }
poseidon-resonance = { workspace = true, default-features = false }

[dev-dependencies]
sp-core = { default-features = true, workspace = true }
sp-io = { default-features = true, workspace = true }
sp-runtime = { default-features = true, workspace = true }

[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
"sp-runtime/std",
"sp-core/std",
"sp-io/std",
"pallet-balances/std",
"sha2/std",
"binary-merkle-tree/std",
"poseidon-resonance/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
]
14 changes: 14 additions & 0 deletions pallets/merkle-airdrop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Merkle Airdrop Pallet

A Substrate pallet for distributing tokens via Merkle proofs.

## Testing & Usage

For testing and interacting with this pallet, please refer to the CLI tool and example in the [resonance-api-client](https://github.com/Quantus-Network/resonance-api-client/blob/master/examples/async/examples/merkle-airdrop-README.md) repository:
- `examples/ac-examples-async/examples/merkle_airdrop_cli.rs`
- `examples/ac-examples-async/examples/merkle_airdrop_cli-README.md` for the documentation

These tool demonstrates how to:
- Generate Merkle trees and proofs
- Create and fund airdrops
- Claim tokens using proofs
118 changes: 118 additions & 0 deletions pallets/merkle-airdrop/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Benchmarking setup for pallet-merkle-airdrop

use super::*;

#[allow(unused)]
use crate::Pallet as MerkleAirdrop;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
extern crate alloc;
use alloc::vec;
use frame_support::traits::fungible::{Inspect, Mutate};
use frame_support::BoundedVec;
use sp_runtime::traits::Saturating;

#[benchmarks]
mod benchmarks {
use super::*;

#[benchmark]
fn create_airdrop() {
let caller: T::AccountId = whitelisted_caller();
let merkle_root = [0u8; 32];

#[extrinsic_call]
create_airdrop(RawOrigin::Signed(caller), merkle_root);
}

#[benchmark]
fn fund_airdrop() {
let caller: T::AccountId = whitelisted_caller();
let merkle_root = [0u8; 32];

let airdrop_id = MerkleAirdrop::<T>::next_airdrop_id();
AirdropMerkleRoots::<T>::insert(airdrop_id, merkle_root);
AirdropCreators::<T>::insert(airdrop_id, caller.clone());
NextAirdropId::<T>::put(airdrop_id + 1);

let amount: BalanceOf<T> = 1u32.into();

// Get ED and ensure caller has sufficient balance
let ed = <T::Currency as Inspect<T::AccountId>>::minimum_balance();

let caller_balance = ed.saturating_mul(10u32.into()).saturating_add(amount);
<T::Currency as Mutate<T::AccountId>>::set_balance(&caller, caller_balance);

<T::Currency as Mutate<T::AccountId>>::set_balance(&MerkleAirdrop::<T>::account_id(), ed);

#[extrinsic_call]
fund_airdrop(RawOrigin::Signed(caller), airdrop_id, amount);
}

#[benchmark]
fn claim() {
let caller: T::AccountId = whitelisted_caller();
let recipient: T::AccountId = whitelisted_caller();

let amount: BalanceOf<T> = 1u32.into();

let leaf_hash = MerkleAirdrop::<T>::calculate_leaf_hash_blake2(&recipient, amount);
let merkle_root = leaf_hash;

let airdrop_id = MerkleAirdrop::<T>::next_airdrop_id();
AirdropMerkleRoots::<T>::insert(airdrop_id, merkle_root);
NextAirdropId::<T>::put(airdrop_id + 1);

let ed = <T::Currency as Inspect<T::AccountId>>::minimum_balance();
let large_balance = ed.saturating_mul(1_000_000u32.into());

<T::Currency as Mutate<T::AccountId>>::set_balance(&caller, large_balance);
<T::Currency as Mutate<T::AccountId>>::set_balance(&recipient, large_balance);
<T::Currency as Mutate<T::AccountId>>::set_balance(
&MerkleAirdrop::<T>::account_id(),
large_balance,
);

AirdropBalances::<T>::insert(airdrop_id, large_balance);

let empty_proof = vec![];
let merkle_proof = BoundedVec::<MerkleHash, T::MaxProofs>::try_from(empty_proof)
.expect("Empty proof should fit in bound");

#[extrinsic_call]
claim(RawOrigin::None, airdrop_id, recipient, amount, merkle_proof);
}

#[benchmark]
fn delete_airdrop() {
let caller: T::AccountId = whitelisted_caller();
let merkle_root = [0u8; 32];

// Create an airdrop first
let airdrop_id = MerkleAirdrop::<T>::next_airdrop_id();
AirdropMerkleRoots::<T>::insert(airdrop_id, merkle_root);
AirdropCreators::<T>::insert(airdrop_id, caller.clone());
NextAirdropId::<T>::put(airdrop_id + 1);

let ed = <T::Currency as Inspect<T::AccountId>>::minimum_balance();
let tiny_amount: BalanceOf<T> = 1u32.into();
let large_balance = ed.saturating_mul(1_000_000u32.into());

<T::Currency as Mutate<T::AccountId>>::set_balance(&caller, large_balance);
<T::Currency as Mutate<T::AccountId>>::set_balance(
&MerkleAirdrop::<T>::account_id(),
large_balance,
);

AirdropBalances::<T>::insert(airdrop_id, tiny_amount);

#[extrinsic_call]
delete_airdrop(RawOrigin::Signed(caller), airdrop_id);
}

impl_benchmark_test_suite!(
MerkleAirdrop,
crate::mock::new_test_ext(),
crate::mock::Test
);
}
Loading