Closed Chralt98 closed 3 years ago
@noctrlz updated one of them https://github.com/substrate-developer-hub/recipes/pull/450
To be honest, picking a simple one and updating it would be a great way to learn.
@JoshOrndorff thank you for your quick reply. I am facing the issue to translate a more complex one (simple_crowdfund).
But I get an error for inserting objects to the Funds structure. And under impl
#![cfg_attr(not(feature = "std"), no_std)]
/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://substrate.dev/docs/en/knowledgebase/runtime/frame>
use frame_support::{
pallet_prelude::*,
traits::{Currency, ExistenceRequirement, ReservableCurrency, WithdrawReasons},
};
use sp_runtime::traits::{Saturating, Zero};
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
// EXPLA: additional struct for information about each fund which is stored
#[derive(Encode, Decode, Default, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct FundInfo<AccountId, Balance, BlockNumber> {
/// The account that will recieve the funds if the campaign is successful
beneficiary: AccountId,
/// The amount of deposit placed
deposit: Balance,
/// The total amount raised
raised: Balance,
/// Block number after which funding must have succeeded
end: BlockNumber,
/// Upper bound on `raised`
goal: Balance,
}
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
type FundInfoOf<T> =
FundInfo<AccountIdOf<T>, BalanceOf<T>, <T as frame_system::Config>::BlockNumber>;
pub use pallet::*;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_system::pallet_prelude::*;
// EXPLA: number how many funds have been ever created
/// Simple index for identifying a fund.
pub type FundIndex = u32;
/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// The currency in which the crowdfunds will be denominated
type Currency: ReservableCurrency<Self::AccountId>;
/// The amount to be held on deposit by the owner of a crowdfund
#[pallet::constant]
type SubmissionDeposit: Get<BalanceOf<Self>>;
/// The minimum amount that may be contributed into a crowdfund. Should almost certainly be at
/// least ExistentialDeposit.
#[pallet::constant]
type MinContribution: Get<BalanceOf<Self>>;
/// The period of time (in blocks) after an unsuccessful crowdfund ending during which
/// contributors are able to withdraw their funds. After this period, their funds are lost.
#[pallet::constant]
type RetirementPeriod: Get<Self::BlockNumber>;
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
#[pallet::storage]
#[pallet::getter(fn funds)]
pub(super) type Funds<T> = StorageMap<_, Blake2_128Concat, FundIndex, Option<FundInfoOf<T>>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn fund_count)]
pub(super) type FundCount<T> = StorageValue<_, FundIndex, ValueQuery>;
// EXPLA: storage additional comment
// EXPLA: child trie (information about how many funds been contributed) this is not explicity declared anywhere
// EXPLA: query the child trie via the Child Trie API
// Additional information is stored i na child trie. See the helper
// functions in the impl<T: Config> Pallet<T> block below
// TODO: remove this storage item later
// The pallet's runtime storage items.
// https://substrate.dev/docs/en/knowledgebase/runtime/storage
#[pallet::storage]
#[pallet::getter(fn something)]
// Learn more about declaring storage items:
// https://substrate.dev/docs/en/knowledgebase/runtime/storage#declaring-storage-items
pub type Something<T> = StorageValue<_, u32>;
// Pallets use events to inform users when important changes are made.
// https://substrate.dev/docs/en/knowledgebase/runtime/events
#[pallet::event]
#[pallet::metadata(T::AccountId = "AccountId", T::BlockNumber = "BlockNumber", BalanceOf<T> = "Balance")]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Event documentation should end with an array that provides descriptive names for event
/// parameters. [something, who]
SomethingStored(u32, T::AccountId),
Created(FundIndex, T::BlockNumber),
Contributed(T::AccountId, FundIndex, BalanceOf<T>, T::BlockNumber),
Withdrew(T::AccountId, FundIndex, BalanceOf<T>, T::BlockNumber),
Retiring(FundIndex, T::BlockNumber),
Dissolved(FundIndex, T::BlockNumber, T::AccountId),
Dispensed(FundIndex, T::BlockNumber, T::AccountId),
}
// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
/// Crowdfund must end after it starts
EndTooEarly,
/// Must contribute at least the minimum amount of funds
ContributionTooSmall,
/// The fund index specified does not exist
InvalidIndex,
/// The crowdfund's contribution period has ended; no more contributions will be accepted
ContributionPeriodOver,
/// You may not withdraw or dispense funds while the fund is still active
FundStillActive,
/// You cannot withdraw funds because you have not contributed any
NoContribution,
/// You cannot dissolve a fund that has not yet completed its retirement period
FundNotRetired,
/// Cannot dispense funds from an unsuccessful fund
UnsuccessfulFund,
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
// EXPLA: Child Trie API
// EXPLA: this pallet uses one trie for each active crowdfund => need to generate an unique child info for each crowdfund (look pub fn id_from_index(index: FundIndex) -> child::ChildInfo below)
// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Create a new fund
#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
pub fn create(
origin: OriginFor<T>,
beneficiary: AccountIdOf<T>,
goal: BalanceOf<T>,
end: T::BlockNumber,
) -> DispatchResult {
let creator = ensure_signed(origin)?;
let now = <frame_system::Pallet<T>>::block_number();
ensure!(end > now, Error::<T>::EndTooEarly);
let deposit = T::SubmissionDeposit::get();
let imb = T::Currency::withdraw(
&creator,
deposit,
WithdrawReasons::TRANSFER,
ExistenceRequirement::AllowDeath,
)?;
let index = FundCount::get();
// not protected against overflow, see safemath section
FundCount::put(index + 1);
// No fees are paid here if we need to create this account; that's why we don't just
// use the stock `transfer`.
T::Currency::resolve_creating(&Self::fund_account_id(index), imb);
<Funds<T>>::insert(
index,
FundInfo {
beneficiary,
deposit,
raised: Zero::zero(),
end,
goal,
},
);
Self::deposit_event(Event::Created(index, now));
// Return a successful DispatchResultWithPostInfo
Ok(())
}
/// Contribute funds to an existing fund
#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
pub fn contribute(
origin: OriginFor<T>,
index: FundIndex,
value: BalanceOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(
value >= T::MinContribution::get(),
Error::<T>::ContributionTooSmall
);
let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidIndex)?;
// Make sure crowdfund has not ended
let now = <frame_system::Pallet<T>>::block_number();
ensure!(fund.end > now, Error::<T>::ContributionPeriodOver);
// Add contribution to the fund
T::Currency::transfer(
&who,
&Self::fund_account_id(index),
value,
ExistenceRequirement::AllowDeath,
)?;
fund.raised += value;
Funds::<T>::insert(index, &fund);
let balance = Self::contribution_get(index, &who);
let balance = balance.saturating_add(value);
Self::contribution_put(index, &who, &balance);
Self::deposit_event(Event::Contributed(who, index, balance, now));
Ok(())
}
/// Withdraw full balance of a contributor to a fund
#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
pub fn withdraw(
origin: OriginFor<T>,
#[pallet::compact] index: FundIndex,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidIndex)?;
let now = <frame_system::Pallet<T>>::block_number();
ensure!(fund.end < now, Error::<T>::FundStillActive);
let balance = Self::contribution_get(index, &who);
ensure!(balance > Zero::zero(), Error::<T>::NoContribution);
// Return funds to caller without charging a transfer fee
let _ = T::Currency::resolve_into_existing(
&who,
T::Currency::withdraw(
&Self::fund_account_id(index),
balance,
WithdrawReasons::TRANSFER,
ExistenceRequirement::AllowDeath,
)?,
);
// Update storage
Self::contribution_kill(index, &who);
fund.raised = fund.raised.saturating_sub(balance);
<Funds<T>>::insert(index, &fund);
Self::deposit_event(Event::Withdrew(who, index, balance, now));
Ok(())
}
/// Dissolve an entire crowdfund after its retirement period has expired.
/// Anyone can call this function, and they are incentivized to do so because
/// they inherit the deposit.
#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
pub fn dissolve(origin: OriginFor<T>, index: FundIndex) -> DispatchResult {
let reporter = ensure_signed(origin)?;
let fund = Self::funds(index).ok_or(Error::<T>::InvalidIndex)?;
// Check that enough time has passed to remove from storage
let now = <frame_system::Pallet<T>>::block_number();
ensure!(
now >= fund.end + T::RetirementPeriod::get(),
Error::<T>::FundNotRetired
);
let account = Self::fund_account_id(index);
// Dissolver collects the deposit and any remaining funds
let _ = T::Currency::resolve_creating(
&reporter,
T::Currency::withdraw(
&account,
fund.deposit + fund.raised,
WithdrawReasons::TRANSFER,
ExistenceRequirement::AllowDeath,
)?,
);
// Remove the fund info from storage
<Funds<T>>::remove(index);
// Remove all the contributor info from storage in a single write.
// This is possible thanks to the use of a child tree.
Self::crowdfund_kill(index);
Self::deposit_event(Event::Dissolved(index, now, reporter));
Ok(())
}
// EXPLA: final reward spending of the crowdfund
/// Dispense a payment to the beneficiary of a successful crowdfund.
/// The beneficiary receives the contributed funds and the caller receives
/// the deposit as a reward to incentivize clearing settled crowdfunds out of storage.
#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
pub fn dispense(origin: OriginFor<T>, index: FundIndex) -> DispatchResult {
let caller = ensure_signed(origin)?;
let fund = Self::funds(index).ok_or(Error::<T>::InvalidIndex)?;
// Check that enough time has passed to remove from storage
let now = <frame_system::Pallet<T>>::block_number();
ensure!(now >= fund.end, Error::<T>::FundStillActive);
// Check that the fund was actually successful
ensure!(fund.raised >= fund.goal, Error::<T>::UnsuccessfulFund);
let account = Self::fund_account_id(index);
// Beneficiary collects the contributed funds
let _ = T::Currency::resolve_creating(
&fund.beneficiary,
T::Currency::withdraw(
&account,
fund.raised,
WithdrawReasons::TRANSFER,
ExistenceRequirement::AllowDeath,
)?,
);
// EXPLA: Data from finished funds takes up space on chain, so it is best to settle the fund and cleanup the data as soon as possible.
// EXPLA: that is why the caller gets a deposit (so the first caller is incentivised to clear the data)
// Caller collects the deposit
let _ = T::Currency::resolve_creating(
&caller,
T::Currency::withdraw(
&account,
fund.deposit,
WithdrawReasons::TRANSFER,
ExistenceRequirement::AllowDeath,
)?,
);
// Remove the fund info from storage
<Funds<T>>::remove(index);
// Remove all the contributor info from storage in a single write.
// This is possible thanks to the use of a child tree.
Self::crowdfund_kill(index);
Self::deposit_event(Event::Dispensed(index, now, caller));
Ok(())
}
/*
read storage example
/// An example dispatchable that may throw a custom error.
#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
pub fn cause_error(origin: OriginFor<T>) -> DispatchResult {
let _who = ensure_signed(origin)?;
// Read a value from storage.
match <Something<T>>::get() {
// Return an error if the value has not been set.
None => Err(Error::<T>::NoneValue)?,
Some(old) => {
// Increment the value read from storage; will error in the event of overflow.
let new = old.checked_add(1).ok_or(Error::<T>::StorageOverflow)?;
// Update the value in storage with the incremented result.
<Something<T>>::put(new);
Ok(())
}
}
}
*/
}
}
// EXPLA: Child Trie API
// EXPLA: this pallet uses one trie for each active crowdfund => need to generate an unique child info for each crowdfund (look pub fn id_from_index(index: FundIndex) -> child::ChildInfo below)
impl<T: Config> Pallet<T> {
/// The account ID of the fund pot.
///
/// This actually does computation. If you need to keep using it, then make sure you cache the
/// value and only call this once.
pub fn fund_account_id(index: FundIndex) -> T::AccountId {
PALLET_ID.into_sub_account(index)
}
/// Find the ID associated with the fund
///
/// Each fund stores information about its contributors and their contributions in a child trie
/// This helper function calculates the id of the associated child trie.
pub fn id_from_index(index: FundIndex) -> child::ChildInfo {
let mut buf = Vec::new();
buf.extend_from_slice(b"crowdfnd");
buf.extend_from_slice(&index.to_le_bytes()[..]);
child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref())
}
/// Record a contribution in the associated child trie.
pub fn contribution_put(index: FundIndex, who: &T::AccountId, balance: &BalanceOf<T>) {
let id = Self::id_from_index(index);
who.using_encoded(|b| child::put(&id, b, &balance));
}
/// Lookup a contribution in the associated child trie.
pub fn contribution_get(index: FundIndex, who: &T::AccountId) -> BalanceOf<T> {
let id = Self::id_from_index(index);
who.using_encoded(|b| child::get_or_default::<BalanceOf<T>>(&id, b))
}
/// Remove a contribution from an associated child trie.
pub fn contribution_kill(index: FundIndex, who: &T::AccountId) {
let id = Self::id_from_index(index);
who.using_encoded(|b| child::kill(&id, b));
}
/// Remove the entire record of contributions in the associated child trie in a single
/// storage write.
pub fn crowdfund_kill(index: FundIndex) {
let id = Self::id_from_index(index);
// The None here means we aren't setting a limit to how many keys to delete.
// Limiting can be useful, but is beyond the scope of this recipe. For more info, see
// https://crates.parity.io/frame_support/storage/child/fn.kill_storage.html
child::kill_storage(&id, None);
}
}
The next problem is, that Rust cannot find struct, variant or union type GenesisConfig
in crate pallet_balances
.
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: vec![(1, 1000), (2, 2000), (3, 3000), (4, 4000)],
}.assimilate_storage(&mut t).unwrap();
t.into()
}
Hey, it would be nice to see the examples with the newer version of FRAME (v2). Is something in sight?