From 7f6ef6fb7ba67f56b7ea39d3e3d58a0fa99390c1 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sat, 27 Mar 2021 17:36:48 +0100 Subject: [PATCH] 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 --- polkadot/runtime/common/src/crowdloan.rs | 94 ++++++++++++++++--- .../runtime/common/src/integration_tests.rs | 3 +- polkadot/runtime/rococo/src/lib.rs | 3 + 3 files changed, 87 insertions(+), 13 deletions(-) diff --git a/polkadot/runtime/common/src/crowdloan.rs b/polkadot/runtime/common/src/crowdloan.rs index d5233a456e..e2e9f4e141 100644 --- a/polkadot/runtime/common/src/crowdloan.rs +++ b/polkadot/runtime/common/src/crowdloan.rs @@ -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; + /// 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), } } @@ -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, - signature: Option + signature: Option, ) { let who = ensure_signed(origin)?; @@ -370,7 +379,7 @@ decl_module! { let now = >::block_number(); ensure!(now < fund.end, Error::::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::::InvalidSignature)?; @@ -382,7 +391,7 @@ decl_module! { CurrencyOf::::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::::NoContributions); CurrencyOf::::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) { + let who = ensure_signed(origin)?; + + ensure!(memo.len() <= T::MaxMemoLength::get().into(), Error::::MemoTooLarge); + let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + + let (balance, _) = Self::contribution_get(fund.trie_index, &who); + ensure!(balance > Zero::zero(), Error::::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 Module { child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref()) } - pub fn contribution_put(index: TrieIndex, who: &T::AccountId, balance: &BalanceOf) { - who.using_encoded(|b| child::put(&Self::id_from_index(index), b, balance)); + pub fn contribution_put(index: TrieIndex, who: &T::AccountId, balance: &BalanceOf, 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 { - who.using_encoded(|b| child::get_or_default::>( + pub fn contribution_get(index: TrieIndex, who: &T::AccountId) -> (BalanceOf, Vec) { + who.using_encoded(|b| child::get_or_default::<(BalanceOf, Vec)>( &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; 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 = 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::::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::::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::::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::(RawEvent::Edited(para_id).into()) } + add_memo { + let fund_index = create_fund::(1, 100u32.into()); + let caller: T::AccountId = whitelisted_caller(); + contribute_fund::(&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::::get(fund_index).expect("fund was created..."); + assert_eq!( + Crowdloan::::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. diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index 162716bbed..2c299ad7b8 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -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; } diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 6b805ac584..33e1d58502 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -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; }