Unverified Commit f9633940 authored by Shawn Tabrizi's avatar Shawn Tabrizi Committed by GitHub
Browse files

Introduce `add_memo` for Crowdloans (#2728)

* Add memo, but don't use it yet

* add_memo

* add weights

* Update lib.rs

* Update crowdloan.rs

* add event
parent a888f0cc
......@@ -101,6 +101,7 @@ pub trait WeightInfo {
fn withdraw() -> Weight;
fn dissolve(k: u32, ) -> Weight;
fn edit() -> Weight;
fn add_memo() -> Weight;
fn on_initialize(n: u32, ) -> Weight;
}
......@@ -111,6 +112,7 @@ impl WeightInfo for TestWeightInfo {
fn withdraw() -> Weight { 0 }
fn dissolve(_k: u32, ) -> Weight { 0 }
fn edit() -> Weight { 0 }
fn add_memo() -> Weight { 0 }
fn on_initialize(_n: u32, ) -> Weight { 0 }
}
......@@ -148,6 +150,9 @@ pub trait Config: frame_system::Config {
LeasePeriod=Self::BlockNumber,
>;
/// The maximum length for the memo attached to a crowdloan contribution.
type MaxMemoLength: Get<u8>;
/// Weight Information for the Extrinsics in the Pallet
type WeightInfo: WeightInfo;
}
......@@ -242,6 +247,8 @@ decl_event! {
HandleBidResult(ParaId, DispatchResult),
/// The configuration to a crowdloan has been edited. [fund_index]
Edited(ParaId),
/// A memo has been updated. [who, fund_index, memo]
MemoUpdated(AccountId, ParaId, Vec<u8>),
}
}
......@@ -287,6 +294,8 @@ decl_error! {
NotReadyToDissolve,
/// Invalid signature.
InvalidSignature,
/// The provided memo is too large.
MemoTooLarge,
}
}
......@@ -357,7 +366,7 @@ decl_module! {
pub fn contribute(origin,
#[compact] index: ParaId,
#[compact] value: BalanceOf<T>,
signature: Option<MultiSignature>
signature: Option<MultiSignature>,
) {
let who = ensure_signed(origin)?;
......@@ -370,7 +379,7 @@ decl_module! {
let now = <frame_system::Pallet<T>>::block_number();
ensure!(now < fund.end, Error::<T>::ContributionPeriodOver);
let old_balance = Self::contribution_get(fund.trie_index, &who);
let (old_balance, memo) = Self::contribution_get(fund.trie_index, &who);
if let Some(ref verifier) = fund.verifier {
let signature = signature.ok_or(Error::<T>::InvalidSignature)?;
......@@ -382,7 +391,7 @@ decl_module! {
CurrencyOf::<T>::transfer(&who, &Self::fund_account_id(index), value, AllowDeath)?;
let balance = old_balance.saturating_add(value);
Self::contribution_put(fund.trie_index, &who, &balance);
Self::contribution_put(fund.trie_index, &who, &balance, &memo);
if T::Auctioneer::is_ending(now).is_some() {
match fund.last_contribution {
......@@ -443,7 +452,7 @@ decl_module! {
let fund_account = Self::fund_account_id(index);
Self::ensure_crowdloan_ended(now, &fund_account, &fund)?;
let balance = Self::contribution_get(fund.trie_index, &who);
let (balance, _) = Self::contribution_get(fund.trie_index, &who);
ensure!(balance > Zero::zero(), Error::<T>::NoContributions);
CurrencyOf::<T>::transfer(&fund_account, &who, balance, AllowDeath)?;
......@@ -531,6 +540,23 @@ decl_module! {
Self::deposit_event(RawEvent::Edited(index));
}
/// Add an optional memo to an existing crowdloan contribution.
///
/// Origin must be Signed, and the user must have contributed to the crowdloan.
#[weight = T::WeightInfo::add_memo()]
pub fn add_memo(origin, index: ParaId, memo: Vec<u8>) {
let who = ensure_signed(origin)?;
ensure!(memo.len() <= T::MaxMemoLength::get().into(), Error::<T>::MemoTooLarge);
let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
let (balance, _) = Self::contribution_get(fund.trie_index, &who);
ensure!(balance > Zero::zero(), Error::<T>::NoContributions);
Self::contribution_put(fund.trie_index, &who, &balance, &memo);
Self::deposit_event(RawEvent::MemoUpdated(who, index, memo));
}
fn on_initialize(n: T::BlockNumber) -> frame_support::weights::Weight {
if let Some(n) = T::Auctioneer::is_ending(n) {
if n.is_zero() {
......@@ -576,12 +602,12 @@ impl<T: Config> Module<T> {
child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref())
}
pub fn contribution_put(index: TrieIndex, who: &T::AccountId, balance: &BalanceOf<T>) {
who.using_encoded(|b| child::put(&Self::id_from_index(index), b, balance));
pub fn contribution_put(index: TrieIndex, who: &T::AccountId, balance: &BalanceOf<T>, memo: &[u8]) {
who.using_encoded(|b| child::put(&Self::id_from_index(index), b, &(balance, memo)));
}
pub fn contribution_get(index: TrieIndex, who: &T::AccountId) -> BalanceOf<T> {
who.using_encoded(|b| child::get_or_default::<BalanceOf<T>>(
pub fn contribution_get(index: TrieIndex, who: &T::AccountId) -> (BalanceOf<T>, Vec<u8>) {
who.using_encoded(|b| child::get_or_default::<(BalanceOf<T>, Vec<u8>)>(
&Self::id_from_index(index),
b,
))
......@@ -806,6 +832,7 @@ mod tests {
pub const RetirementPeriod: u64 = 5;
pub const CrowdloanModuleId: ModuleId = ModuleId(*b"py/cfund");
pub const RemoveKeysLimit: u32 = 10;
pub const MaxMemoLength: u8 = 32;
}
impl Config for Test {
......@@ -818,6 +845,7 @@ mod tests {
type RemoveKeysLimit = RemoveKeysLimit;
type Registrar = TestRegistrar<Test>;
type Auctioneer = TestAuctioneer;
type MaxMemoLength = MaxMemoLength;
type WeightInfo = crate::crowdloan::TestWeightInfo;
}
......@@ -865,7 +893,7 @@ mod tests {
assert_eq!(Crowdloan::funds(ParaId::from(0)), None);
let empty: Vec<ParaId> = Vec::new();
assert_eq!(Crowdloan::new_raise(), empty);
assert_eq!(Crowdloan::contribution_get(0u32, &1), 0);
assert_eq!(Crowdloan::contribution_get(0u32, &1).0, 0);
assert_eq!(Crowdloan::endings_count(), 0);
assert_ok!(TestAuctioneer::new_auction(5, 0));
......@@ -976,14 +1004,14 @@ mod tests {
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, None));
// No contributions yet
assert_eq!(Crowdloan::contribution_get(u32::from(para), &1), 0);
assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
// User 1 contributes to their own crowdloan
assert_ok!(Crowdloan::contribute(Origin::signed(1), para, 49, None));
// User 1 has spent some funds to do this, transfer fees **are** taken
assert_eq!(Balances::free_balance(1), 950);
// Contributions are stored in the trie
assert_eq!(Crowdloan::contribution_get(u32::from(para), &1), 49);
assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 49);
// Contributions appear in free balance of crowdloan
assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(para)), 49);
// Crowdloan is added to NewRaise
......@@ -1006,7 +1034,7 @@ mod tests {
assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, Some(pubkey.clone())));
// No contributions yet
assert_eq!(Crowdloan::contribution_get(u32::from(para), &1), 0);
assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
// Missing signature
assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 49, None), Error::<Test>::InvalidSignature);
......@@ -1351,6 +1379,34 @@ mod tests {
assert!(old_crowdloan.last_slot != new_crowdloan.last_slot);
});
}
#[test]
fn add_memo_works() {
new_test_ext().execute_with(|| {
let para_1 = new_para();
assert_ok!(Crowdloan::create(Origin::signed(1), para_1, 1000, 1, 1, 9, None));
// Cant add a memo before you have contributed.
assert_noop!(
Crowdloan::add_memo(Origin::signed(1), para_1, b"hello, world".to_vec()),
Error::<Test>::NoContributions,
);
// Make a contribution. Initially no memo.
assert_ok!(Crowdloan::contribute(Origin::signed(1), para_1, 100, None));
assert_eq!(Crowdloan::contribution_get(0u32, &1), (100, vec![]));
// Can't place a memo that is too large.
assert_noop!(
Crowdloan::add_memo(Origin::signed(1), para_1, vec![123; 123]),
Error::<Test>::MemoTooLarge,
);
// Adding a memo to an existing contribution works
assert_ok!(Crowdloan::add_memo(Origin::signed(1), para_1, b"hello, world".to_vec()));
assert_eq!(Crowdloan::contribution_get(0u32, &1), (100, b"hello, world".to_vec()));
// Can contribute again and data persists
assert_ok!(Crowdloan::contribute(Origin::signed(1), para_1, 100, None));
assert_eq!(Crowdloan::contribution_get(0u32, &1), (200, b"hello, world".to_vec()));
});
}
}
#[cfg(feature = "runtime-benchmarks")]
......@@ -1520,6 +1576,20 @@ mod benchmarking {
assert_last_event::<T>(RawEvent::Edited(para_id).into())
}
add_memo {
let fund_index = create_fund::<T>(1, 100u32.into());
let caller: T::AccountId = whitelisted_caller();
contribute_fund::<T>(&caller, fund_index);
let worst_memo = vec![42; T::MaxMemoLength::get().into()];
}: _(RawOrigin::Signed(caller.clone()), fund_index, worst_memo.clone())
verify {
let fund = Funds::<T>::get(fund_index).expect("fund was created...");
assert_eq!(
Crowdloan::<T>::contribution_get(fund.trie_index, &caller),
(T::MinContribution::get(), worst_memo),
);
}
// Worst case scenario: N funds are all in the `NewRaise` list, we are
// in the beginning of the ending period, and each fund outbids the next
// over the same slot.
......
......@@ -216,7 +216,7 @@ parameter_types! {
pub const MinContribution: Balance = 1;
pub const RetirementPeriod: BlockNumber = 10;
pub const RemoveKeysLimit: u32 = 100;
pub const MaxMemoLength: u8 = 32;
}
impl crowdloan::Config for Test {
......@@ -229,6 +229,7 @@ impl crowdloan::Config for Test {
type RemoveKeysLimit = RemoveKeysLimit;
type Registrar = Registrar;
type Auctioneer = Auctions;
type MaxMemoLength = MaxMemoLength;
type WeightInfo = crate::crowdloan::TestWeightInfo;
}
......
......@@ -660,6 +660,8 @@ parameter_types! {
pub const MinContribution: Balance = 1 * DOLLARS;
pub const RetirementPeriod: BlockNumber = 6 * HOURS;
pub const RemoveKeysLimit: u32 = 500;
// Allow 32 bytes for an additional memo to a crowdloan.
pub const MaxMemoLength: u8 = 32;
}
impl crowdloan::Config for Runtime {
......@@ -672,6 +674,7 @@ impl crowdloan::Config for Runtime {
type RemoveKeysLimit = RemoveKeysLimit;
type Registrar = Registrar;
type Auctioneer = Auctions;
type MaxMemoLength = MaxMemoLength;
type WeightInfo = crowdloan::TestWeightInfo;
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment