Skip to content

Commit bad793f

Browse files
committed
pallet-dap: implement FundingSource and basic DAP pallet structure
Minimal initial implementation of pallet-dap. Only FundingSink is functional and allows replacing burns in other pallets with returns to DAP buffer.
1 parent 6c5e001 commit bad793f

File tree

4 files changed

+293
-0
lines changed

4 files changed

+293
-0
lines changed

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ members = [
355355
"substrate/frame/contracts/uapi",
356356
"substrate/frame/conviction-voting",
357357
"substrate/frame/core-fellowship",
358+
"substrate/frame/dap",
358359
"substrate/frame/delegated-staking",
359360
"substrate/frame/democracy",
360361
"substrate/frame/derivatives",
@@ -983,6 +984,7 @@ pallet-contracts-proc-macro = { path = "substrate/frame/contracts/proc-macro", d
983984
pallet-contracts-uapi = { path = "substrate/frame/contracts/uapi", default-features = false }
984985
pallet-conviction-voting = { path = "substrate/frame/conviction-voting", default-features = false }
985986
pallet-core-fellowship = { path = "substrate/frame/core-fellowship", default-features = false }
987+
pallet-dap = { path = "substrate/frame/dap", default-features = false }
986988
pallet-default-config-example = { path = "substrate/frame/examples/default-config", default-features = false }
987989
pallet-delegated-staking = { path = "substrate/frame/delegated-staking", default-features = false }
988990
pallet-democracy = { path = "substrate/frame/democracy", default-features = false }

substrate/frame/dap/Cargo.toml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
[package]
2+
name = "pallet-dap"
3+
version = "0.1.0"
4+
authors.workspace = true
5+
edition.workspace = true
6+
license = "Apache-2.0"
7+
homepage.workspace = true
8+
repository.workspace = true
9+
description = "FRAME pallet for Dynamic Allocation Pool (DAP)"
10+
11+
[lints]
12+
workspace = true
13+
14+
[package.metadata.docs.rs]
15+
targets = ["x86_64-unknown-linux-gnu"]
16+
17+
[dependencies]
18+
codec = { features = ["derive", "max-encoded-len"], workspace = true }
19+
frame-benchmarking = { optional = true, workspace = true }
20+
frame-support = { workspace = true }
21+
frame-system = { workspace = true }
22+
log = { workspace = true }
23+
scale-info = { features = ["derive"], workspace = true }
24+
sp-runtime = { workspace = true }
25+
26+
[dev-dependencies]
27+
pallet-balances = { workspace = true, default-features = true }
28+
sp-core = { workspace = true, default-features = true }
29+
sp-io = { workspace = true, default-features = true }
30+
31+
[features]
32+
default = ["std"]
33+
std = [
34+
"codec/std",
35+
"frame-benchmarking?/std",
36+
"frame-support/std",
37+
"frame-system/std",
38+
"log/std",
39+
"pallet-balances/std",
40+
"scale-info/std",
41+
"sp-core/std",
42+
"sp-io/std",
43+
"sp-runtime/std",
44+
]
45+
runtime-benchmarks = [
46+
"frame-benchmarking/runtime-benchmarks",
47+
"frame-support/runtime-benchmarks",
48+
"frame-system/runtime-benchmarks",
49+
"pallet-balances/runtime-benchmarks",
50+
"sp-runtime/runtime-benchmarks",
51+
]
52+
try-runtime = [
53+
"frame-support/try-runtime",
54+
"frame-system/try-runtime",
55+
"pallet-balances/try-runtime",
56+
"sp-runtime/try-runtime",
57+
]

substrate/frame/dap/src/lib.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
//! # Dynamic Allocation Pool (DAP) Pallet
19+
//!
20+
//! Minimal initial implementation: only `FundingSink` (return_funds) is functional.
21+
//! This allows replacing burns in other pallets with returns to DAP buffer.
22+
//!
23+
//! Future phases will add:
24+
//! - `FundingSource` (request_funds) for pulling funds
25+
//! - Issuance curve and minting logic
26+
//! - Distribution rules and scheduling
27+
28+
#![cfg_attr(not(feature = "std"), no_std)]
29+
30+
extern crate alloc;
31+
32+
use frame_support::{
33+
pallet_prelude::*,
34+
traits::{
35+
fungible::{Inspect, Mutate},
36+
tokens::{FundingSink, FundingSource, Preservation},
37+
},
38+
PalletId,
39+
};
40+
41+
pub use pallet::*;
42+
43+
const LOG_TARGET: &str = "runtime::dap";
44+
45+
/// The DAP pallet ID, used to derive the buffer account.
46+
pub const DAP_PALLET_ID: PalletId = PalletId(*b"dap/buff");
47+
48+
/// Type alias for balance.
49+
pub type BalanceOf<T> =
50+
<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
51+
52+
#[frame_support::pallet]
53+
pub mod pallet {
54+
use super::*;
55+
use frame_support::sp_runtime::traits::AccountIdConversion;
56+
57+
#[pallet::pallet]
58+
pub struct Pallet<T>(_);
59+
60+
#[pallet::config]
61+
pub trait Config: frame_system::Config {
62+
/// The currency type.
63+
type Currency: Inspect<Self::AccountId> + Mutate<Self::AccountId>;
64+
}
65+
66+
impl<T: Config> Pallet<T> {
67+
/// Get the DAP buffer account derived from the pallet ID.
68+
pub fn buffer_account() -> T::AccountId {
69+
DAP_PALLET_ID.into_account_truncating()
70+
}
71+
}
72+
73+
#[pallet::event]
74+
#[pallet::generate_deposit(pub(super) fn deposit_event)]
75+
pub enum Event<T: Config> {
76+
/// Funds returned to DAP buffer.
77+
FundsReturned { from: T::AccountId, amount: BalanceOf<T> },
78+
}
79+
80+
#[pallet::error]
81+
pub enum Error<T> {
82+
/// FundingSource not yet implemented.
83+
NotImplemented,
84+
}
85+
}
86+
87+
/// Implementation of FundingSource - NOT YET IMPLEMENTED.
88+
/// Will panic if called.
89+
pub struct PullFromDap<T>(core::marker::PhantomData<T>);
90+
91+
impl<T: Config> FundingSource<T::AccountId, BalanceOf<T>> for PullFromDap<T> {
92+
fn request_funds(
93+
_beneficiary: &T::AccountId,
94+
_amount: BalanceOf<T>,
95+
) -> Result<BalanceOf<T>, DispatchError> {
96+
unimplemented!("PullFromDap::request_funds not yet implemented")
97+
}
98+
}
99+
100+
/// Implementation of FundingSink that returns funds to DAP buffer.
101+
/// When using this, returned funds are transferred to the buffer account instead of being burned.
102+
pub struct ReturnToDap<T>(core::marker::PhantomData<T>);
103+
104+
impl<T: Config> FundingSink<T::AccountId, BalanceOf<T>> for ReturnToDap<T> {
105+
fn return_funds(source: &T::AccountId, amount: BalanceOf<T>) -> Result<(), DispatchError> {
106+
let buffer = Pallet::<T>::buffer_account();
107+
108+
T::Currency::transfer(source, &buffer, amount, Preservation::Preserve)?;
109+
110+
Pallet::<T>::deposit_event(Event::FundsReturned { from: source.clone(), amount });
111+
112+
log::debug!(
113+
target: LOG_TARGET,
114+
"Returned {amount:?} from {source:?} to DAP buffer"
115+
);
116+
117+
Ok(())
118+
}
119+
}
120+
121+
#[cfg(test)]
122+
mod tests {
123+
use super::*;
124+
use frame_support::{
125+
assert_noop, assert_ok, derive_impl, sp_runtime::traits::AccountIdConversion,
126+
traits::tokens::FundingSink,
127+
};
128+
use sp_runtime::BuildStorage;
129+
130+
type Block = frame_system::mocking::MockBlock<Test>;
131+
132+
frame_support::construct_runtime!(
133+
pub enum Test {
134+
System: frame_system,
135+
Balances: pallet_balances,
136+
Dap: crate,
137+
}
138+
);
139+
140+
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
141+
impl frame_system::Config for Test {
142+
type Block = Block;
143+
type AccountData = pallet_balances::AccountData<u64>;
144+
}
145+
146+
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
147+
impl pallet_balances::Config for Test {
148+
type AccountStore = System;
149+
}
150+
151+
impl Config for Test {
152+
type Currency = Balances;
153+
}
154+
155+
fn new_test_ext() -> sp_io::TestExternalities {
156+
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
157+
pallet_balances::GenesisConfig::<Test> {
158+
balances: vec![(1, 100), (2, 200), (3, 300)],
159+
..Default::default()
160+
}
161+
.assimilate_storage(&mut t)
162+
.unwrap();
163+
t.into()
164+
}
165+
166+
#[test]
167+
fn buffer_account_is_derived_from_pallet_id() {
168+
new_test_ext().execute_with(|| {
169+
let buffer = Dap::buffer_account();
170+
let expected: u64 = DAP_PALLET_ID.into_account_truncating();
171+
assert_eq!(buffer, expected);
172+
});
173+
}
174+
175+
#[test]
176+
fn return_funds_transfers_to_buffer() {
177+
new_test_ext().execute_with(|| {
178+
System::set_block_number(1);
179+
let buffer = Dap::buffer_account();
180+
181+
// Given: account 1 has 100, buffer has 0
182+
assert_eq!(Balances::free_balance(1), 100);
183+
assert_eq!(Balances::free_balance(buffer), 0);
184+
185+
// When: return 30 from account 1
186+
assert_ok!(ReturnToDap::<Test>::return_funds(&1, 30));
187+
188+
// Then: account 1 has 70, buffer has 30
189+
assert_eq!(Balances::free_balance(1), 70);
190+
assert_eq!(Balances::free_balance(buffer), 30);
191+
// ...and an event is emitted
192+
System::assert_last_event(Event::<Test>::FundsReturned { from: 1, amount: 30 }.into());
193+
});
194+
}
195+
196+
#[test]
197+
fn return_funds_fails_with_insufficient_balance() {
198+
new_test_ext().execute_with(|| {
199+
// Given: account 1 has 100
200+
assert_eq!(Balances::free_balance(1), 100);
201+
202+
// When: try to return 150 (more than balance)
203+
// Then: fails
204+
assert_noop!(
205+
ReturnToDap::<Test>::return_funds(&1, 150),
206+
sp_runtime::TokenError::FundsUnavailable
207+
);
208+
});
209+
}
210+
211+
#[test]
212+
#[should_panic(expected = "not yet implemented")]
213+
fn pull_from_dap_panics() {
214+
new_test_ext().execute_with(|| {
215+
let _ = PullFromDap::<Test>::request_funds(&1, 10);
216+
});
217+
}
218+
}

0 commit comments

Comments
 (0)